cequel 0.5.6 → 1.0.0.pre.1
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.
- checksums.yaml +7 -0
- data/lib/cequel.rb +5 -8
- data/lib/cequel/errors.rb +1 -0
- data/lib/cequel/metal.rb +17 -0
- data/lib/cequel/metal/batch.rb +62 -0
- data/lib/cequel/metal/cql_row_specification.rb +26 -0
- data/lib/cequel/metal/data_set.rb +461 -0
- data/lib/cequel/metal/deleter.rb +47 -0
- data/lib/cequel/metal/incrementer.rb +35 -0
- data/lib/cequel/metal/inserter.rb +53 -0
- data/lib/cequel/metal/keyspace.rb +213 -0
- data/lib/cequel/metal/row.rb +48 -0
- data/lib/cequel/metal/row_specification.rb +37 -0
- data/lib/cequel/metal/statement.rb +30 -0
- data/lib/cequel/metal/updater.rb +65 -0
- data/lib/cequel/metal/writer.rb +73 -0
- data/lib/cequel/model.rb +12 -84
- data/lib/cequel/model/association_collection.rb +23 -0
- data/lib/cequel/model/associations.rb +84 -80
- data/lib/cequel/model/base.rb +74 -0
- data/lib/cequel/model/belongs_to_association.rb +31 -0
- data/lib/cequel/model/callbacks.rb +14 -10
- data/lib/cequel/model/collection.rb +255 -0
- data/lib/cequel/model/errors.rb +6 -6
- data/lib/cequel/model/has_many_association.rb +26 -0
- data/lib/cequel/model/mass_assignment.rb +31 -0
- data/lib/cequel/model/persistence.rb +119 -115
- data/lib/cequel/model/properties.rb +89 -87
- data/lib/cequel/model/railtie.rb +21 -14
- data/lib/cequel/model/record_set.rb +285 -0
- data/lib/cequel/model/schema.rb +33 -0
- data/lib/cequel/model/scoped.rb +5 -48
- data/lib/cequel/model/validations.rb +18 -18
- data/lib/cequel/schema.rb +15 -0
- data/lib/cequel/schema/column.rb +135 -0
- data/lib/cequel/schema/create_table_dsl.rb +56 -0
- data/lib/cequel/schema/keyspace.rb +50 -0
- data/lib/cequel/schema/table.rb +120 -0
- data/lib/cequel/schema/table_property.rb +67 -0
- data/lib/cequel/schema/table_reader.rb +139 -0
- data/lib/cequel/schema/table_synchronizer.rb +114 -0
- data/lib/cequel/schema/table_updater.rb +83 -0
- data/lib/cequel/schema/table_writer.rb +80 -0
- data/lib/cequel/schema/update_table_dsl.rb +60 -0
- data/lib/cequel/type.rb +232 -0
- data/lib/cequel/version.rb +1 -1
- data/spec/environment.rb +5 -1
- data/spec/examples/metal/data_set_spec.rb +608 -0
- data/spec/examples/model/associations_spec.rb +84 -74
- data/spec/examples/model/callbacks_spec.rb +66 -59
- data/spec/examples/model/list_spec.rb +393 -0
- data/spec/examples/model/map_spec.rb +229 -0
- data/spec/examples/model/mass_assignment_spec.rb +55 -0
- data/spec/examples/model/naming_spec.rb +11 -4
- data/spec/examples/model/persistence_spec.rb +140 -150
- data/spec/examples/model/properties_spec.rb +122 -75
- data/spec/examples/model/record_set_spec.rb +285 -0
- data/spec/examples/model/schema_spec.rb +44 -0
- data/spec/examples/model/serialization_spec.rb +20 -14
- data/spec/examples/model/set_spec.rb +133 -0
- data/spec/examples/model/spec_helper.rb +0 -10
- data/spec/examples/model/validations_spec.rb +51 -38
- data/spec/examples/schema/table_reader_spec.rb +328 -0
- data/spec/examples/schema/table_synchronizer_spec.rb +172 -0
- data/spec/examples/schema/table_updater_spec.rb +157 -0
- data/spec/examples/schema/table_writer_spec.rb +225 -0
- data/spec/examples/spec_helper.rb +29 -0
- data/spec/examples/type_spec.rb +204 -0
- data/spec/support/helpers.rb +67 -8
- metadata +121 -152
- data/lib/cequel/batch.rb +0 -58
- data/lib/cequel/cql_row_specification.rb +0 -22
- data/lib/cequel/data_set.rb +0 -371
- data/lib/cequel/keyspace.rb +0 -205
- data/lib/cequel/model/class_internals.rb +0 -49
- data/lib/cequel/model/column.rb +0 -20
- data/lib/cequel/model/counter.rb +0 -35
- data/lib/cequel/model/dictionary.rb +0 -126
- data/lib/cequel/model/dirty.rb +0 -53
- data/lib/cequel/model/dynamic.rb +0 -31
- data/lib/cequel/model/inheritable.rb +0 -48
- data/lib/cequel/model/instance_internals.rb +0 -23
- data/lib/cequel/model/local_association.rb +0 -42
- data/lib/cequel/model/magic.rb +0 -79
- data/lib/cequel/model/mass_assignment_security.rb +0 -21
- data/lib/cequel/model/naming.rb +0 -17
- data/lib/cequel/model/observer.rb +0 -42
- data/lib/cequel/model/readable_dictionary.rb +0 -182
- data/lib/cequel/model/remote_association.rb +0 -40
- data/lib/cequel/model/scope.rb +0 -362
- data/lib/cequel/model/subclass_internals.rb +0 -45
- data/lib/cequel/model/timestamps.rb +0 -52
- data/lib/cequel/model/translation.rb +0 -17
- data/lib/cequel/row_specification.rb +0 -63
- data/lib/cequel/statement.rb +0 -23
- data/spec/examples/data_set_spec.rb +0 -444
- data/spec/examples/keyspace_spec.rb +0 -84
- data/spec/examples/model/counter_spec.rb +0 -94
- data/spec/examples/model/dictionary_spec.rb +0 -301
- data/spec/examples/model/dirty_spec.rb +0 -39
- data/spec/examples/model/dynamic_spec.rb +0 -41
- data/spec/examples/model/inheritable_spec.rb +0 -45
- data/spec/examples/model/magic_spec.rb +0 -199
- data/spec/examples/model/mass_assignment_security_spec.rb +0 -13
- data/spec/examples/model/observer_spec.rb +0 -86
- data/spec/examples/model/scope_spec.rb +0 -677
- data/spec/examples/model/timestamps_spec.rb +0 -52
- data/spec/examples/model/translation_spec.rb +0 -23
data/lib/cequel/type.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Cequel
|
4
|
+
|
5
|
+
module Type
|
6
|
+
|
7
|
+
UnknownType = Class.new(ArgumentError)
|
8
|
+
|
9
|
+
BY_CQL_NAME = {}
|
10
|
+
BY_INTERNAL_NAME = {}
|
11
|
+
|
12
|
+
def self.register(type)
|
13
|
+
BY_CQL_NAME[type.cql_name] = type
|
14
|
+
type.cql_aliases.each { |aliaz| BY_CQL_NAME[aliaz] = type }
|
15
|
+
BY_INTERNAL_NAME[type.internal_name] = type
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.[](cql_name)
|
19
|
+
cql_name.is_a?(Base) ? cql_name : lookup_cql(cql_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.lookup_cql(cql_name)
|
23
|
+
BY_CQL_NAME.fetch(cql_name.to_sym)
|
24
|
+
rescue KeyError
|
25
|
+
raise UnknownType, "Unrecognized CQL type #{cql_name.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.lookup_internal(internal_name)
|
29
|
+
BY_INTERNAL_NAME.fetch(internal_name)
|
30
|
+
rescue KeyError
|
31
|
+
raise UnknownType, "Unrecognized internal type #{internal_name.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
class Base
|
35
|
+
include Singleton
|
36
|
+
|
37
|
+
def cql_name
|
38
|
+
self.class.name.demodulize.underscore.to_sym
|
39
|
+
end
|
40
|
+
|
41
|
+
def cql_aliases
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
|
45
|
+
def internal_name
|
46
|
+
"org.apache.cassandra.db.marshal.#{self.class.name.demodulize}Type"
|
47
|
+
end
|
48
|
+
|
49
|
+
def cast(value)
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
cql_name.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
class String < Base
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def ensure_encoding(value, encoding)
|
64
|
+
str = value.to_s
|
65
|
+
str.encoding.name == encoding ? str : str.dup.force_encoding(encoding)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class Ascii < String
|
71
|
+
def cast(value)
|
72
|
+
ensure_encoding(value, 'US-ASCII')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
register Ascii.instance
|
76
|
+
|
77
|
+
class Blob < String
|
78
|
+
|
79
|
+
def internal_name
|
80
|
+
'org.apache.cassandra.db.marshal.BytesType'
|
81
|
+
end
|
82
|
+
|
83
|
+
def cast(value)
|
84
|
+
case value
|
85
|
+
when Integer then ensure_encoding(value.to_s(16), 'ASCII-8BIT')
|
86
|
+
else ensure_encoding(value.to_s, 'ASCII-8BIT')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
register Blob.instance
|
92
|
+
|
93
|
+
class Boolean < Base
|
94
|
+
def cast(value)
|
95
|
+
!!value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
register Boolean.instance
|
99
|
+
|
100
|
+
class Counter < Base
|
101
|
+
|
102
|
+
def internal_name
|
103
|
+
'org.apache.cassandra.db.marshal.CounterColumnType'
|
104
|
+
end
|
105
|
+
|
106
|
+
def cast(value)
|
107
|
+
value.to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
register Counter.instance
|
112
|
+
|
113
|
+
class Decimal < Base
|
114
|
+
def cast(value)
|
115
|
+
BigDecimal.new(value, 0)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
register Decimal.instance
|
119
|
+
|
120
|
+
class Double < Base
|
121
|
+
def cast(value)
|
122
|
+
value.to_f
|
123
|
+
end
|
124
|
+
end
|
125
|
+
register Double.instance
|
126
|
+
|
127
|
+
class Inet < Base
|
128
|
+
|
129
|
+
def internal_name
|
130
|
+
'org.apache.cassandra.db.marshal.InetAddressType'
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
register Inet.instance
|
135
|
+
|
136
|
+
class Int < Base
|
137
|
+
|
138
|
+
def internal_name
|
139
|
+
'org.apache.cassandra.db.marshal.Int32Type'
|
140
|
+
end
|
141
|
+
|
142
|
+
def cast(value)
|
143
|
+
value.to_i
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
register Int.instance
|
148
|
+
|
149
|
+
class Float < Double; end
|
150
|
+
register Float.instance
|
151
|
+
|
152
|
+
class Long < Int
|
153
|
+
|
154
|
+
def internal_name
|
155
|
+
'org.apache.cassandra.db.marshal.LongType'
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
register Long.instance
|
160
|
+
|
161
|
+
class Text < String
|
162
|
+
|
163
|
+
def internal_name
|
164
|
+
'org.apache.cassandra.db.marshal.UTF8Type'
|
165
|
+
end
|
166
|
+
|
167
|
+
def cql_aliases
|
168
|
+
[:varchar]
|
169
|
+
end
|
170
|
+
|
171
|
+
def cast(value)
|
172
|
+
ensure_encoding(value, 'UTF-8')
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
register Text.instance
|
177
|
+
|
178
|
+
class Timestamp < Base
|
179
|
+
|
180
|
+
def internal_name
|
181
|
+
'org.apache.cassandra.db.marshal.DateType'
|
182
|
+
end
|
183
|
+
|
184
|
+
def cast(value)
|
185
|
+
if ::String === value then Time.parse(value)
|
186
|
+
elsif value.respond_to?(:to_time) then value.to_time
|
187
|
+
elsif Numeric === value then Time.at(value)
|
188
|
+
else Time.parse(value.to_s)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
register Timestamp.instance
|
194
|
+
|
195
|
+
class Uuid < Base
|
196
|
+
|
197
|
+
def internal_name
|
198
|
+
'org.apache.cassandra.db.marshal.UUIDType'
|
199
|
+
end
|
200
|
+
|
201
|
+
def cast(value)
|
202
|
+
case value
|
203
|
+
when CassandraCQL::UUID then value
|
204
|
+
when SimpleUUID::UUID then CassandraCQL::UUID.new(value.to_s)
|
205
|
+
else CassandraCQL::UUID.new(value)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
register Uuid.instance
|
211
|
+
|
212
|
+
class Timeuuid < Uuid
|
213
|
+
|
214
|
+
def internal_name
|
215
|
+
'org.apache.cassandra.db.marshal.TimeUUIDType'
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
register Timeuuid.instance
|
220
|
+
|
221
|
+
class Varint < Int
|
222
|
+
|
223
|
+
def internal_name
|
224
|
+
'org.apache.cassandra.db.marshal.IntegerType'
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
register Varint.instance
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
data/lib/cequel/version.rb
CHANGED
data/spec/environment.rb
CHANGED
@@ -0,0 +1,608 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Metal::DataSet do
|
4
|
+
before :all do
|
5
|
+
cequel.schema.create_table(:posts) do
|
6
|
+
key :blog_subdomain, :text
|
7
|
+
key :permalink, :text
|
8
|
+
column :title, :text
|
9
|
+
column :body, :text
|
10
|
+
column :published_at, :timestamp
|
11
|
+
list :categories, :text
|
12
|
+
set :tags, :text
|
13
|
+
map :trackbacks, :timestamp, :text
|
14
|
+
end
|
15
|
+
cequel.schema.create_table :post_activity do
|
16
|
+
key :blog_subdomain, :text
|
17
|
+
key :permalink, :text
|
18
|
+
column :visits, :counter
|
19
|
+
column :tweets, :counter
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
after :each do
|
24
|
+
subdomains = cequel[:posts].select(:blog_subdomain).
|
25
|
+
map { |row| row[:blog_subdomain] }
|
26
|
+
cequel[:posts].where(blog_subdomain: subdomains).delete if subdomains.any?
|
27
|
+
end
|
28
|
+
|
29
|
+
after :all do
|
30
|
+
cequel.schema.drop_table(:posts)
|
31
|
+
cequel.schema.drop_table(:post_activity)
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:row_keys) { {blog_subdomain: 'cassandra', permalink: 'big-data'} }
|
35
|
+
|
36
|
+
describe '#insert' do
|
37
|
+
let(:row) do
|
38
|
+
row_keys.merge(
|
39
|
+
title: 'Fun times',
|
40
|
+
categories: ['Fun', 'Profit'],
|
41
|
+
tags: Set['cassandra', 'big-data'],
|
42
|
+
trackbacks: {
|
43
|
+
Time.at(Time.now.to_i) => 'www.google.com',
|
44
|
+
Time.at(Time.now.to_i - 60) => 'www.yahoo.com'
|
45
|
+
}
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should insert a row' do
|
50
|
+
cequel[:posts].insert(row)
|
51
|
+
cequel[:posts].where(row_keys).first[:title].should == 'Fun times'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should correctly insert a list' do
|
55
|
+
cequel[:posts].insert(row)
|
56
|
+
cequel[:posts].where(row_keys).first[:categories].
|
57
|
+
should == ['Fun', 'Profit']
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should correctly insert a set' do
|
61
|
+
cequel[:posts].insert(row)
|
62
|
+
cequel[:posts].where(row_keys).first[:tags].
|
63
|
+
should == Set['cassandra', 'big-data']
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should correctly insert a map' do
|
67
|
+
cequel[:posts].insert(row)
|
68
|
+
cequel[:posts].where(row_keys).first[:trackbacks].
|
69
|
+
should == row[:trackbacks]
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should include ttl argument' do
|
73
|
+
cequel[:posts].insert(row, :ttl => 10.minutes)
|
74
|
+
cequel[:posts].select_ttl(:title).where(row_keys).first.ttl(:title).
|
75
|
+
should be_within(5).of(10.minutes)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should include timestamp argument' do
|
79
|
+
cequel.schema.truncate_table(:posts)
|
80
|
+
time = 1.day.ago
|
81
|
+
cequel[:posts].insert(row, :timestamp => time)
|
82
|
+
cequel[:posts].select_writetime(:title).where(row_keys).
|
83
|
+
first.writetime(:title).should == (time.to_f * 1_000_000).to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should include multiple arguments joined by AND' do
|
87
|
+
cequel.schema.truncate_table(:posts)
|
88
|
+
time = 1.day.ago
|
89
|
+
cequel[:posts].insert(row, :ttl => 600, :timestamp => time)
|
90
|
+
result = cequel[:posts].select_ttl(:title).select_writetime(:title).
|
91
|
+
where(row_keys).first
|
92
|
+
result.writetime(:title).should == (time.to_f * 1_000_000).to_i
|
93
|
+
result.ttl(:title).should be_within(5).of(10.minutes)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#update' do
|
98
|
+
it 'should send basic update statement' do
|
99
|
+
cequel[:posts].where(row_keys).
|
100
|
+
update(:title => 'Fun times', :body => 'Fun')
|
101
|
+
cequel[:posts].where(row_keys).
|
102
|
+
first[:title].should == 'Fun times'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should send update statement with options' do
|
106
|
+
cequel.schema.truncate_table(:posts)
|
107
|
+
time = Time.now - 10.minutes
|
108
|
+
|
109
|
+
cequel[:posts].where(row_keys).
|
110
|
+
update({title: 'Fun times', body: 'Fun'}, ttl: 600, timestamp: time)
|
111
|
+
|
112
|
+
row = cequel[:posts].
|
113
|
+
select_ttl(:title).select_writetime(:title).
|
114
|
+
where(row_keys).first
|
115
|
+
|
116
|
+
row.ttl(:title).should be_within(5).of(10.minutes)
|
117
|
+
row.writetime(:title).should == (time.to_f * 1_000_000).to_i
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should overwrite list column' do
|
121
|
+
cequel[:posts].where(row_keys).
|
122
|
+
update(categories: ['Big Data', 'Cassandra'])
|
123
|
+
cequel[:posts].where(row_keys).first[:categories].
|
124
|
+
should == ['Big Data', 'Cassandra']
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should overwrite set column' do
|
128
|
+
cequel[:posts].where(row_keys).update(tags: Set['big-data', 'nosql'])
|
129
|
+
cequel[:posts].where(row_keys).first[:tags].
|
130
|
+
should == Set['big-data', 'nosql']
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should overwrite map column' do
|
134
|
+
time1 = Time.at(Time.now.to_i)
|
135
|
+
time2 = Time.at(10.minutes.ago.to_i)
|
136
|
+
cequel[:posts].where(row_keys).update(
|
137
|
+
trackbacks: {time1 => 'foo', time2 => 'bar'})
|
138
|
+
cequel[:posts].where(row_keys).first[:trackbacks].
|
139
|
+
should == {time1 => 'foo', time2 => 'bar'}
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should perform various types of update in one go' do
|
143
|
+
cequel[:posts].insert(
|
144
|
+
row_keys.merge(title: 'Big Data',
|
145
|
+
body: 'Cassandra',
|
146
|
+
categories: ['Scalability']))
|
147
|
+
cequel[:posts].where(row_keys).update do
|
148
|
+
set(title: 'Bigger Data')
|
149
|
+
list_append(:categories, 'Fault-Tolerance')
|
150
|
+
end
|
151
|
+
cequel[:posts].where(row_keys).first[:title].should == 'Bigger Data'
|
152
|
+
cequel[:posts].where(row_keys).first[:categories].
|
153
|
+
should == %w(Scalability Fault-Tolerance)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#list_prepend' do
|
158
|
+
it 'should prepend a single element to list column' do
|
159
|
+
cequel[:posts].insert(
|
160
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra']))
|
161
|
+
cequel[:posts].where(row_keys).
|
162
|
+
list_prepend(:categories, 'Scalability')
|
163
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
164
|
+
['Scalability', 'Big Data', 'Cassandra']
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should prepend multiple elements to list column' do
|
168
|
+
cequel[:posts].insert(
|
169
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra']))
|
170
|
+
cequel[:posts].where(row_keys).
|
171
|
+
list_prepend(:categories, ['Scalability', 'Partition Tolerance'])
|
172
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
173
|
+
['Partition Tolerance', 'Scalability', 'Big Data', 'Cassandra']
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe '#list_append' do
|
178
|
+
it 'should append single element to list column' do
|
179
|
+
cequel[:posts].insert(
|
180
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra']))
|
181
|
+
cequel[:posts].where(row_keys).
|
182
|
+
list_append(:categories, 'Scalability')
|
183
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
184
|
+
['Big Data', 'Cassandra', 'Scalability']
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'should append multiple elements to list column' do
|
188
|
+
cequel[:posts].insert(
|
189
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra']))
|
190
|
+
cequel[:posts].where(row_keys).
|
191
|
+
list_append(:categories, ['Scalability', 'Partition Tolerance'])
|
192
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
193
|
+
['Big Data', 'Cassandra', 'Scalability', 'Partition Tolerance']
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe '#list_replace' do
|
198
|
+
it 'should add to list at specified index' do
|
199
|
+
cequel[:posts].insert(
|
200
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra', 'Scalability']))
|
201
|
+
cequel[:posts].where(row_keys).
|
202
|
+
list_replace(:categories, 1, 'C*')
|
203
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
204
|
+
['Big Data', 'C*', 'Scalability']
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe '#list_remove' do
|
209
|
+
it 'should remove from list by specified value' do
|
210
|
+
cequel[:posts].insert(
|
211
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra', 'Scalability']))
|
212
|
+
cequel[:posts].where(row_keys).
|
213
|
+
list_remove(:categories, 'Cassandra')
|
214
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
215
|
+
['Big Data', 'Scalability']
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should remove from list by multiple values' do
|
219
|
+
cequel[:posts].insert(
|
220
|
+
row_keys.merge(categories: ['Big Data', 'Cassandra', 'Scalability']))
|
221
|
+
cequel[:posts].where(row_keys).
|
222
|
+
list_remove(:categories, ['Big Data', 'Cassandra'])
|
223
|
+
cequel[:posts].where(row_keys).first[:categories].should ==
|
224
|
+
['Scalability']
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#set_add' do
|
229
|
+
it 'should add one element to set' do
|
230
|
+
cequel[:posts].insert(
|
231
|
+
row_keys.merge(tags: Set['big-data', 'nosql']))
|
232
|
+
cequel[:posts].where(row_keys).set_add(:tags, 'cassandra')
|
233
|
+
cequel[:posts].where(row_keys).first[:tags].
|
234
|
+
should == Set['big-data', 'nosql', 'cassandra']
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'should add multiple elements to set' do
|
238
|
+
cequel[:posts].insert(
|
239
|
+
row_keys.merge(tags: Set['big-data', 'nosql']))
|
240
|
+
cequel[:posts].where(row_keys).set_add(:tags, 'cassandra')
|
241
|
+
cequel[:posts].where(row_keys).first[:tags].
|
242
|
+
should == Set['big-data', 'nosql', 'cassandra']
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe '#set_remove' do
|
247
|
+
it 'should remove elements from set' do
|
248
|
+
cequel[:posts].insert(
|
249
|
+
row_keys.merge(tags: Set['big-data', 'nosql', 'cassandra']))
|
250
|
+
cequel[:posts].where(row_keys).set_remove(:tags, 'cassandra')
|
251
|
+
cequel[:posts].where(row_keys).first[:tags].
|
252
|
+
should == Set['big-data', 'nosql']
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'should remove multiple elements from set' do
|
256
|
+
cequel[:posts].insert(
|
257
|
+
row_keys.merge(tags: Set['big-data', 'nosql', 'cassandra']))
|
258
|
+
cequel[:posts].where(row_keys).
|
259
|
+
set_remove(:tags, Set['nosql', 'cassandra'])
|
260
|
+
cequel[:posts].where(row_keys).first[:tags].
|
261
|
+
should == Set['big-data']
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe '#map_update' do
|
266
|
+
it 'should update specified map key with value' do
|
267
|
+
time1 = Time.at(Time.now.to_i)
|
268
|
+
time2 = Time.at(10.minutes.ago.to_i)
|
269
|
+
time3 = Time.at(1.hour.ago.to_i)
|
270
|
+
cequel[:posts].insert(row_keys.merge(
|
271
|
+
trackbacks: {time1 => 'foo', time2 => 'bar'}))
|
272
|
+
cequel[:posts].where(row_keys).map_update(:trackbacks, time3 => 'baz')
|
273
|
+
cequel[:posts].where(row_keys).first[:trackbacks].
|
274
|
+
should == {time1 => 'foo', time2 => 'bar', time3 => 'baz'}
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'should update specified map key with multiple values' do
|
278
|
+
time1 = Time.at(Time.now.to_i)
|
279
|
+
time2 = Time.at(10.minutes.ago.to_i)
|
280
|
+
time3 = Time.at(1.hour.ago.to_i)
|
281
|
+
cequel[:posts].insert(row_keys.merge(
|
282
|
+
trackbacks: {time1 => 'foo', time2 => 'bar'}))
|
283
|
+
cequel[:posts].where(row_keys).
|
284
|
+
map_update(:trackbacks, time1 => 'FOO', time3 => 'baz')
|
285
|
+
cequel[:posts].where(row_keys).first[:trackbacks].
|
286
|
+
should == {time1 => 'FOO', time2 => 'bar', time3 => 'baz'}
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe '#increment' do
|
291
|
+
after { cequel.schema.truncate_table(:post_activity) }
|
292
|
+
|
293
|
+
it 'should increment counter columns' do
|
294
|
+
cequel[:post_activity].
|
295
|
+
where(row_keys).
|
296
|
+
increment(visits: 1, tweets: 2)
|
297
|
+
|
298
|
+
row = cequel[:post_activity].where(row_keys).first
|
299
|
+
|
300
|
+
row[:visits].should == 1
|
301
|
+
row[:tweets].should == 2
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
describe '#decrement' do
|
306
|
+
after { cequel.schema.truncate_table(:post_activity) }
|
307
|
+
|
308
|
+
it 'should decrement counter columns' do
|
309
|
+
cequel[:post_activity].where(row_keys).
|
310
|
+
decrement(visits: 1, tweets: 2)
|
311
|
+
|
312
|
+
row = cequel[:post_activity].where(row_keys).first
|
313
|
+
row[:visits].should == -1
|
314
|
+
row[:tweets].should == -2
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe '#delete' do
|
319
|
+
before do
|
320
|
+
cequel[:posts].
|
321
|
+
insert(row_keys.merge(title: 'Big Data', body: 'It\'s big.'))
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'should send basic delete statement' do
|
325
|
+
cequel[:posts].where(row_keys).delete
|
326
|
+
cequel[:posts].where(row_keys).first.should be_nil
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should send delete statement for specified columns' do
|
330
|
+
cequel[:posts].where(row_keys).delete(:body)
|
331
|
+
row = cequel[:posts].where(row_keys).first
|
332
|
+
row[:body].should be_nil
|
333
|
+
row[:title].should == 'Big Data'
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'should send delete statement with writetime option' do
|
337
|
+
time = Time.now - 10.minutes
|
338
|
+
|
339
|
+
cequel[:posts].where(row_keys).delete(
|
340
|
+
:body, :timestamp => time
|
341
|
+
)
|
342
|
+
row = cequel[:posts].select(:body).where(row_keys).first
|
343
|
+
row[:body].should == 'It\'s big.'
|
344
|
+
# This means timestamp is working, since the earlier timestamp would cause
|
345
|
+
# Cassandra to ignore the deletion
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe '#list_remove_at' do
|
350
|
+
it 'should remove element at specified position from list' do
|
351
|
+
cequel[:posts].
|
352
|
+
insert(row_keys.merge(categories: ['Big Data', 'NoSQL', 'Cassandra']))
|
353
|
+
cequel[:posts].where(row_keys).list_remove_at(:categories, 1)
|
354
|
+
cequel[:posts].where(row_keys).first[:categories].
|
355
|
+
should == ['Big Data', 'Cassandra']
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'should remove element at specified positions from list' do
|
359
|
+
cequel[:posts].
|
360
|
+
insert(row_keys.merge(categories: ['Big Data', 'NoSQL', 'Cassandra']))
|
361
|
+
cequel[:posts].where(row_keys).list_remove_at(:categories, 0, 2)
|
362
|
+
cequel[:posts].where(row_keys).first[:categories].
|
363
|
+
should == ['NoSQL']
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe '#map_remove' do
|
368
|
+
it 'should remove one element from a map' do
|
369
|
+
time1 = Time.at(Time.now.to_i)
|
370
|
+
time2 = Time.at(10.minutes.ago.to_i)
|
371
|
+
time3 = Time.at(1.hour.ago.to_i)
|
372
|
+
cequel[:posts].insert(row_keys.merge(
|
373
|
+
trackbacks: {time1 => 'foo', time2 => 'bar', time3 => 'baz'}))
|
374
|
+
cequel[:posts].where(row_keys).map_remove(:trackbacks, time2)
|
375
|
+
cequel[:posts].where(row_keys).first[:trackbacks].
|
376
|
+
should == {time1 => 'foo', time3 => 'baz'}
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'should remove multiple elements from a map' do
|
380
|
+
time1 = Time.at(Time.now.to_i)
|
381
|
+
time2 = Time.at(10.minutes.ago.to_i)
|
382
|
+
time3 = Time.at(1.hour.ago.to_i)
|
383
|
+
cequel[:posts].insert(row_keys.merge(
|
384
|
+
trackbacks: {time1 => 'foo', time2 => 'bar', time3 => 'baz'}))
|
385
|
+
cequel[:posts].where(row_keys).map_remove(:trackbacks, time1, time3)
|
386
|
+
cequel[:posts].where(row_keys).first[:trackbacks].
|
387
|
+
should == {time2 => 'bar'}
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
describe '#cql' do
|
392
|
+
it 'should generate select statement with all columns' do
|
393
|
+
cequel[:posts].cql.should == ['SELECT * FROM posts']
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe '#select' do
|
398
|
+
before do
|
399
|
+
cequel[:posts].insert(row_keys.merge(
|
400
|
+
title: 'Big Data',
|
401
|
+
body: 'Fault Tolerance',
|
402
|
+
published_at: Time.now
|
403
|
+
))
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'should generate select statement with given columns' do
|
407
|
+
cequel[:posts].select(:title, :body).where(row_keys).first.
|
408
|
+
keys.should == %w(title body)
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'should accept array argument' do
|
412
|
+
cequel[:posts].select([:title, :body]).where(row_keys).first.
|
413
|
+
keys.should == %w(title body)
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'should combine multiple selects' do
|
417
|
+
cequel[:posts].select(:title).select(:body).where(row_keys).first.
|
418
|
+
keys.should == %w(title body)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
describe '#select!' do
|
423
|
+
before do
|
424
|
+
cequel[:posts].insert(row_keys.merge(
|
425
|
+
title: 'Big Data',
|
426
|
+
body: 'Fault Tolerance',
|
427
|
+
published_at: Time.now
|
428
|
+
))
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should override select statement with given columns' do
|
432
|
+
cequel[:posts].select(:title, :body).select!(:published_at).
|
433
|
+
where(row_keys).first.keys.should == %w(published_at)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
describe '#where' do
|
438
|
+
before do
|
439
|
+
cequel[:posts].insert(row_keys.merge(
|
440
|
+
title: 'Big Data',
|
441
|
+
body: 'Fault Tolerance',
|
442
|
+
published_at: Time.now
|
443
|
+
))
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'should build WHERE statement from hash' do
|
447
|
+
cequel[:posts].where(blog_subdomain: row_keys[:blog_subdomain]).
|
448
|
+
first[:title].should == 'Big Data'
|
449
|
+
cequel[:posts].where(blog_subdomain: 'foo').first.should be_nil
|
450
|
+
end
|
451
|
+
|
452
|
+
it 'should build WHERE statement from multi-element hash' do
|
453
|
+
cequel[:posts].where(row_keys).first[:title].should == 'Big Data'
|
454
|
+
cequel[:posts].where(row_keys.merge(:permalink => 'foo')).
|
455
|
+
first.should be_nil
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'should build WHERE statement with IN' do
|
459
|
+
cequel[:posts].insert(row_keys.merge(
|
460
|
+
blog_subdomain: 'big-data-weekly',
|
461
|
+
title: 'Cassandra',
|
462
|
+
))
|
463
|
+
cequel[:posts].insert(row_keys.merge(
|
464
|
+
blog_subdomain: 'bogus-blog',
|
465
|
+
title: 'Bogus Post',
|
466
|
+
))
|
467
|
+
cequel[:posts].where(
|
468
|
+
:blog_subdomain => %w(cassandra big-data-weekly),
|
469
|
+
:permalink => 'big-data'
|
470
|
+
).map { |row| row[:title] }.should == ['Big Data', 'Cassandra']
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'should use = if provided one-element array' do
|
474
|
+
cequel[:posts].
|
475
|
+
where(row_keys.merge(blog_subdomain: [row_keys[:blog_subdomain]])).
|
476
|
+
first[:title].should == 'Big Data'
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'should build WHERE statement from CQL string' do
|
480
|
+
cequel[:posts].where("blog_subdomain = '#{row_keys[:blog_subdomain]}'").
|
481
|
+
first[:title].should == 'Big Data'
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'should build WHERE statement from CQL string with bind variables' do
|
485
|
+
cequel[:posts].where("blog_subdomain = ?", row_keys[:blog_subdomain]).
|
486
|
+
first[:title].should == 'Big Data'
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'should aggregate multiple WHERE statements' do
|
490
|
+
cequel[:posts].where(:blog_subdomain => row_keys[:blog_subdomain]).
|
491
|
+
where('permalink = ?', row_keys[:permalink]).
|
492
|
+
first[:title].should == 'Big Data'
|
493
|
+
end
|
494
|
+
|
495
|
+
end
|
496
|
+
|
497
|
+
describe '#where!' do
|
498
|
+
before do
|
499
|
+
cequel[:posts].insert(row_keys.merge(
|
500
|
+
title: 'Big Data',
|
501
|
+
body: 'Fault Tolerance',
|
502
|
+
published_at: Time.now
|
503
|
+
))
|
504
|
+
end
|
505
|
+
|
506
|
+
it 'should override chained conditions' do
|
507
|
+
cequel[:posts].where(:permalink => 'bogus').
|
508
|
+
where!(:blog_subdomain => row_keys[:blog_subdomain]).
|
509
|
+
first[:title].should == 'Big Data'
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
describe '#limit' do
|
514
|
+
before do
|
515
|
+
cequel[:posts].insert(row_keys.merge(title: 'Big Data'))
|
516
|
+
cequel[:posts].insert(
|
517
|
+
row_keys.merge(permalink: 'marshmallows', title: 'Marshmallows'))
|
518
|
+
cequel[:posts].insert(
|
519
|
+
row_keys.merge(permalink: 'zz-top', title: 'ZZ Top'))
|
520
|
+
end
|
521
|
+
|
522
|
+
it 'should add LIMIT' do
|
523
|
+
cequel[:posts].where(row_keys.slice(:blog_subdomain)).limit(2).
|
524
|
+
map { |row| row[:title] }.should == ['Big Data', 'Marshmallows']
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
describe '#order' do
|
529
|
+
before do
|
530
|
+
cequel[:posts].insert(row_keys.merge(title: 'Big Data'))
|
531
|
+
cequel[:posts].insert(
|
532
|
+
row_keys.merge(permalink: 'marshmallows', title: 'Marshmallows'))
|
533
|
+
cequel[:posts].insert(
|
534
|
+
row_keys.merge(permalink: 'zz-top', title: 'ZZ Top'))
|
535
|
+
end
|
536
|
+
|
537
|
+
it 'should add order' do
|
538
|
+
cequel[:posts].where(row_keys.slice(:blog_subdomain)).
|
539
|
+
order(permalink: 'desc').map { |row| row[:title] }.
|
540
|
+
should == ['ZZ Top', 'Marshmallows', 'Big Data']
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
describe 'result enumeration' do
|
545
|
+
let(:row) { row_keys.merge(:title => 'Big Data') }
|
546
|
+
|
547
|
+
before do
|
548
|
+
cequel[:posts].insert(row)
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'should enumerate over results' do
|
552
|
+
cequel[:posts].to_a.map { |row| row.select { |k, v| v }}.
|
553
|
+
should == [row.stringify_keys]
|
554
|
+
end
|
555
|
+
|
556
|
+
it 'should provide results with indifferent access' do
|
557
|
+
cequel[:posts].to_a.first[:blog_permalink].
|
558
|
+
should == row_keys[:blog_permalink]
|
559
|
+
end
|
560
|
+
|
561
|
+
it 'should not run query if no block given to #each' do
|
562
|
+
expect { cequel[:posts].each }.to_not raise_error
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'should return Enumerator if no block given to #each' do
|
566
|
+
cequel[:posts].each.each_with_index.
|
567
|
+
map { |row, i| [row[:blog_permalink], i] }.
|
568
|
+
should == [[row[:blog_permalink], 0]]
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
describe '#first' do
|
573
|
+
let(:row) { row_keys.merge(:title => 'Big Data') }
|
574
|
+
|
575
|
+
before do
|
576
|
+
cequel[:posts].insert(row)
|
577
|
+
cequel[:posts].insert(
|
578
|
+
row_keys.merge(:permalink => 'zz-top', :title => 'ZZ Top'))
|
579
|
+
end
|
580
|
+
|
581
|
+
it 'should run a query with LIMIT 1 and return first row' do
|
582
|
+
cequel[:posts].first.select { |k, v| v }.should == row.stringify_keys
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
describe '#count' do
|
587
|
+
before do
|
588
|
+
4.times do |i|
|
589
|
+
cequel[:posts].insert(row_keys.merge(
|
590
|
+
permalink: "post-#{i}", title: "Post #{i}"))
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'should run a count query and return count' do
|
595
|
+
cequel[:posts].count.should == 4
|
596
|
+
end
|
597
|
+
|
598
|
+
it 'should use where clause if specified' do
|
599
|
+
cequel[:posts].where(row_keys.merge(permalink: 'post-1')).
|
600
|
+
count.should == 1
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'should use limit if specified' do
|
604
|
+
cequel[:posts].limit(2).count.should == 2
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
end
|