cequel 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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