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.
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