cequel 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +3 -0
  3. data/CHANGELOG.md +7 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/Gemfile +5 -4
  6. data/Gemfile.lock +215 -22
  7. data/README.md +19 -6
  8. data/Vagrantfile +6 -1
  9. data/lib/cequel.rb +3 -2
  10. data/lib/cequel/metal/batch.rb +16 -1
  11. data/lib/cequel/metal/data_set.rb +63 -39
  12. data/lib/cequel/metal/deleter.rb +2 -2
  13. data/lib/cequel/metal/incrementer.rb +2 -2
  14. data/lib/cequel/metal/inserter.rb +8 -6
  15. data/lib/cequel/metal/keyspace.rb +127 -34
  16. data/lib/cequel/metal/logger.rb +1 -1
  17. data/lib/cequel/metal/row.rb +3 -4
  18. data/lib/cequel/metal/updater.rb +2 -2
  19. data/lib/cequel/metal/writer.rb +18 -12
  20. data/lib/cequel/record/associations.rb +7 -1
  21. data/lib/cequel/record/bound.rb +1 -1
  22. data/lib/cequel/record/callbacks.rb +10 -6
  23. data/lib/cequel/record/data_set_builder.rb +13 -2
  24. data/lib/cequel/record/persistence.rb +14 -12
  25. data/lib/cequel/record/properties.rb +3 -3
  26. data/lib/cequel/record/railtie.rb +1 -1
  27. data/lib/cequel/record/record_set.rb +12 -12
  28. data/lib/cequel/record/schema.rb +5 -1
  29. data/lib/cequel/schema/keyspace.rb +1 -1
  30. data/lib/cequel/schema/table_property.rb +1 -1
  31. data/lib/cequel/type.rb +44 -10
  32. data/lib/cequel/uuids.rb +46 -0
  33. data/lib/cequel/version.rb +1 -1
  34. data/spec/examples/metal/data_set_spec.rb +93 -0
  35. data/spec/examples/metal/keyspace_spec.rb +74 -0
  36. data/spec/examples/record/associations_spec.rb +84 -0
  37. data/spec/examples/record/persistence_spec.rb +23 -0
  38. data/spec/examples/record/properties_spec.rb +1 -1
  39. data/spec/examples/record/record_set_spec.rb +12 -3
  40. data/spec/examples/record/schema_spec.rb +1 -1
  41. data/spec/examples/record/secondary_index_spec.rb +1 -1
  42. data/spec/examples/schema/table_reader_spec.rb +2 -3
  43. data/spec/examples/spec_helper.rb +8 -0
  44. data/spec/examples/type_spec.rb +7 -5
  45. data/spec/examples/uuids_spec.rb +22 -0
  46. data/spec/support/helpers.rb +25 -19
  47. data/templates/config/cequel.yml +5 -5
  48. metadata +40 -33
@@ -0,0 +1,46 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Cequel
3
+ #
4
+ # This module adds some utility methods for generating and type-checking UUID
5
+ # objects for use with Cequel. These methods are provided because the actual
6
+ # UUID implementation is an artifact of the underlying driver;
7
+ # initializing/typechecking those driver classes directly is potentially
8
+ # breaking.
9
+ #
10
+ module Uuids
11
+ #
12
+ # Create a UUID
13
+ #
14
+ # @param value [Time,String,Integer] timestamp to assign to the UUID, or
15
+ # numeric or string representation of the UUID
16
+ # @return a UUID appropriate for use with Cequel
17
+ #
18
+ def uuid(value = nil)
19
+ if value.nil?
20
+ timeuuid_generator.next
21
+ elsif value.is_a?(Time)
22
+ timeuuid_generator.from_time(value)
23
+ elsif value.is_a?(DateTime)
24
+ timeuuid_generator.from_time(Time.at(value.to_f))
25
+ else
26
+ Type::Timeuuid.instance.cast(value)
27
+ end
28
+ end
29
+
30
+ #
31
+ # Determine if an object is a UUID
32
+ #
33
+ # @param object an object to check
34
+ # @return [Boolean] true if the object is recognized by Cequel as a UUID
35
+ #
36
+ def uuid?(object)
37
+ object.is_a?(Cql::Uuid)
38
+ end
39
+
40
+ private
41
+
42
+ def timeuuid_generator
43
+ @timeuuid_generator ||= Cql::TimeUuid::Generator.new
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Cequel
3
3
  # The current version of the library
