datastax_rails 1.0.6 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -67,5 +67,10 @@ attributes on any model. DSR will only upload schema files if they have changed
67
67
 
68
68
  === Known issues
69
69
 
70
- Calling :find on a model with a bogus ID returns an empty model instead of RecordNotFound. This is due to a bug
71
- in DSE that is supposedly fixed in the upcoming 2.2 release.
70
+ Calling +find+ on a model with a bogus ID returns an empty model instead of RecordNotFound. This is due to a bug
71
+ in DSE that is supposedly fixed in the upcoming 2.2 release.
72
+
73
+ === More information
74
+
75
+ The documentation for DatastaxRails::Base and DatastaxRails::SearchMethods will give you quite a few examples
76
+ of things you can do. You can find a copy of the latest documentation on line at http://rdoc.info/github/jasonmk/datastax_rails/master/frames.
data/Rakefile CHANGED
@@ -16,6 +16,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
16
16
  rdoc.rdoc_dir = 'rdoc'
17
17
  rdoc.title = 'DatastaxRails'
18
18
  rdoc.options << '--line-numbers'
19
+ rdoc.options << '--main=README.rdoc'
19
20
  rdoc.rdoc_files.include('README.rdoc')
20
21
  rdoc.rdoc_files.include('lib/**/*.rb')
21
22
  end
@@ -67,4 +67,5 @@
67
67
  <% @copy_fields.each do |field| %>
68
68
  <copyField source="<%= field[:source] %>" dest="<%= field[:dest]%>"/>
69
69
  <% end %>
70
+
70
71
  </schema>
@@ -133,6 +133,32 @@ module DatastaxRails #:nodoc:
133
133
  # user.name = "David"
134
134
  # user.occupation = "Code Artist"
135
135
  #
136
+ # == Consistency
137
+ #
138
+ # Cassandra has a concept of consistency levels when it comes to saving records. For a
139
+ # detailed discussion on Cassandra data consistency, see:
140
+ # http://www.datastax.com/docs/1.0/dml/data_consistency
141
+ #
142
+ # DatastaxRails allows you to specify the consistency when you save and retrieve objects.
143
+ #
144
+ # user = User.new(:name => 'David')
145
+ # user.save(:consistency => 'ALL')
146
+ #
147
+ # User.create(params[:user], {:consistency => :local_quorum})
148
+ #
149
+ # User.consistency(:local_quorum).where(:name => 'David')
150
+ #
151
+ # The default consistency level in DatastaxRails is QUORUM for writes and for retrieval
152
+ # by ID. SOLR only supports a consistency level of ONE. See the documentation for
153
+ # SearchMethods#consistency for a more detailed explanation.
154
+ #
155
+ # The overall default consistency for a given model can be overridden by adding a method
156
+ # to the model like so:
157
+ #
158
+ # def default_consistency
159
+ # :local_quorum
160
+ # end
161
+ #
136
162
  # == Conditions
137
163
  #
138
164
  # Conditions are specified as a hash representing key/value pairs that will eventually be passed to SOLR or as
@@ -265,7 +291,6 @@ module DatastaxRails #:nodoc:
265
291
  include ActiveModel::MassAssignmentSecurity
266
292
 
267
293
  include Connection
268
- include Consistency
269
294
  include Identity
270
295
  include FinderMethods
271
296
  include Batches
@@ -281,7 +306,6 @@ module DatastaxRails #:nodoc:
281
306
  include Timestamps
282
307
  include Serialization
283
308
  include Migrations
284
- # include Mocking
285
309
 
286
310
  # Stores the default scope for the class
287
311
  class_attribute :default_scopes, :instance_writer => false
@@ -403,6 +427,10 @@ module DatastaxRails #:nodoc:
403
427
  self.class.attribute_names
404
428
  end
405
429
 
430
+ def valid_consistency?(level) #:nodoc:
431
+ self.class.validate_consistency(level.to_s.upcase)
432
+ end
433
+
406
434
  private
407
435
  def populate_with_current_scope_attributes
408
436
  return unless self.class.scope_attributes?
@@ -486,6 +514,10 @@ module DatastaxRails #:nodoc:
486
514
  search.raw_results.map { |result| result.primary_key }
487
515
  end
488
516
 
517
+ def valid_consistency?(level) #:nodoc:
518
+ DatastaxRails::Cql::Consistency::VALID_CONSISTENCY_LEVELS.include?(level)
519
+ end
520
+
489
521
  protected
490
522
 
491
523
 
@@ -17,7 +17,7 @@ module DatastaxRails
17
17
  define_model_callbacks :save, :create, :update, :destroy
18
18
  end
19
19
 
