cequel 0.5.6 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cequel.rb +5 -8
  3. data/lib/cequel/errors.rb +1 -0
  4. data/lib/cequel/metal.rb +17 -0
  5. data/lib/cequel/metal/batch.rb +62 -0
  6. data/lib/cequel/metal/cql_row_specification.rb +26 -0
  7. data/lib/cequel/metal/data_set.rb +461 -0
  8. data/lib/cequel/metal/deleter.rb +47 -0
  9. data/lib/cequel/metal/incrementer.rb +35 -0
  10. data/lib/cequel/metal/inserter.rb +53 -0
  11. data/lib/cequel/metal/keyspace.rb +213 -0
  12. data/lib/cequel/metal/row.rb +48 -0
  13. data/lib/cequel/metal/row_specification.rb +37 -0
  14. data/lib/cequel/metal/statement.rb +30 -0
  15. data/lib/cequel/metal/updater.rb +65 -0
  16. data/lib/cequel/metal/writer.rb +73 -0
  17. data/lib/cequel/model.rb +12 -84
  18. data/lib/cequel/model/association_collection.rb +23 -0
  19. data/lib/cequel/model/associations.rb +84 -80
  20. data/lib/cequel/model/base.rb +74 -0
  21. data/lib/cequel/model/belongs_to_association.rb +31 -0
  22. data/lib/cequel/model/callbacks.rb +14 -10
  23. data/lib/cequel/model/collection.rb +255 -0
  24. data/lib/cequel/model/errors.rb +6 -6
  25. data/lib/cequel/model/has_many_association.rb +26 -0
  26. data/lib/cequel/model/mass_assignment.rb +31 -0
  27. data/lib/cequel/model/persistence.rb +119 -115
  28. data/lib/cequel/model/properties.rb +89 -87
  29. data/lib/cequel/model/railtie.rb +21 -14
  30. data/lib/cequel/model/record_set.rb +285 -0
  31. data/lib/cequel/model/schema.rb +33 -0
  32. data/lib/cequel/model/scoped.rb +5 -48
  33. data/lib/cequel/model/validations.rb +18 -18
  34. data/lib/cequel/schema.rb +15 -0
  35. data/lib/cequel/schema/column.rb +135 -0
  36. data/lib/cequel/schema/create_table_dsl.rb +56 -0
  37. data/lib/cequel/schema/keyspace.rb +50 -0
  38. data/lib/cequel/schema/table.rb +120 -0
  39. data/lib/cequel/schema/table_property.rb +67 -0
  40. data/lib/cequel/schema/table_reader.rb +139 -0
  41. data/lib/cequel/schema/table_synchronizer.rb +114 -0
  42. data/lib/cequel/schema/table_updater.rb +83 -0
  43. data/lib/cequel/schema/table_writer.rb +80 -0
  44. data/lib/cequel/schema/update_table_dsl.rb +60 -0
  45. data/lib/cequel/type.rb +232 -0
  46. data/lib/cequel/version.rb +1 -1
  47. data/spec/environment.rb +5 -1
  48. data/spec/examples/metal/data_set_spec.rb +608 -0
  49. data/spec/examples/model/associations_spec.rb +84 -74
  50. data/spec/examples/model/callbacks_spec.rb +66 -59
  51. data/spec/examples/model/list_spec.rb +393 -0
  52. data/spec/examples/model/map_spec.rb +229 -0
  53. data/spec/examples/model/mass_assignment_spec.rb +55 -0
  54. data/spec/examples/model/naming_spec.rb +11 -4
  55. data/spec/examples/model/persistence_spec.rb +140 -150
  56. data/spec/examples/model/properties_spec.rb +122 -75
  57. data/spec/examples/model/record_set_spec.rb +285 -0
  58. data/spec/examples/model/schema_spec.rb +44 -0
  59. data/spec/examples/model/serialization_spec.rb +20 -14
  60. data/spec/examples/model/set_spec.rb +133 -0
  61. data/spec/examples/model/spec_helper.rb +0 -10
  62. data/spec/examples/model/validations_spec.rb +51 -38
  63. data/spec/examples/schema/table_reader_spec.rb +328 -0
  64. data/spec/examples/schema/table_synchronizer_spec.rb +172 -0
  65. data/spec/examples/schema/table_updater_spec.rb +157 -0
  66. data/spec/examples/schema/table_writer_spec.rb +225 -0
  67. data/spec/examples/spec_helper.rb +29 -0
  68. data/spec/examples/type_spec.rb +204 -0
  69. data/spec/support/helpers.rb +67 -8
  70. metadata +121 -152
  71. data/lib/cequel/batch.rb +0 -58
  72. data/lib/cequel/cql_row_specification.rb +0 -22
  73. data/lib/cequel/data_set.rb +0 -371
  74. data/lib/cequel/keyspace.rb +0 -205
  75. data/lib/cequel/model/class_internals.rb +0 -49
  76. data/lib/cequel/model/column.rb +0 -20
  77. data/lib/cequel/model/counter.rb +0 -35
  78. data/lib/cequel/model/dictionary.rb +0 -126
  79. data/lib/cequel/model/dirty.rb +0 -53
  80. data/lib/cequel/model/dynamic.rb +0 -31
  81. data/lib/cequel/model/inheritable.rb +0 -48
  82. data/lib/cequel/model/instance_internals.rb +0 -23
  83. data/lib/cequel/model/local_association.rb +0 -42
  84. data/lib/cequel/model/magic.rb +0 -79
  85. data/lib/cequel/model/mass_assignment_security.rb +0 -21
  86. data/lib/cequel/model/naming.rb +0 -17
  87. data/lib/cequel/model/observer.rb +0 -42
  88. data/lib/cequel/model/readable_dictionary.rb +0 -182
  89. data/lib/cequel/model/remote_association.rb +0 -40
  90. data/lib/cequel/model/scope.rb +0 -362
  91. data/lib/cequel/model/subclass_internals.rb +0 -45
  92. data/lib/cequel/model/timestamps.rb +0 -52
  93. data/lib/cequel/model/translation.rb +0 -17
  94. data/lib/cequel/row_specification.rb +0 -63
  95. data/lib/cequel/statement.rb +0 -23
  96. data/spec/examples/data_set_spec.rb +0 -444
  97. data/spec/examples/keyspace_spec.rb +0 -84
  98. data/spec/examples/model/counter_spec.rb +0 -94
  99. data/spec/examples/model/dictionary_spec.rb +0 -301
  100. data/spec/examples/model/dirty_spec.rb +0 -39
  101. data/spec/examples/model/dynamic_spec.rb +0 -41
  102. data/spec/examples/model/inheritable_spec.rb +0 -45
  103. data/spec/examples/model/magic_spec.rb +0 -199
  104. data/spec/examples/model/mass_assignment_security_spec.rb +0 -13
  105. data/spec/examples/model/observer_spec.rb +0 -86
  106. data/spec/examples/model/scope_spec.rb +0 -677
  107. data/spec/examples/model/timestamps_spec.rb +0 -52
  108. data/spec/examples/model/translation_spec.rb +0 -23
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Cequel
2
- VERSION = '0.5.6'
2
+ VERSION = '1.0.0.pre.1'
3
3
  end
data/spec/environment.rb CHANGED
@@ -1,3 +1,7 @@
1
1
  require 'bundler'
2
2
 
3
- Bundler.require(:default, :test)
3
+ if ENV['CI']
4
+ Bundler.require(:default, :test)
5
+ else
6
+ Bundler.require(:default, :test, :debug)
7
+ end
@@ -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