4
- VERSION = '1.0.4'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -84,6 +84,12 @@ describe Cequel::Metal::DataSet do
84
84
  first.writetime(:title).should == (time.to_f * 1_000_000).to_i
85
85
  end
86
86
 
87
+ it 'should insert row with given consistency' do
88
+ expect_query_with_consistency(/INSERT/, :one) do
89
+ cequel[:posts].insert(row, consistency: :one)
90
+ end
91
+ end
92
+
87
93
  it 'should include multiple arguments joined by AND' do
88
94
  cequel.schema.truncate_table(:posts)
89
95
  time = 1.day.ago
@@ -118,6 +124,13 @@ describe Cequel::Metal::DataSet do
118
124
  row.writetime(:title).should == (time.to_f * 1_000_000).to_i
119
125
  end
120
126
 
127
+ it 'should send update statement with given consistency' do
128
+ expect_query_with_consistency(/UPDATE/, :one) do
129
+ cequel[:posts].where(row_keys).update(
130
+ {title: 'Marshmallows'}, consistency: :one)
131
+ end
132
+ end
133
+
121
134
  it 'should overwrite list column' do
122
135
  cequel[:posts].where(row_keys).
123
136
  update(categories: ['Big Data', 'Cassandra'])
@@ -357,6 +370,12 @@ describe Cequel::Metal::DataSet do
357
370
  # This means timestamp is working, since the earlier timestamp would cause
358
371
  # Cassandra to ignore the deletion
359
372
  end
373
+
374
+ it 'should send delete with specified consistency' do
375
+ expect_query_with_consistency(/DELETE/, :one) do
376
+ cequel[:posts].where(row_keys).delete(:body, :consistency => :one)
377
+ end
378
+ end
360
379
  end
361
380
 
362
381
  describe '#list_remove_at' do
@@ -554,6 +573,80 @@ describe Cequel::Metal::DataSet do
554
573
  end
555
574
  end
556
575
 
576
+ describe '#consistency' do
577
+ let(:data_set) { cequel[:posts].consistency(:one) }
578
+
579
+ it 'should issue SELECT with scoped consistency' do
580
+ expect_query_with_consistency(/SELECT/, :one) { data_set.to_a }
581
+ end
582
+
583
+ it 'should issue COUNT with scoped consistency' do
584
+ expect_query_with_consistency(/SELECT.*COUNT/, :one) { data_set.count }
585
+ end
586
+
587
+ it 'should issue INSERT with scoped consistency' do
588
+ expect_query_with_consistency(/INSERT/, :one) do
589
+ data_set.insert(row_keys)
590
+ end
591
+ end
592
+
593
+ it 'should issue UPDATE with scoped consistency' do
594
+ expect_query_with_consistency(/UPDATE/, :one) do
595
+ data_set.where(row_keys).update(title: 'Marshmallows')
596
+ end
597
+ end
598
+
599
+ it 'should issue DELETE with scoped consistency' do
600
+ expect_query_with_consistency(/DELETE/, :one) do
601
+ data_set.where(row_keys).delete
602
+ end
603
+ end
604
+
605
+ it 'should issue DELETE column with scoped consistency' do
606
+ expect_query_with_consistency(/DELETE/, :one) do
607
+ data_set.where(row_keys).delete(:title)
608
+ end
609
+ end
610
+ end
611
+
612
+ describe 'default consistency' do
613
+ before(:all) { cequel.default_consistency = :all }
614
+ after(:all) { cequel.default_consistency = nil }
615
+ let(:data_set) { cequel[:posts] }
616
+
617
+ it 'should issue SELECT with default consistency' do
618
+ expect_query_with_consistency(/SELECT/, :all) { data_set.to_a }
619
+ end
620
+
621
+ it 'should issue COUNT with default consistency' do
622
+ expect_query_with_consistency(/SELECT.*COUNT/, :all) { data_set.count }
623
+ end
624
+
625
+ it 'should issue INSERT with default consistency' do
626
+ expect_query_with_consistency(/INSERT/, :all) do
627
+ data_set.insert(row_keys)
628
+ end
629
+ end
630
+
631
+ it 'should issue UPDATE with default consistency' do
632
+ expect_query_with_consistency(/UPDATE/, :all) do
633
+ data_set.where(row_keys).update(title: 'Marshmallows')
634
+ end
635
+ end
636
+
637
+ it 'should issue DELETE with default consistency' do
638
+ expect_query_with_consistency(/DELETE/, :all) do
639
+ data_set.where(row_keys).delete
640
+ end
641
+ end
642
+
643
+ it 'should issue DELETE column with default consistency' do
644
+ expect_query_with_consistency(/DELETE/, :all) do
645
+ data_set.where(row_keys).delete(:title)
646
+ end
647
+ end
648
+ end
649
+
557
650
  describe 'result enumeration' do