20
- def destroy #:nodoc:
20
+ def destroy(*) #:nodoc:
21
21
  _run_destroy_callbacks { super }
22
22
  end
23
23
 
@@ -1,10 +1,19 @@
1
1
  module DatastaxRails
2
2
  module Cql
3
3
  class Base
4
- def to_cql #:nodoc:
5
- nil
4
+ # Base initialize that sets the default consistency.
5
+ def initialize(klass, *args)
6
+ @consistency = klass.respond_to?(:default_consistency) ? klass.default_consistency.to_s.upcase : DatastaxRails::Cql::Consistency::QUORUM
7
+ end
8
+
9
+ # Abstract. Should be overridden by subclasses
10
+ def to_cql
11
+ raise NotImplementedError
6
12
  end
7
13
 
14
+ # Generates the CQL and calls Cassandra to execute it.
15
+ # If you are using this outside of Rails, then DatastaxRails::Base.connection must have
16
+ # already been set up (Rails does this for you).
8
17
  def execute
9
18
  cql = self.to_cql
10
19
  Rails.logger.debug(cql)
@@ -1,13 +1,14 @@
1
1
  module DatastaxRails
2
2
  module Cql
3
3
  module Consistency
4
+ ANY = 'ANY'
4
5
  ONE = 'ONE'
5
6
  QUORUM = 'QUORUM'
6
7
  LOCAL_QUORUM = 'LOCAL_QUORUM'
7
8
  EACH_QUORUM = 'EACH_QUORUM'
8
9
  ALL = 'ALL'
9
10
 
10
- VALID_CONSISTENCY_LEVELS = [ONE, QUORUM, LOCAL_QUORUM, EACH_QUORUM, ALL]
11
+ VALID_CONSISTENCY_LEVELS = [ANY, ONE, QUORUM, LOCAL_QUORUM, EACH_QUORUM, ALL]
11
12
  end
12
13
  end
13
14
  end
@@ -4,9 +4,9 @@ module DatastaxRails
4
4
  def initialize(klass, keys)
5
5
  @klass = klass
6
6
  @keys = keys
7
- @consistency = DatastaxRails::Cql::Consistency::QUORUM
8
7
  @timestamp = nil
9
8
  @columns = []
9
+ super
10
10
  end
11
11
 
12
12
  def using(consistency)
@@ -3,10 +3,10 @@ module DatastaxRails
3
3
  class Insert < Base
4
4
  def initialize(klass)
5
5
  @klass = klass
6
- @consistency = DatastaxRails::Cql::Consistency::QUORUM
7
6
  @ttl = nil
8
7
  @timestamp = nil
9
8
  @columns = {}
9
+ super
10
10
  end
11
11
 
12
12
  def using(consistency)
@@ -4,9 +4,9 @@ module DatastaxRails#:nodoc:
4
4
  def initialize(klass, select)
5
5
  @klass = klass
6
6
  @select = select.join(",")
7
- @consistency = DatastaxRails::Cql::Consistency::QUORUM
8
7
  @limit = nil
9
8
  @conditions = {}
9
+ super
10
10
  end
11
11
 
12
12
  def using(consistency)
@@ -3,6 +3,7 @@ module DatastaxRails
3
3
  class Truncate < Base
4
4
  def initialize(klass)
5
5
  @klass = klass
6
+ super
6
7
  end
7
8
 
8
9
  def to_cql
@@ -4,8 +4,8 @@ module DatastaxRails
4
4
  def initialize(klass, key)
5
5
  @klass = klass
6
6
  @key = key
7
- @consistency = DatastaxRails::Cql::Consistency::QUORUM
8
7
  @columns = {}
8
+ super
9
9
  end
10
10
 
11
11
  def using(consistency)
@@ -34,6 +34,7 @@ module DatastaxRails
34
34
  end
35
35
 
36
36
  def to_cql
37
+ columns = @columns.dup
37
38
  values = []
38
39
 
39
40
  stmt = "update #{@klass.column_family} using consistency #{@consistency} "
@@ -46,19 +47,21 @@ module DatastaxRails
46
47
  stmt << "AND TIMESTAMP #{@timestamp}"
47
48
  end
48
49
 
49
- stmt << "SET "
50
-
51
- first_entry = @columns.shift
52
-
53
- stmt << "#{first_entry.first.to_s} = ? "
54
- values << first_entry.last
55
-
56
- @columns.each do |k,v|
57
- stmt << ", #{k.to_s} = ? "
58
- values << v
50
+ unless columns.empty?
51
+ stmt << "SET "
52
+
53
+ first_entry = columns.shift
54
+
55
+ stmt << "#{first_entry.first.to_s} = ?"
56
+ values << first_entry.last
57
+
58
+ columns.each do |k,v|
59
+ stmt << ", #{k.to_s} = ?"
60
+ values << v
61
+ end
59
62
  end
