cequel 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Appraisals +3 -0
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +5 -4
- data/Gemfile.lock +215 -22
- data/README.md +19 -6
- data/Vagrantfile +6 -1
- data/lib/cequel.rb +3 -2
- data/lib/cequel/metal/batch.rb +16 -1
- data/lib/cequel/metal/data_set.rb +63 -39
- data/lib/cequel/metal/deleter.rb +2 -2
- data/lib/cequel/metal/incrementer.rb +2 -2
- data/lib/cequel/metal/inserter.rb +8 -6
- data/lib/cequel/metal/keyspace.rb +127 -34
- data/lib/cequel/metal/logger.rb +1 -1
- data/lib/cequel/metal/row.rb +3 -4
- data/lib/cequel/metal/updater.rb +2 -2
- data/lib/cequel/metal/writer.rb +18 -12
- data/lib/cequel/record/associations.rb +7 -1
- data/lib/cequel/record/bound.rb +1 -1
- data/lib/cequel/record/callbacks.rb +10 -6
- data/lib/cequel/record/data_set_builder.rb +13 -2
- data/lib/cequel/record/persistence.rb +14 -12
- data/lib/cequel/record/properties.rb +3 -3
- data/lib/cequel/record/railtie.rb +1 -1
- data/lib/cequel/record/record_set.rb +12 -12
- data/lib/cequel/record/schema.rb +5 -1
- data/lib/cequel/schema/keyspace.rb +1 -1
- data/lib/cequel/schema/table_property.rb +1 -1
- data/lib/cequel/type.rb +44 -10
- data/lib/cequel/uuids.rb +46 -0
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/metal/data_set_spec.rb +93 -0
- data/spec/examples/metal/keyspace_spec.rb +74 -0
- data/spec/examples/record/associations_spec.rb +84 -0
- data/spec/examples/record/persistence_spec.rb +23 -0
- data/spec/examples/record/properties_spec.rb +1 -1
- data/spec/examples/record/record_set_spec.rb +12 -3
- data/spec/examples/record/schema_spec.rb +1 -1
- data/spec/examples/record/secondary_index_spec.rb +1 -1
- data/spec/examples/schema/table_reader_spec.rb +2 -3
- data/spec/examples/spec_helper.rb +8 -0
- data/spec/examples/type_spec.rb +7 -5
- data/spec/examples/uuids_spec.rb +22 -0
- data/spec/support/helpers.rb +25 -19
- data/templates/config/cequel.yml +5 -5
- metadata +40 -33
data/lib/cequel/uuids.rb
ADDED
@@ -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
|
data/lib/cequel/version.rb
CHANGED
@@ -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
|
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) {
|
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 =>
|
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(:
|
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
|
|