558
651
  let(:row) { row_keys.merge(:title => 'Big Data') }
559
652
 
@@ -0,0 +1,74 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require_relative '../spec_helper'
3
+
4
+ describe Cequel::Metal::Keyspace do
5
+ before :all do
6
+ cequel.schema.create_table(:posts) do
7
+ key :id, :int
8
+ column :title, :text
9
+ column :body, :text
10
+ end
11
+ end
12
+
13
+ after :each do
14
+ ids = cequel[:posts].select(:id).map { |row| row[:id] }
15
+ cequel[:posts].where(id: ids).delete if ids.any?
16
+ end
17
+
18
+ after :all do
19
+ cequel.schema.drop_table(:posts)
20
+ end
21
+
22
+ describe '::batch' do
23
+ it 'should send enclosed write statements in bulk' do
24
+ expect(cequel).to receive(:execute).once.and_call_original
25
+ cequel.batch do
26
+ cequel[:posts].insert(id: 1, title: 'Hey')
27
+ cequel[:posts].where(id: 1).update(body: 'Body')
28
+ cequel[:posts].where(id: 1).delete(:title)
29
+ end
30
+ RSpec::Mocks.proxy_for(cequel).reset
31
+ expect(cequel[:posts].first).to eq({id: 1, title: nil, body: 'Body'}
32
+ .with_indifferent_access)
33
+ end
34
+
35
+ it 'should auto-apply if option given' do
36
+ cequel.batch(auto_apply: 2) do
37
+ cequel[:posts].insert(id: 1, title: 'One')
38
+ expect(cequel[:posts].count).to be_zero
39
+ cequel[:posts].insert(id: 2, title: 'Two')
40
+ expect(cequel[:posts].count).to be(2)
41
+ end
42
+ end
43
+
44
+ it 'should do nothing if no statements executed in batch' do
45
+ expect { cequel.batch {} }.to_not raise_error
46
+ end
47
+
48
+ it 'should execute unlogged batch if specified' do
49
+ expect_query_with_consistency(/BEGIN UNLOGGED BATCH/, anything) do
50
+ cequel.batch(unlogged: true) do
51
+ cequel[:posts].insert(id: 1, title: 'One')
52
+ cequel[:posts].insert(id: 2, title: 'Two')
53
+ end
54
+ end
55
+ end
56
+
57
+ it 'should execute batch with given consistency' do
58
+ expect_query_with_consistency(/BEGIN BATCH/, :one) do
59
+ cequel.batch(consistency: :one) do
60
+ cequel[:posts].insert(id: 1, title: 'One')
61
+ cequel[:posts].insert(id: 2, title: 'Two')
62
+ end
63
+ end
64
+ end
65
+
66
+ it 'should raise error if consistency specified in individual query in batch' do
67
+ expect {
68
+ cequel.batch(consistency: :one) do
69
+ cequel[:posts].consistency(:quorum).insert(id: 1, title: 'One')
70
+ end
71
+ }.to raise_error(ArgumentError)
72
+ end
73
+ end
74
+ end
@@ -44,6 +44,12 @@ describe Cequel::Record::Associations do
44
44
  before_destroy { self.class.callback_count += 1 }