60
63
 
61
- stmt << "WHERE KEY IN (?)"
64
+ stmt << " WHERE KEY IN (?)"
62
65
  values << @key
63
66
 
64
67
  CassandraCQL::Statement.sanitize(stmt, values)
@@ -1,16 +1,16 @@
1
1
  module DatastaxRails
2
- class DatastaxRailsError < StandardError
2
+ class DatastaxRailsError < StandardError #:nodoc:
3
3
  end
4
4
 
5
- class AssociationTypeMismatch < DatastaxRailsError
5
+ class AssociationTypeMismatch < DatastaxRailsError #:nodoc:
6
6
  end
7
7
 
8
- class RecordNotSaved < DatastaxRailsError
8
+ class RecordNotSaved < DatastaxRailsError #:nodoc:
9
9
  end
10
10
 
11
- class DeleteRestrictionError < DatastaxRailsError
11
+ class DeleteRestrictionError < DatastaxRailsError #:nodoc:
12
12
  end
13
13
 
14
- class RecordNotFound < DatastaxRailsError
14
+ class RecordNotFound < DatastaxRailsError #:nodoc:
15
15
  end
16
16
  end
@@ -9,8 +9,21 @@ module DatastaxRails
9
9
  module ClassMethods
10
10
  # Removes one or more records with corresponding keys
11
11
  def remove(*keys)
12
+ options = {}
13
+ if keys.last.is_a?(Hash)
14
+ options = keys.pop
15
+ end
12
16
  ActiveSupport::Notifications.instrument("remove.datastax_rails", :column_family => column_family, :key => key) do
13
- cql.delete(keys).using(thrift_write_consistency).execute
17
+ c = cql.delete(keys)
18
+ if(options[:consistency])
19
+ level = options[:consistency].to_s.upcase
20
+ if(valid_consistency?(level))
21
+ c.using(level)
22
+ else
23
+ raise ArgumentError, "'#{level}' is not a valid Cassandra consistency level"
24
+ end
25
+ end
26
+ c.execute
14
27
  end
15
28
  end
16
29
 
@@ -28,11 +41,20 @@ module DatastaxRails
28
41
  end
29
42
  end
30
43
 
31
- def write(key, attributes, schema_version)
44
+ def write(key, attributes, options = {})
32
45
  key.tap do |key|
33
- attributes = encode_attributes(attributes, schema_version)
46
+ attributes = encode_attributes(attributes, options[:schema_version])
34
47
  ActiveSupport::Notifications.instrument("insert.datastax_rails", :column_family => column_family, :key => key, :attributes => attributes) do
35
- cql.update(key.to_s).columns(attributes).using(thrift_write_consistency).execute
48
+ c = cql.update(key.to_s).columns(attributes)
49
+ if(options[:consistency])
50
+ level = options[:consistency].to_s.upcase
51
+ if(valid_consistency?(level))
52
+ c.using(options[:consistency])
53
+ else
54
+ raise ArgumentError, "'#{level}' is not a valid Cassandra consistency level"
55
+ end
56
+ end
57
+ c.execute
36
58
  end
37
59
  end
38
60
  end
@@ -89,8 +111,8 @@ module DatastaxRails
89
111
  create_or_update(options) || raise(RecordNotSaved)
90
112
  end
91
113
 
92
- def destroy
93
- self.class.remove(key)
114
+ def destroy(options = {})
115
+ self.class.remove(key, options)
94
116
  @destroyed = true
95
117
  freeze
96
118
  end
@@ -134,7 +156,7 @@ module DatastaxRails
134
156
 
135
157
  def write(options) #:nodoc:
136
158
  changed_attributes = changed.inject({}) { |h, n| h[n] = read_attribute(n); h }
137
- self.class.write(key, changed_attributes, schema_version)
159
+ self.class.write(key, changed_attributes, options.merge(:schema_version => schema_version))
138
160
  end
139
161
  end
140
162
  end
@@ -1,8 +1,8 @@
1
1
  require 'active_support/core_ext/class/attribute'
2
2
  require 'active_support/core_ext/object/inclusion'
3
3
 
4
- # This is shamelessly ripped from Active Record 3.1
5
4
  module DatastaxRails
5
+ # This is shamelessly ripped from Active Record 3.1
6
6
  # = DatastaxRails Reflection
7
7
  module Reflection # :nodoc:
8
8
  extend ActiveSupport::Concern
