cequel 0.4.2 → 0.5.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.
- data/lib/cequel.rb +1 -0
- data/lib/cequel/data_set.rb +23 -2
- data/lib/cequel/keyspace.rb +103 -20
- data/lib/cequel/model.rb +9 -4
- data/lib/cequel/model/class_internals.rb +1 -1
- data/lib/cequel/model/counter.rb +35 -0
- data/lib/cequel/model/dictionary.rb +31 -133
- data/lib/cequel/model/railtie.rb +1 -1
- data/lib/cequel/model/readable_dictionary.rb +169 -0
- data/lib/cequel/model/scoped.rb +16 -4
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/data_set_spec.rb +26 -0
- data/spec/examples/keyspace_spec.rb +1 -1
- data/spec/examples/model/counter_spec.rb +94 -0
- data/spec/examples/model/dictionary_spec.rb +2 -125
- data/spec/examples/spec_helper.rb +3 -0
- data/spec/models/comment_counts.rb +8 -0
- data/spec/shared/readable_dictionary.rb +174 -0
- metadata +26 -18
data/lib/cequel/model/railtie.rb
CHANGED
@@ -10,7 +10,7 @@ module Cequel
|
|
10
10
|
config_path = Rails.root.join('config/cequel.yml').to_s
|
11
11
|
|
12
12
|
if File.exist?(config_path)
|
13
|
-
yaml = YAML.
|
13
|
+
yaml = YAML::load(ERB.new(IO.read(config_path)).result)[Rails.env]
|
14
14
|
Cequel::Model.configure(yaml.symbolize_keys) if yaml
|
15
15
|
end
|
16
16
|
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
class ReadableDictionary
|
6
|
+
|
7
|
+
class <<self
|
8
|
+
|
9
|
+
attr_writer :column_family, :default_batch_size
|
10
|
+
|
11
|
+
def key_alias
|
12
|
+
@key_alias ||= :KEY
|
13
|
+
end
|
14
|
+
|
15
|
+
def key_type
|
16
|
+
@key_type ||= :text
|
17
|
+
end
|
18
|
+
|
19
|
+
def comparator
|
20
|
+
@comparator ||= :text
|
21
|
+
end
|
22
|
+
|
23
|
+
def validation
|
24
|
+
:counter
|
25
|
+
end
|
26
|
+
|
27
|
+
def key(key_alias, type)
|
28
|
+
@key_alias, @key_type = key_alias, type
|
29
|
+
|
30
|
+
module_eval(<<-RUBY)
|
31
|
+
def #{key_alias.downcase}
|
32
|
+
@key
|
33
|
+
end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
|
37
|
+
def columns(comparator)
|
38
|
+
@comparator = comparator
|
39
|
+
end
|
40
|
+
|
41
|
+
def column_family
|
42
|
+
return @column_family if @column_family
|
43
|
+
self.column_family_name = name.underscore.to_sym
|
44
|
+
@column_family
|
45
|
+
end
|
46
|
+
|
47
|
+
def column_family_name=(column_family_name)
|
48
|
+
self.column_family = Cequel::Model.keyspace[column_family_name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_batch_size
|
52
|
+
@default_batch_size || 1000
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](key)
|
56
|
+
new(key)
|
57
|
+
end
|
58
|
+
private :new
|
59
|
+
|
60
|
+
def load(*keys)
|
61
|
+
options = keys.extract_options!
|
62
|
+
keys.flatten!
|
63
|
+
batch_size = options[:columns] || options[:batch_size] ||
|
64
|
+
default_batch_size
|
65
|
+
column_family.select(:first => batch_size).
|
66
|
+
where(key_alias.to_s => keys).
|
67
|
+
map { |row| new(row.delete(key_alias.to_s), row) }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
include Enumerable
|
73
|
+
|
74
|
+
def initialize(key, row = nil)
|
75
|
+
@key = key
|
76
|
+
setup(row)
|
77
|
+
end
|
78
|
+
|
79
|
+
def [](column)
|
80
|
+
if @loaded
|
81
|
+
@row[column]
|
82
|
+
else
|
83
|
+
scope.select(column).first[column]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def keys
|
88
|
+
@loaded ? @row.keys : each_pair.map { |key, value| key }
|
89
|
+
end
|
90
|
+
|
91
|
+
def values
|
92
|
+
@loaded ? @row.values : each_pair.map { |key, value| value }
|
93
|
+
end
|
94
|
+
|
95
|
+
def slice(*columns)
|
96
|
+
if @loaded
|
97
|
+
@row.slice(*columns)
|
98
|
+
else
|
99
|
+
deserialize_row(load_raw_slice(columns))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def key?(column)
|
104
|
+
@row.key?(column) || load_raw_slice([column])[column].present?
|
105
|
+
end
|
106
|
+
|
107
|
+
def each_pair(options = {}, &block)
|
108
|
+
return Enumerator.new(self, :each_pair, options) unless block
|
109
|
+
return @row.each_pair(&block) if @loaded
|
110
|
+
batch_size = options[:batch_size] || self.class.default_batch_size
|
111
|
+
each_slice(batch_size) do |batch_results|
|
112
|
+
batch_results.each_pair(&block)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def each(&block)
|
117
|
+
each_pair(&block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def each_slice(batch_size)
|
121
|
+
batch_scope = scope.select(:first => batch_size)
|
122
|
+
key_alias = self.class.key_alias
|
123
|
+
last_key = nil
|
124
|
+
begin
|
125
|
+
batch_results = batch_scope.first
|
126
|
+
batch_results.delete(key_alias)
|
127
|
+
result_length = batch_results.length
|
128
|
+
batch_results.delete(last_key) unless last_key.nil?
|
129
|
+
yield deserialize_row(batch_results)
|
130
|
+
last_key = batch_results.keys.last
|
131
|
+
batch_scope = batch_scope.select(:from => last_key)
|
132
|
+
end while result_length == batch_size
|
133
|
+
end
|
134
|
+
|
135
|
+
def load
|
136
|
+
@row = {}
|
137
|
+
each_pair { |column, value| @row[column] = value }
|
138
|
+
@loaded = true
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def loaded?
|
143
|
+
!!@loaded
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def setup(init_row = nil)
|
149
|
+
@row = deserialize_row(init_row || {})
|
150
|
+
@loaded = !!init_row
|
151
|
+
end
|
152
|
+
|
153
|
+
def scope
|
154
|
+
self.class.column_family.where(self.class.key_alias => @key)
|
155
|
+
end
|
156
|
+
|
157
|
+
def load_raw_slice(columns)
|
158
|
+
row = scope.select(*columns).first.except(self.class.key_alias)
|
159
|
+
end
|
160
|
+
|
161
|
+
def deserialize_row(row)
|
162
|
+
row
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
data/lib/cequel/model/scoped.rb
CHANGED
@@ -16,7 +16,7 @@ module Cequel
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def all
|
19
|
-
|
19
|
+
current_scope || @_cequel.default_scope || empty_scope
|
20
20
|
end
|
21
21
|
|
22
22
|
def select(*rows)
|
@@ -25,12 +25,12 @@ module Cequel
|
|
25
25
|
|
26
26
|
def with_scope(scope)
|
27
27
|
@_cequel.synchronize do
|
28
|
-
old_scope =
|
28
|
+
old_scope = current_scope
|
29
29
|
begin
|
30
|
-
|
30
|
+
self.current_scope = scope
|
31
31
|
yield
|
32
32
|
ensure
|
33
|
-
|
33
|
+
self.current_scope = old_scope
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -41,6 +41,18 @@ module Cequel
|
|
41
41
|
Scope.new(self, [column_family])
|
42
42
|
end
|
43
43
|
|
44
|
+
def current_scope
|
45
|
+
::Thread.current[current_scope_key]
|
46
|
+
end
|
47
|
+
|
48
|
+
def current_scope=(scope)
|
49
|
+
::Thread.current[current_scope_key] = scope
|
50
|
+
end
|
51
|
+
|
52
|
+
def current_scope_key
|
53
|
+
:"cequel-current_scope-#{object_id}"
|
54
|
+
end
|
55
|
+
|
44
56
|
end
|
45
57
|
|
46
58
|
end
|
data/lib/cequel/version.rb
CHANGED
@@ -93,6 +93,32 @@ describe Cequel::DataSet do
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
describe '#increment' do
|
97
|
+
it 'should increment counter columns' do
|
98
|
+
connection.should_receive(:execute).with(
|
99
|
+
'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
|
100
|
+
'somepost', 'somepost', 1,
|
101
|
+
'anotherpost', 'anotherpost', 2,
|
102
|
+
'blog_id', 'myblog'
|
103
|
+
)
|
104
|
+
cequel[:comment_counts].where('blog_id' => 'myblog').
|
105
|
+
increment('somepost' => 1, 'anotherpost' => 2)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#decrement' do
|
110
|
+
it 'should decrement counter columns' do
|
111
|
+
connection.should_receive(:execute).with(
|
112
|
+
'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
|
113
|
+
'somepost', 'somepost', 1,
|
114
|
+
'anotherpost', 'anotherpost', 2,
|
115
|
+
'blog_id', 'myblog'
|
116
|
+
)
|
117
|
+
cequel[:comment_counts].where('blog_id' => 'myblog').
|
118
|
+
decrement('somepost' => 1, 'anotherpost' => 2)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
96
122
|
describe '#delete' do
|
97
123
|
it 'should send basic delete statement' do
|
98
124
|
connection.should_receive(:execute).
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Counter do
|
4
|
+
let(:counter) { CommentCounts[1] }
|
5
|
+
let(:dictionary) { counter }
|
6
|
+
let(:uuid1) { CassandraCQL::UUID.new }
|
7
|
+
let(:uuid2) { CassandraCQL::UUID.new }
|
8
|
+
|
9
|
+
it_behaves_like 'readable dictionary'
|
10
|
+
|
11
|
+
describe '#increment' do
|
12
|
+
it 'should increment single column by default 1' do
|
13
|
+
connection.should_receive(:execute).with(
|
14
|
+
'UPDATE comment_counts SET ? = ? + ? WHERE ? = ?',
|
15
|
+
uuid1, uuid1, 1, :blog_id, 1
|
16
|
+
)
|
17
|
+
dictionary.increment(uuid1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should increment single column by given value' do
|
21
|
+
connection.should_receive(:execute).with(
|
22
|
+
'UPDATE comment_counts SET ? = ? + ? WHERE ? = ?',
|
23
|
+
uuid1, uuid1, 4, :blog_id, 1
|
24
|
+
)
|
25
|
+
dictionary.increment(uuid1, 4)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should increment multiple columns by default value' do
|
29
|
+
connection.should_receive(:execute).with(
|
30
|
+
'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
|
31
|
+
uuid1, uuid1, 1, uuid2, uuid2, 1, :blog_id, 1
|
32
|
+
)
|
33
|
+
dictionary.increment([uuid1, uuid2])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should increment multiple columns by given value' do
|
37
|
+
connection.should_receive(:execute).with(
|
38
|
+
'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
|
39
|
+
uuid1, uuid1, 4, uuid2, uuid2, 4, :blog_id, 1
|
40
|
+
)
|
41
|
+
dictionary.increment([uuid1, uuid2], 4)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should increment multiple columns by given deltas' do
|
45
|
+
connection.should_receive(:execute).with(
|
46
|
+
'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
|
47
|
+
uuid1, uuid1, 4, uuid2, uuid2, 2, :blog_id, 1
|
48
|
+
)
|
49
|
+
dictionary.increment(uuid1 => 4, uuid2 => 2)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#decrement' do
|
54
|
+
it 'should increment single column by default 1' do
|
55
|
+
connection.should_receive(:execute).with(
|
56
|
+
'UPDATE comment_counts SET ? = ? - ? WHERE ? = ?',
|
57
|
+
uuid1, uuid1, 1, :blog_id, 1
|
58
|
+
)
|
59
|
+
dictionary.decrement(uuid1)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should increment single column by given value' do
|
63
|
+
connection.should_receive(:execute).with(
|
64
|
+
'UPDATE comment_counts SET ? = ? - ? WHERE ? = ?',
|
65
|
+
uuid1, uuid1, 4, :blog_id, 1
|
66
|
+
)
|
67
|
+
dictionary.decrement(uuid1, 4)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should increment multiple columns by default value' do
|
71
|
+
connection.should_receive(:execute).with(
|
72
|
+
'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
|
73
|
+
uuid1, uuid1, 1, uuid2, uuid2, 1, :blog_id, 1
|
74
|
+
)
|
75
|
+
dictionary.decrement([uuid1, uuid2])
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should increment multiple columns by given value' do
|
79
|
+
connection.should_receive(:execute).with(
|
80
|
+
'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
|
81
|
+
uuid1, uuid1, 4, uuid2, uuid2, 4, :blog_id, 1
|
82
|
+
)
|
83
|
+
dictionary.decrement([uuid1, uuid2], 4)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should increment multiple columns by given deltas' do
|
87
|
+
connection.should_receive(:execute).with(
|
88
|
+
'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
|
89
|
+
uuid1, uuid1, 4, uuid2, uuid2, 2, :blog_id, 1
|
90
|
+
)
|
91
|
+
dictionary.decrement(uuid1 => 4, uuid2 => 2)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -6,6 +6,8 @@ describe Cequel::Model::Dictionary do
|
|
6
6
|
let(:uuid3) { uuid }
|
7
7
|
let(:dictionary) { BlogPosts[1] }
|
8
8
|
|
9
|
+
it_behaves_like 'readable dictionary'
|
10
|
+
|
9
11
|
describe '#save' do
|
10
12
|
before do
|
11
13
|
connection.stub(:execute).
|
@@ -125,131 +127,6 @@ describe Cequel::Model::Dictionary do
|
|
125
127
|
end
|
126
128
|
end
|
127
129
|
|
128
|
-
context 'without row in memory' do
|
129
|
-
|
130
|
-
describe '#each_pair' do
|
131
|
-
|
132
|
-
it 'should load columns in batches and yield them' do
|
133
|
-
connection.should_receive(:execute).
|
134
|
-
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
135
|
-
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
136
|
-
connection.should_receive(:execute).
|
137
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
138
|
-
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
139
|
-
connection.should_receive(:execute).
|
140
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
141
|
-
and_return result_stub({'blog_id' => 1})
|
142
|
-
hash = {}
|
143
|
-
dictionary.each_pair do |key, value|
|
144
|
-
hash[key] = value
|
145
|
-
end
|
146
|
-
hash.should == {uuid1 => 1, uuid2 => 2, uuid3 => 3}
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
describe '#[]' do
|
152
|
-
|
153
|
-
it 'should load column from cassandra' do
|
154
|
-
connection.stub(:execute).
|
155
|
-
with('SELECT ? FROM blog_posts WHERE ? = ? LIMIT 1', [uuid1], :blog_id, 1).
|
156
|
-
and_return result_stub(uuid1 => 1)
|
157
|
-
dictionary[uuid1].should == 1
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
describe '#slice' do
|
163
|
-
it 'should load columns from data store' do
|
164
|
-
connection.stub(:execute).
|
165
|
-
with('SELECT ? FROM blog_posts WHERE ? = ? LIMIT 1', [uuid1,uuid2], :blog_id, 1).
|
166
|
-
and_return result_stub(uuid1 => 1, uuid2 => 2)
|
167
|
-
dictionary.slice(uuid1, uuid2).should == {uuid1 => 1, uuid2 => 2}
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
describe '#keys' do
|
172
|
-
it 'should load keys from data store' do
|
173
|
-
connection.should_receive(:execute).
|
174
|
-
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
175
|
-
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
176
|
-
connection.should_receive(:execute).
|
177
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
178
|
-
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
179
|
-
connection.should_receive(:execute).
|
180
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
181
|
-
and_return result_stub({'blog_id' => 1})
|
182
|
-
dictionary.keys.should == [uuid1, uuid2, uuid3]
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
describe '#values' do
|
187
|
-
it 'should load values from data store' do
|
188
|
-
connection.should_receive(:execute).
|
189
|
-
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
190
|
-
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
191
|
-
connection.should_receive(:execute).
|
192
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
193
|
-
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
194
|
-
connection.should_receive(:execute).
|
195
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
196
|
-
and_return result_stub({'blog_id' => 1})
|
197
|
-
dictionary.values.should == [1, 2, 3]
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
end
|
202
|
-
|
203
|
-
context 'with data loaded in memory' do
|
204
|
-
before do
|
205
|
-
connection.stub(:execute).
|
206
|
-
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
207
|
-
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
208
|
-
connection.stub(:execute).
|
209
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
210
|
-
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
211
|
-
connection.stub(:execute).
|
212
|
-
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
213
|
-
and_return result_stub({'blog_id' => 1})
|
214
|
-
dictionary.load
|
215
|
-
connection.should_not_receive(:execute)
|
216
|
-
end
|
217
|
-
|
218
|
-
describe '#each_pair' do
|
219
|
-
it 'should yield data from memory' do
|
220
|
-
hash = {}
|
221
|
-
dictionary.each_pair do |key, value|
|
222
|
-
hash[key] = value
|
223
|
-
end
|
224
|
-
hash.should == {uuid1 => 1, uuid2 => 2, uuid3 => 3}
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
describe '#[]' do
|
229
|
-
it 'should return value from memory' do
|
230
|
-
dictionary[uuid1].should == 1
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
describe '#slice' do
|
235
|
-
it 'should return slice of data in memory' do
|
236
|
-
dictionary.slice(uuid1, uuid2).should == {uuid1 => 1, uuid2 => 2}
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
describe '#keys' do
|
241
|
-
it 'should return keys from memory' do
|
242
|
-
dictionary.keys.should == [uuid1, uuid2, uuid3]
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
describe '#values' do
|
247
|
-
it 'should return values from memory' do
|
248
|
-
dictionary.values.should == [1, 2, 3]
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
130
|
context 'with data modified but not loaded in memory' do
|
254
131
|
let(:uuid4) { uuid }
|
255
132
|
|