45
45
  end
46
46
 
47
+ model :Photo do
48
+ belongs_to :post, partition: true
49
+ key :id, :uuid, auto: true
50
+ column :caption, :text
51
+ end
52
+
47
53
  describe '::belongs_to' do
48
54
  let(:blog) { Blog.new { |blog| blog.subdomain = 'big-data' }}
49
55
  let(:post) { Post.new }
@@ -52,6 +58,18 @@ describe Cequel::Record::Associations do
52
58
  Post.key_column_names.first.should == :blog_subdomain
53
59
  end
54
60
 
61
+ it 'should add parent key as the partition key' do
62
+ Post.partition_key_column_names.should == [:blog_subdomain]
63
+ end
64
+
65
+ it "should add parent's keys as first keys" do
66
+ Comment.key_column_names.first(2).should == [:post_blog_subdomain, :post_id]
67
+ end
68
+
69
+ it "should add parent's first key as partition key" do
70
+ Comment.partition_key_column_names.should == [:post_blog_subdomain]
71
+ end
72
+
55
73
  it 'should provide accessors for association object' do
56
74
  post.blog = blog
57
75
  post.blog.should == blog
@@ -102,6 +120,72 @@ describe Cequel::Record::Associations do
102
120
  end.to raise_error(Cequel::Record::InvalidRecordConfiguration)
103
121
  end
104
122
 
123
+ context "with partition => true" do
124
+ let(:post) { Post.new { |post| post.blog_subdomain = 'big-data' }}
125
+ let(:photo) { Photo.new }
126
+
127
+ it "should add parent's keys as first keys" do
128
+ Photo.key_column_names.first(2).should == [:post_blog_subdomain, :post_id]
129
+ end
130
+
131
+ it "should add parent's keys as partition keys" do
132
+ Photo.partition_key_column_names.should == [:post_blog_subdomain, :post_id]
133
+ end
134
+
135
+ it 'should provide accessors for association object' do
136
+ photo.post = post
137
+ photo.post.should == post
138
+ end
139
+
140
+ it 'should set parent key(s) when setting association object' do
141
+ photo.post = post
142
+ photo.post_blog_subdomain.should == 'big-data'
143
+ photo.post_id.should == post.id
144
+ end
145
+
146
+ it 'should raise ArgumentError when parent is set without a key' do
147
+ post.blog_subdomain = nil
148
+ expect { photo.post = post }.to raise_error(ArgumentError)
149
+ end
150
+
151
+ it 'should raise ArgumentError when parent is set to wrong class' do
152
+ expect { photo.post = photo }.to raise_error(ArgumentError)
153
+ end
154
+
155
+ it 'should return Photo instance when parent keys are set directly' do
156
+ photo.post_blog_subdomain = 'big-data'
157
+ photo.post_id = post.id
158
+ photo.post.should == post
159
+ end
160
+
161
+ it 'should not hydrate parent instance when creating from keys' do
162
+ photo.post_blog_subdomain = 'big-data'
163
+ photo.post_id = post.id
164
+ disallow_queries!
165
+ photo.post.should_not be_loaded
166
+ end
167
+
168
+ it 'should not allow declaring belongs_to after key' do
169
+ expect do
170
+ Class.new do
171
+ include Cequel::Record
172
+ key :permalink, :text
173
+ belongs_to :post, partition: true
174
+ end
175
+ end.to raise_error(Cequel::Record::InvalidRecordConfiguration)
176
+ end
177
+
178
+ it 'should not allow declaring belongs_to more than once' do
179
+ expect do
180
+ Class.new do
181
+ include Cequel::Record
182
+ belongs_to :post, partition: true
183
+ belongs_to :user
184
+ end
185
+ end.to raise_error(Cequel::Record::InvalidRecordConfiguration)
186
+ end
187
+ end
188
+
105
189
  end
106
190
 
107
191
  describe '::has_many' do
@@ -48,6 +48,15 @@ describe Cequel::Record::Persistence do
48
48
  Blog.new.save