@@ -1,6 +1,31 @@
1
1
  module DatastaxRails
2
2
  module SearchMethods
3
3
 
4
+ # The default consistency level for DSR is QUORUM when searching by ID.
5
+ # For all searches using SOLR, the default consistency is ONE. Use this
6
+ # to override it in either case.
7
+ #
8
+ # Model.consistency(:local_quorum).find("12345")
9
+ #
10
+ # Note that Solr searches (basically anything but find by id) don't allow you
11
+ # to specify the consistency level. DSR sort of gets around this by taking the
12
+ # search results and then going to Cassandra to retrieve the objects by ID using
13
+ # the consistency you specified. However, it is possible that you might not get
14
+ # all of the records you are expecting if the SOLR node you were talking to hasn't
15
+ # been updated yet with the results. In practice, this should not happen for
16
+ # records that were created over your connection, but it is possible for other
17
+ # connections to create records that you can't see yet.
18
+ def consistency(level)
19
+ level = level.to_s.upcase
20
+ unless self.valid_consistency?(level)
21
+ raise ArgumentError, "'#{level}' is not a valid Cassandra consistency level"
22
+ end
23
+
24
+ clone.tap do |r|
25
+ r.consistency_value = level
26
+ end
27
+ end
28
+
4
29
  # Normally special characters (other than wild cards) are escaped before the search
5
30
  # is submitted. If you want to handle escaping yourself because you need to use
6
31
  # those special characters, then just include this in your chain.
@@ -36,7 +36,6 @@ module DatastaxRails
36
36
  @per_page_value = @klass.default_page_size
37
37
  @page_value = 1
38
38
  @use_solr_value = true
39
- @consistency_value = "QUORUM"
40
39
  @extensions = []
41
40
  @create_with_value = {}
42
41
  @escape_value = true
@@ -241,6 +240,9 @@ module DatastaxRails
241
240
  limit(1).select(:id).to_a.total_entries
242
241
  end
243
242
 
243
+ # Escapes values that might otherwise mess up the URL or confuse SOLR.
244
+ # If you want to handle escaping yourself for a particular query then
245
+ # SearchMethods#dont_escape is what you're looking for.
244
246
  def solr_escape(str)
245
247
  if str.is_a?(String) && escape_value
246
248
  str.gsub(SOLR_CHAR_RX, '\\\\\1')
@@ -317,9 +319,17 @@ module DatastaxRails
317
319
  response = rsolr.paginate(@page_value, @per_page_value, 'select', :params => params)["response"]
318
320
  results = DatastaxRails::Collection.new
319
321
  results.total_entries = response['numFound'].to_i
320
- response['docs'].each do |doc|
321
- key = doc.delete('id')
322
- results << @klass.instantiate(key,doc)
322
+ if @consistency_value
323
+ response['docs'].each do |doc|
324
+ id = doc['id']
325
+ obj = @klass.with_cassandra.consistency(@consistency_value).find_by_id(id)
326
+ results << obj if obj
327
+ end
328
+ else
329
+ response['docs'].each do |doc|
330
+ key = doc.delete('id')
331
+ results << @klass.instantiate(key,doc)
332
+ end
323
333
  end
324
334
  results
325
335
  end
@@ -1,3 +1,4 @@
1
1
  module DatastaxRails
2
- VERSION = "1.0.6"
2
+ # The current version of the gem
3
+ VERSION = "1.0.8"
3
4
  end
@@ -1,6 +1,7 @@
1
1
  require 'active_support/all'
2
2
  require 'cassandra-cql/1.0'
3
3
 
4
+ # Welcome to DatastaxRails. DatastaxRails::Base is probably a good place to start.
4
5
  module DatastaxRails
5
6
  extend ActiveSupport::Autoload
6
7
 
@@ -11,12 +12,10 @@ module DatastaxRails
11
12
  autoload :Callbacks
12
13
  autoload :Collection
13
14
  autoload :Connection
14
- autoload :Consistency
15
15
  autoload :Cql
16
16
  autoload :Cursor
17
17
  autoload :Identity
18
18
  autoload :Migrations
19
- #autoload :Mocking
20
19
  autoload :Persistence
21
20
  autoload :Reflection
22
21
  autoload :Relation
@@ -69,8 +68,9 @@ module DatastaxRails
69
68
  end
70
69
  end
71
70
 
72
- # Fixup the thrift library
73
71
  require "thrift"
72
+ # Thrift is how we communicate with Cassandra. We need to do a little fixup
73
+ # work to handle UTF-8 properly in Ruby 1.8.6.
74
74
  module Thrift
75
75
  class BinaryProtocol
76
76
  def write_string(str)
@@ -82,6 +82,5 @@ end
82
82
 
83
83
  require 'datastax_rails/railtie' if defined?(Rails)
84
84
  require 'datastax_rails/errors'
85
- # require 'solr_no_escape'
86
85
 
87
86
  ActiveSupport.run_load_hooks(:datastax_rails, DatastaxRails::Base)
@@ -12,4 +12,10 @@ describe DatastaxRails::Base do
12
12
  p.save!
13
13
  p.instance_variable_get(:@after_save_ran).should == "yup"
14
14
  end
15
+
16
+ it "should raise RecordNotFound when finding a bogus ID" do
17
+ pending "Datastax Enterprise 2.2 should fix this" do
18
+ lambda { Person.find("xyzzy") }.should raise_exception(DatastaxRails::RecordNotFound)
19
+ end
20
+ end
15
21
  end
@@ -4,9 +4,12 @@ describe DatastaxRails::Cql::Select do
4
4
  before(:each) do
5
5
  @model_class = mock("Model Class", :column_family => 'users')
6
6
  end
7
+
7
8
  it "should generate valid CQL" do
8
9
  cql = DatastaxRails::Cql::Select.new(@model_class, ["*"])
9
10
  cql.using(DatastaxRails::Cql::Consistency::QUORUM).conditions(:key => '12345').limit(1)
10
11
  cql.to_cql.should == "SELECT * FROM users USING CONSISTENCY QUORUM WHERE key = '12345' LIMIT 1"
11
12
  end
13
+
14
+ it_has_behavior "default_consistency"
12
15
  end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe DatastaxRails::Cql::Update do
4
+ before(:each) do
5
+ @model_class = mock("Model Class", :column_family => 'users')
6
+ end
7
+
8
+ it "should generate valid CQL" do
9
+ cql = DatastaxRails::Cql::Update.new(@model_class, "12345")
10
+ cql.using(DatastaxRails::Cql::Consistency::QUORUM).columns(:name => 'John', :age => '23')
11
+ cql.to_cql.should == "update users using consistency QUORUM SET age = '23', name = 'John' WHERE KEY IN ('12345')"
12
+ end
13
+
14
+ it_has_behavior "default_consistency"
15
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "DatastaxRails::Base" do
4
+ describe "persistence" do
5
+ describe "#create" do
6
+ it "should persist at the given consistency level" do
7
+ DatastaxRails::Base.connection.stub(:execute_cql_query)
8
+ DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(/USING CONSISTENCY LOCAL_QUORUM/i).and_return(true)
9
+ Person.create({:name => 'Steven'},{:consistency => 'LOCAL_QUORUM'})
10
+ end
11
+ end
12
+
13
+ describe "#save" do
14
+ it "should persist at the given consistency level" do
15
+ DatastaxRails::Base.connection.stub(:execute_cql_query)
16
+ DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(/USING CONSISTENCY LOCAL_QUORUM/i).and_return(true)
17
+ p=Person.new(:name => 'Steven')
18
+ p.save(:consistency => 'LOCAL_QUORUM')
19
+ end
20
+ end
21
+
22
+ describe "#remove" do
23
+ it "should remove at the given consistency level" do
24
+ p=Person.create(:name => 'Steven')
25
+ DatastaxRails::Base.connection.stub(:execute_cql_query)
26
+ DatastaxRails::Base.connection.should_receive(:execute_cql_query).with(/USING CONSISTENCY LOCAL_QUORUM/i).and_return(true)
27
+ p.destroy(:consistency => :local_quorum)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,6 +5,24 @@ describe DatastaxRails::Relation do
5
5
  @relation = DatastaxRails::Relation.new(Hobby, "hobbies")
6
6
  end
7
7
 
8
+ describe "#consistency" do
9
+ it "should throw an ArgumentError for invalid consistency levels" do
10
+ lambda { @relation.consistency(:foo) }.should raise_exception(ArgumentError)
11
+ end
12
+
13
+ it "should not raise an exception for a valid consistency level" do
14
+ lambda { @relation.consistency(:local_quorum) }.should_not raise_exception
15
+ end
16
+
17
+ it "should call cassandra to enforce consistency" do
18
+ h=Hobby.create(:name => 'swimming')
19
+ Hobby.commit_solr
20
+ Hobby.stub_chain(:with_cassandra,:consistency).and_return(@relation)
21
+ @relation.should_receive(:find_by_id).with(h.id)
22
+ @relation.consistency(:all).where(:name => 'swimming').all
23
+ end
24
+ end
25
+
8
26
  describe "#limit" do
9
27
  it "should limit the page size" do
10
28
  "a".upto("l") do |letter|