49
49
  }.to raise_error(Cequel::Record::MissingKeyError)
50
50
  end
51
+
52
+ it 'should save with specified consistency' do
53
+ expect_query_with_consistency(/INSERT/, :one) do
54
+ Blog.new do |blog|
55
+ blog.subdomain = 'cequel'
56
+ blog.name = 'Cequel'
57
+ end.save(consistency: :one)
58
+ end
59
+ end
51
60
  end
52
61
 
53
62
  context 'on update' do
@@ -78,6 +87,13 @@ describe Cequel::Record::Persistence do
78
87
  blog.save
79
88
  }.to raise_error(ArgumentError)
80
89
  end
90
+
91
+ it 'should save with specified consistency' do
92
+ expect_query_with_consistency(/UPDATE/, :one) do
93
+ blog.name = 'Cequel'
94
+ blog.save(consistency: :one)
95
+ end
96
+ end
81
97
  end
82
98
  end
83
99
 
@@ -161,6 +177,13 @@ describe Cequel::Record::Persistence do
161
177
  it 'should mark record transient' do
162
178
  blog.should be_transient
163
179
  end
180
+
181
+ it 'should destroy with specified consistency' do
182
+ blog = Blog.create(:subdomain => 'big-data', :name => 'Big Data')
183
+ expect_query_with_consistency(/DELETE/, :one) do
184
+ blog.destroy(consistency: :one)
185
+ end
186
+ end
164
187
  end
165
188
  end
166
189
 
@@ -147,7 +147,7 @@ describe Cequel::Record::Properties do
147
147
  end
148
148
 
149
149
  it 'should auto-generate UUID key' do
150
- Post.new.id.should be_a(CassandraCQL::UUID)
150
+ Cequel.uuid?(Post.new.id).should be_true
151
151
  end
152
152
 
153
153
  it 'should raise ArgumentError if auto specified for non-UUID' do
@@ -38,7 +38,7 @@ describe Cequel::Record::RecordSet do
38
38
  end
39
39
 
40
40
  let(:subdomains) { blogs.map(&:subdomain) }
41
- let(:uuids) { Array.new(2) { CassandraCQL::UUID.new }}
41
+ let(:uuids) { Array.new(2) { Cequel.uuid }}
42
42
  let(:now) { Time.at(Time.now.to_i) }
43
43
 
44
44
  let(:blogs) do
@@ -110,7 +110,7 @@ describe Cequel::Record::RecordSet do
110
110
  Comment.new(
111
111
  :blog_subdomain => 'cassandra',
112
112
  :permalink => 'cequel0',
113
- :id => CassandraCQL::UUID.new(Time.now - 5 + i),
113
+ :id => Cequel.uuid(Time.now - 5 + i),
114
114
  :body => "Comment #{i}"
115
115
  )
116
116
  end
@@ -353,10 +353,11 @@ describe Cequel::Record::RecordSet do
353
353
  let(:records) { [posts, blogs, mongo_posts] }
354
354
 
355
355
  it 'should respect :batch_size argument' do
356
- cequel.should_receive(:execute).twice.and_call_original
356
+ cequel.should_receive(:execute_with_consistency).twice.and_call_original
357
357
  Blog.find_each(:batch_size => 2).map(&:subdomain).
358
358
  should =~ subdomains
359
359
  end
360
+
360
361
  it 'should iterate over all keys' do
361
362
  Post.find_each(:batch_size => 2).map(&:title).should =~
362
363
  (0...5).flat_map { |i| ["Cequel #{i}", "Sequel #{i}", "Mongoid #{i}"] }
@@ -616,6 +617,14 @@ describe Cequel::Record::RecordSet do
616
617
  end
617
618
  end
618
619
 
620
+ describe '#consistency' do
621
+ it 'should perform query with specified consistency' do
622
+ expect_query_with_consistency(/SELECT/, :one) do
623
+ Post.consistency(:one).to_a
624
+ end
625
+ end
626
+ end
627
+
619
628
  describe '#count' do
620
629
  let(:records) { blogs }
621
630