hyper_record 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/CHANGELOG +83 -0
- data/LICENSE +20 -0
- data/README +49 -0
- data/Rakefile +43 -0
- data/VERSION.yml +4 -0
- data/benchmark/save.rb +58 -0
- data/hyper_record.gemspec +76 -0
- data/init.rb +1 -0
- data/lib/active_record/connection_adapters/hyper_table_definition.rb +26 -0
- data/lib/active_record/connection_adapters/hypertable_adapter.rb +680 -0
- data/lib/active_record/connection_adapters/qualified_column.rb +57 -0
- data/lib/associations/hyper_has_and_belongs_to_many_association_extension.rb +107 -0
- data/lib/associations/hyper_has_many_association_extension.rb +87 -0
- data/lib/hyper_record.rb +636 -0
- data/lib/hypertable/gen-rb/client_constants.rb +12 -0
- data/lib/hypertable/gen-rb/client_service.rb +1436 -0
- data/lib/hypertable/gen-rb/client_types.rb +253 -0
- data/lib/hypertable/gen-rb/hql_constants.rb +12 -0
- data/lib/hypertable/gen-rb/hql_service.rb +281 -0
- data/lib/hypertable/gen-rb/hql_types.rb +73 -0
- data/lib/hypertable/thrift_client.rb +94 -0
- data/lib/hypertable/thrift_transport_monkey_patch.rb +29 -0
- data/pkg/hyper_record-0.2.8.gem +0 -0
- data/spec/fixtures/pages.yml +8 -0
- data/spec/fixtures/qualified_pages.yml +1 -0
- data/spec/lib/associations_spec.rb +235 -0
- data/spec/lib/hyper_record_spec.rb +948 -0
- data/spec/lib/hypertable_adapter_spec.rb +121 -0
- data/spec/spec_helper.rb +130 -0
- data/test/test_helper.rb +10 -0
- data/test/thrift_client_test.rb +590 -0
- metadata +99 -0
@@ -0,0 +1,948 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module HyperRecord
|
5
|
+
describe HyperBase, '.describe_table' do
|
6
|
+
fixtures :pages
|
7
|
+
|
8
|
+
it "should return a string describing the table schema" do
|
9
|
+
table_description = Page.connection.describe_table(Page.table_name)
|
10
|
+
table_description.should_not be_empty
|
11
|
+
table_description.should include("name")
|
12
|
+
table_description.should include("url")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe HyperBase, '.table_exists?' do
|
17
|
+
fixtures :pages
|
18
|
+
|
19
|
+
it "should return true if the underlying table exists" do
|
20
|
+
Page.table_exists?.should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return false if the underlying table does not exists" do
|
24
|
+
Dummy.table_exists?.should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe HyperBase, '.drop_table' do
|
29
|
+
fixtures :pages
|
30
|
+
|
31
|
+
it "should remove a table from hypertable" do
|
32
|
+
Page.table_exists?.should be_true
|
33
|
+
Page.drop_table
|
34
|
+
Page.table_exists?.should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe HyperBase, '.columns' do
|
39
|
+
fixtures :pages
|
40
|
+
|
41
|
+
it "should return an array of columns within the table" do
|
42
|
+
table_columns = Page.columns
|
43
|
+
table_columns.should_not be_empty
|
44
|
+
# column list include the special ROW key.
|
45
|
+
table_columns.map{|c| c.name}.should == ['ROW', 'name', 'url']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe HyperBase, '.column_families_without_row_key' do
|
50
|
+
fixtures :pages
|
51
|
+
|
52
|
+
it "should return an array of columns within the table but does not include the row key" do
|
53
|
+
columns_without_row_key = Page.column_families_without_row_key
|
54
|
+
columns_without_row_key.should_not be_empty
|
55
|
+
# column list does not include the special ROW key.
|
56
|
+
columns_without_row_key.map{|c| c.name}.should == ['name', 'url']
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe HyperBase, '.qualified_column_names_without_row_key' do
|
61
|
+
fixtures :pages
|
62
|
+
|
63
|
+
it "should return an array of column names where column families are replaced by fully qualified columns" do
|
64
|
+
cols = QualifiedPage.qualified_column_names_without_row_key
|
65
|
+
cols.should_not be_empty
|
66
|
+
cols.should == ['misc:name', 'misc:url', 'misc2:foo', 'misc2:bar']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe HyperBase, '.qualified_columns' do
|
71
|
+
it "should include qualified columns in the regular column list" do
|
72
|
+
columns = QualifiedPage.columns
|
73
|
+
columns.should_not be_empty
|
74
|
+
columns.map{|c| c.name}.should == ['ROW', 'misc', 'misc2']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe HyperBase, '.find_by_hql' do
|
79
|
+
fixtures :pages
|
80
|
+
|
81
|
+
it "should return the cells matching the hql specified" do
|
82
|
+
pages = Page.find_by_hql("SELECT * FROM pages LIMIT=1")
|
83
|
+
pages.length.should == 1
|
84
|
+
page = pages.first
|
85
|
+
page.class.should == Page
|
86
|
+
page.name.should == "LOLcats and more"
|
87
|
+
page.url.should == "http://www.icanhascheezburger.com"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should respond to the find_by_sql alias" do
|
91
|
+
pages = Page.find_by_hql("SELECT * FROM pages LIMIT=1")
|
92
|
+
pages.length.should == 1
|
93
|
+
page = pages.first
|
94
|
+
page.class.should == Page
|
95
|
+
page.name.should == "LOLcats and more"
|
96
|
+
page.url.should == "http://www.icanhascheezburger.com"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe HyperBase, '.find_initial' do
|
101
|
+
fixtures :pages
|
102
|
+
|
103
|
+
it "should return the first row in the table" do
|
104
|
+
page = Page.find_initial({})
|
105
|
+
page.class.should == Page
|
106
|
+
page.name.should == "LOLcats and more"
|
107
|
+
page.url.should == "http://www.icanhascheezburger.com"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe HyperBase, '.find_one' do
|
112
|
+
fixtures :pages
|
113
|
+
|
114
|
+
it "should return the requested row from the table" do
|
115
|
+
page = Page.find_one('page_1', {})
|
116
|
+
page.class.should == Page
|
117
|
+
page.name.should == "LOLcats and more"
|
118
|
+
page.url.should == "http://www.icanhascheezburger.com"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe HyperBase, '.find_some' do
|
123
|
+
fixtures :pages
|
124
|
+
|
125
|
+
it "should return the requested rows from the table" do
|
126
|
+
row_keys = Page.find(:all).map{|p| p.ROW}
|
127
|
+
record_count = row_keys.length
|
128
|
+
record_count.should == 2
|
129
|
+
pages = Page.find(row_keys)
|
130
|
+
pages.length.should == record_count
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe HyperBase, '.find' do
|
135
|
+
fixtures :pages, :qualified_pages
|
136
|
+
|
137
|
+
it "should return the declared list of qualified columns by default" do
|
138
|
+
qp = QualifiedPage.new
|
139
|
+
qp.new_record?.should be_true
|
140
|
+
qp.misc['name'] = 'new page'
|
141
|
+
qp.misc['url']= 'new.com'
|
142
|
+
qp.ROW = 'new_qualified_page'
|
143
|
+
qp.save.should be_true
|
144
|
+
|
145
|
+
qp = QualifiedPage.find('new_qualified_page')
|
146
|
+
qp.misc.keys.sort.should == ['name', 'url']
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should support the limit option" do
|
150
|
+
p = Page.new({:ROW => 'row key', :name => 'new entry'})
|
151
|
+
p.save.should be_true
|
152
|
+
Page.find(:all).length.should == 3
|
153
|
+
pages = Page.find(:all, :limit => 2)
|
154
|
+
pages.length.should == 2
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should support the start_row and end_row option" do
|
158
|
+
p = Page.new({:ROW => 'row key', :name => 'new entry'})
|
159
|
+
p.save.should be_true
|
160
|
+
pages = Page.find(:all)
|
161
|
+
pages.length.should == 3
|
162
|
+
start_row = pages[1].ROW
|
163
|
+
end_row = pages[2].ROW
|
164
|
+
|
165
|
+
pages_2 = Page.find(:all, :start_row => start_row, :end_row => end_row)
|
166
|
+
pages_2.length.should == 2
|
167
|
+
pages_2[0].ROW.should == start_row
|
168
|
+
pages_2[1].ROW.should == end_row
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should support the row_keys option" do
|
172
|
+
p = Page.new({:ROW => 'row key', :name => 'new entry'})
|
173
|
+
p.save.should be_true
|
174
|
+
pages = Page.find(:all)
|
175
|
+
pages.length.should == 3
|
176
|
+
row_key_1 = pages[1].ROW
|
177
|
+
row_key_2 = pages[2].ROW
|
178
|
+
|
179
|
+
pages_2 = Page.find(:all, :row_keys => [row_key_1, row_key_2])
|
180
|
+
pages_2.length.should == 2
|
181
|
+
pages_2[0].ROW.should == row_key_1
|
182
|
+
pages_2[1].ROW.should == row_key_2
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should support the row_intervals option" do
|
186
|
+
p = Page.new({:ROW => 'row key', :name => 'new entry'})
|
187
|
+
p.save.should be_true
|
188
|
+
pages = Page.find(:all)
|
189
|
+
pages.length.should == 3
|
190
|
+
row_key_1 = pages[1].ROW
|
191
|
+
row_key_2 = pages[2].ROW
|
192
|
+
|
193
|
+
pages_2 = Page.find(:all, :row_intervals => [[row_key_1, row_key_2]])
|
194
|
+
pages_2.length.should == 2
|
195
|
+
pages_2[0].ROW.should == row_key_1
|
196
|
+
pages_2[1].ROW.should == row_key_2
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should not support finder conditions not in Hash format" do
|
200
|
+
lambda {Page.find(:all, :conditions => "value = 1")}.should raise_error
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should not support finder conditions in Hash format" do
|
204
|
+
# NOTE: will be supported in the future when Hypertable supports
|
205
|
+
# efficient lookup on arbitrary columns
|
206
|
+
lambda {
|
207
|
+
pages = Page.find(:all, :conditions => {:name => 'ESPN'})
|
208
|
+
pages.length.should == 1
|
209
|
+
p = pages.first
|
210
|
+
p.name.should == 'ESPN'
|
211
|
+
p.ROW.should == 'page_2'
|
212
|
+
}.should raise_error
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should not support finder conditions in Hash format when the value is an array" do
|
216
|
+
# NOTE: will be supported in the future when Hypertable supports
|
217
|
+
# efficient lookup on arbitrary columns
|
218
|
+
lambda {
|
219
|
+
all_pages = Page.find(:all)
|
220
|
+
all_pages.length.should == 2
|
221
|
+
name_values = all_pages.map{|p| p.name}
|
222
|
+
pages = Page.find(:all, :conditions => {:name => name_values})
|
223
|
+
pages.length.should == 2
|
224
|
+
}.should raise_error
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should return a specific list of qualifiers when requested explicitly in finder options" do
|
228
|
+
qp = QualifiedPage.new
|
229
|
+
qp.new_record?.should be_true
|
230
|
+
qp.misc['name'] = 'new page'
|
231
|
+
qp.misc['url']= 'new.com'
|
232
|
+
qp.ROW = 'new_qualified_page'
|
233
|
+
qp.save.should be_true
|
234
|
+
|
235
|
+
qp = QualifiedPage.find('new_qualified_page', :select => 'misc:url')
|
236
|
+
# NOTE: will be supported in the future when Hypertable supports
|
237
|
+
# efficient lookup on arbitrary columns
|
238
|
+
# qp.misc.keys.sort.should == ['url']
|
239
|
+
# For now, it returns all columns
|
240
|
+
qp.misc.keys.sort.should == ['name', 'url']
|
241
|
+
end
|
242
|
+
|
243
|
+
describe ':select option' do
|
244
|
+
before(:each) do
|
245
|
+
@qpweq = QualifiedPageWithoutExplicitQualifiers.new
|
246
|
+
@qpweq.new_record?.should be_true
|
247
|
+
@qpweq.misc['name'] = 'new page'
|
248
|
+
@qpweq.misc['url'] = 'new.com'
|
249
|
+
@qpweq.ROW = 'new_qualified_page'
|
250
|
+
@qpweq.save.should be_true
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should return an empty hash for all qualified columns even if none are explicitly listed in qualifiers" do
|
254
|
+
@qpweq2 = QualifiedPageWithoutExplicitQualifiers.find(@qpweq.ROW)
|
255
|
+
@qpweq2.misc.should == {}
|
256
|
+
@qpweq2.misc2.should == ""
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should return correct values for qualified columns named in select list using comma separated string" do
|
260
|
+
qpweq2 = QualifiedPageWithoutExplicitQualifiers.find(@qpweq.ROW, :select => "misc,misc2")
|
261
|
+
qpweq2.misc.should == {"name"=>"new page", "url"=>"new.com"}
|
262
|
+
qpweq2.misc2.should == ""
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should return correct values for qualified columns named in select list using array" do
|
266
|
+
qpweq2 = QualifiedPageWithoutExplicitQualifiers.find(@qpweq.ROW, :select => ["misc", "misc2"])
|
267
|
+
qpweq2.misc.should == {"name"=>"new page", "url"=>"new.com"}
|
268
|
+
qpweq2.misc2.should == ""
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should instantiate the object with empty hashes for qualified columns when no explicit select list is supplied" do
|
273
|
+
qp = QualifiedPage.new
|
274
|
+
qp.new_record?.should be_true
|
275
|
+
qp.ROW = 'new_qualified_page'
|
276
|
+
qp.misc2['name'] = 'test'
|
277
|
+
qp.save.should be_true
|
278
|
+
|
279
|
+
qp = QualifiedPage.find(:first)
|
280
|
+
qp.misc.should == {}
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should only instantiate requested columns when option set" do
|
284
|
+
p = Page.find("page_1")
|
285
|
+
p.name.should == "LOLcats and more"
|
286
|
+
p.url.should == "http://www.icanhascheezburger.com"
|
287
|
+
|
288
|
+
p = Page.find("page_1",
|
289
|
+
:select => 'name',
|
290
|
+
:instantiate_only_requested_columns => true)
|
291
|
+
|
292
|
+
p.name.should == "LOLcats and more"
|
293
|
+
lambda {p.url}.should raise_error(::ActiveRecord::MissingAttributeError)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should allow user to specify ROW key as part of initialize attributes" do
|
297
|
+
p = Page.new({:ROW => 'row key'})
|
298
|
+
p.ROW.should == 'row key'
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should not have any residual state between calls to new" do
|
302
|
+
qp = QualifiedPage.new
|
303
|
+
qp.new_record?.should be_true
|
304
|
+
qp.misc['name'] = 'new page'
|
305
|
+
qp.misc['url']= 'new.com'
|
306
|
+
qp.ROW = 'new_qualified_page'
|
307
|
+
qp.save.should be_true
|
308
|
+
|
309
|
+
qp2 = QualifiedPage.new
|
310
|
+
qp2.misc.should == {}
|
311
|
+
qp2.misc2.should == {}
|
312
|
+
qp.misc.object_id.should_not == qp2.misc.object_id
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
describe HyperBase, '.table_exists?' do
|
317
|
+
fixtures :pages
|
318
|
+
|
319
|
+
it "should return true for a table that does exist" do
|
320
|
+
Page.table_exists?.should be_true
|
321
|
+
end
|
322
|
+
|
323
|
+
it "should return false for a table that does exist" do
|
324
|
+
Dummy.table_exists?.should be_false
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe HyperBase, '.primary_key' do
|
329
|
+
it "should always return the special ROW key" do
|
330
|
+
Dummy.primary_key.should == 'ROW'
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe HyperBase, '.new' do
|
335
|
+
fixtures :pages, :qualified_pages
|
336
|
+
|
337
|
+
it "should an object of correct class" do
|
338
|
+
p = Page.new
|
339
|
+
p.new_record?.should be_true
|
340
|
+
p.class.should == Page
|
341
|
+
p.class.should < ActiveRecord::HyperBase
|
342
|
+
p.attributes.keys.sort.should == ['name', 'url']
|
343
|
+
end
|
344
|
+
|
345
|
+
it "should not allow an object to be saved without a row key" do
|
346
|
+
page_count = Page.find(:all).length
|
347
|
+
p = Page.new
|
348
|
+
p.new_record?.should be_true
|
349
|
+
p.name = "new page"
|
350
|
+
p.url = "new.com"
|
351
|
+
p.valid?.should be_false
|
352
|
+
p.save.should be_false
|
353
|
+
p.new_record?.should be_true
|
354
|
+
p.ROW = "new_page"
|
355
|
+
p.valid?.should be_true
|
356
|
+
p.save.should be_true
|
357
|
+
p.new_record?.should be_false
|
358
|
+
Page.find(:all).length.should == page_count + 1
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should save a table with qualified columns correctly" do
|
362
|
+
qp = QualifiedPage.new
|
363
|
+
qp.new_record?.should be_true
|
364
|
+
qp.misc['name'] = 'new page'
|
365
|
+
qp.misc['url']= 'new.com'
|
366
|
+
qp.ROW = 'new_qualified_page'
|
367
|
+
qp.save.should be_true
|
368
|
+
qp.new_record?.should be_false
|
369
|
+
qp.reload.should == qp
|
370
|
+
qp.misc['name'].should == 'new page'
|
371
|
+
qp.misc['url'].should == 'new.com'
|
372
|
+
qp.misc.keys.sort.should == ['name', 'url']
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
describe HyperBase, '.reload' do
|
377
|
+
fixtures :pages
|
378
|
+
|
379
|
+
it "should reload an object and revert any changed state" do
|
380
|
+
p = Page.find(:first)
|
381
|
+
p.class.should == Page
|
382
|
+
original_url = p.url.clone
|
383
|
+
p.url = "new url"
|
384
|
+
p.reload.should == p
|
385
|
+
p.url.should == original_url
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
describe HyperBase, '.save' do
|
390
|
+
fixtures :pages, :qualified_pages
|
391
|
+
|
392
|
+
it "should update an object in hypertable" do
|
393
|
+
p = Page.find(:first)
|
394
|
+
p.class.should == Page
|
395
|
+
original_url = p.url.clone
|
396
|
+
p.url = "new url"
|
397
|
+
p.save.should be_true
|
398
|
+
p.url.should == "new url"
|
399
|
+
p.reload.should == p
|
400
|
+
p.url.should == "new url"
|
401
|
+
end
|
402
|
+
|
403
|
+
it "should allow undeclared qualified columns to be saved, provided that the column family is declared" do
|
404
|
+
qp = QualifiedPage.new
|
405
|
+
qp.new_record?.should be_true
|
406
|
+
qp.misc['name'] = 'new page'
|
407
|
+
qp.misc['url'] = 'new.com'
|
408
|
+
qp.misc['new_column'] = 'value'
|
409
|
+
qp.ROW = 'new_qualified_page'
|
410
|
+
qp.save.should be_true
|
411
|
+
qp.new_record?.should be_false
|
412
|
+
qp.reload.should == qp
|
413
|
+
qp.misc['name'].should == 'new page'
|
414
|
+
qp.misc['url'].should == 'new.com'
|
415
|
+
qp.misc['new_column'].should == 'value'
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
describe HyperBase, '.save_with_mutator' do
|
420
|
+
fixtures :pages
|
421
|
+
|
422
|
+
it "should successfully save an object with mutator" do
|
423
|
+
m = Page.open_mutator
|
424
|
+
p1 = Page.new({:ROW => 'created_with_mutator_1', :url => 'url_1'})
|
425
|
+
p1.save_with_mutator!(m)
|
426
|
+
|
427
|
+
p2 = Page.new({:ROW => 'created_with_mutator_2', :url => 'url_2'})
|
428
|
+
p2.save_with_mutator!(m)
|
429
|
+
|
430
|
+
Page.close_mutator(m)
|
431
|
+
|
432
|
+
new_page_1 = Page.find('created_with_mutator_1')
|
433
|
+
new_page_1.url.should == 'url_1'
|
434
|
+
|
435
|
+
new_page_2 = Page.find('created_with_mutator_2')
|
436
|
+
new_page_2.url.should == 'url_2'
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should still flush the mutator and create objects when flush is not requested on close mutator" do
|
440
|
+
# As of release 0.9.2.5, Hypertable now auto-flushes the
|
441
|
+
# mutator on close.
|
442
|
+
|
443
|
+
m = Page.open_mutator
|
444
|
+
p1 = Page.new({:ROW => 'created_with_mutator_1', :url => 'url_1'})
|
445
|
+
p1.save_with_mutator!(m)
|
446
|
+
Page.close_mutator(m, 0)
|
447
|
+
|
448
|
+
page = Page.find('created_with_mutator_1')
|
449
|
+
page.should_not be_nil
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should support explicit flushing of the mutator" do
|
453
|
+
m = Page.open_mutator
|
454
|
+
p1 = Page.new({:ROW => 'created_with_mutator_1', :url => 'url_1'})
|
455
|
+
p1.save_with_mutator!(m)
|
456
|
+
Page.flush_mutator(m)
|
457
|
+
Page.close_mutator(m, 0)
|
458
|
+
|
459
|
+
new_page_1 = Page.find('created_with_mutator_1')
|
460
|
+
new_page_1.url.should == 'url_1'
|
461
|
+
end
|
462
|
+
|
463
|
+
it "should support periodic flushing" do
|
464
|
+
m = Page.open_mutator(0, 500)
|
465
|
+
p1 = Page.new({:ROW => 'created_with_mutator_1', :url => 'url_1'})
|
466
|
+
p1.save_with_mutator!(m)
|
467
|
+
|
468
|
+
lambda {p1.reload}.should raise_error(::ActiveRecord::RecordNotFound)
|
469
|
+
sleep 1
|
470
|
+
lambda {p1.reload}.should_not raise_error(::ActiveRecord::RecordNotFound)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
describe HyperBase, '.update' do
|
475
|
+
fixtures :pages
|
476
|
+
|
477
|
+
it "should update an object in hypertable" do
|
478
|
+
p = Page.find(:first)
|
479
|
+
p.class.should == Page
|
480
|
+
original_url = p.url.clone
|
481
|
+
p.url = "new url"
|
482
|
+
p.update.should be_true
|
483
|
+
p.url.should == "new url"
|
484
|
+
p.reload.should == p
|
485
|
+
p.url.should == "new url"
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
describe HyperBase, '.destroy' do
|
490
|
+
fixtures :pages
|
491
|
+
|
492
|
+
it "should remove an object from hypertable" do
|
493
|
+
p = Page.find(:first)
|
494
|
+
p.reload.should == p
|
495
|
+
p.destroy
|
496
|
+
lambda {p.reload}.should raise_error(::ActiveRecord::RecordNotFound)
|
497
|
+
end
|
498
|
+
|
499
|
+
it "should remove an object from hypertable based on the id" do
|
500
|
+
p = Page.find(:first)
|
501
|
+
p.reload.should == p
|
502
|
+
Page.destroy(p.ROW)
|
503
|
+
lambda {p.reload}.should raise_error(::ActiveRecord::RecordNotFound)
|
504
|
+
end
|
505
|
+
|
506
|
+
it "should remove multiple objects from hypertable based on ids" do
|
507
|
+
pages = Page.find(:all)
|
508
|
+
pages.length.should == 2
|
509
|
+
Page.destroy(pages.map{|p| p.ROW})
|
510
|
+
pages = Page.find(:all)
|
511
|
+
pages.length.should == 0
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
describe HyperBase, '.delete' do
|
516
|
+
fixtures :pages
|
517
|
+
|
518
|
+
it "should remove an object from hypertable based on the id" do
|
519
|
+
p = Page.find(:first)
|
520
|
+
p.reload.should == p
|
521
|
+
Page.delete(p.ROW)
|
522
|
+
lambda {p.reload}.should raise_error(::ActiveRecord::RecordNotFound)
|
523
|
+
end
|
524
|
+
|
525
|
+
it "should remove multiple objects from hypertable based on ids" do
|
526
|
+
pages = Page.find(:all)
|
527
|
+
pages.length.should == 2
|
528
|
+
Page.delete(pages.map{|p| p.ROW})
|
529
|
+
pages = Page.find(:all)
|
530
|
+
pages.length.should == 0
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
describe HyperBase, '.exists' do
|
535
|
+
fixtures :pages
|
536
|
+
|
537
|
+
it "should confirm that a record exists" do
|
538
|
+
p = Page.find(:first)
|
539
|
+
Page.exists?(p.ROW).should be_true
|
540
|
+
end
|
541
|
+
|
542
|
+
it "should refute that a record does not exists" do
|
543
|
+
Page.exists?('foofooofoofoofoofoomonkey').should be_false
|
544
|
+
end
|
545
|
+
|
546
|
+
it "should not support arguments that are not numbers, strings or hashes" do
|
547
|
+
lambda {Page.exists?([1])}.should raise_error
|
548
|
+
end
|
549
|
+
|
550
|
+
it "should not allow a Hash argument for conditions" do
|
551
|
+
lambda {
|
552
|
+
Page.exists?(:name => 'ESPN').should be_true
|
553
|
+
}.should raise_error
|
554
|
+
|
555
|
+
|
556
|
+
lambda {
|
557
|
+
Page.find(:first, :conditions => {:name => 'ESPN'}).should_not be_nil
|
558
|
+
}.should raise_error
|
559
|
+
|
560
|
+
lambda {
|
561
|
+
Page.exists?(:name => 'foofoofoofoofoo').should be_false
|
562
|
+
}.should raise_error
|
563
|
+
|
564
|
+
lambda {
|
565
|
+
Page.find(:first, :conditions => {:name => 'foofoofoofoofoo'}).should be_nil
|
566
|
+
}.should raise_error
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
describe HyperBase, '.increment' do
|
571
|
+
fixtures :pages
|
572
|
+
|
573
|
+
it "should increment an integer value" do
|
574
|
+
p = Page.find(:first)
|
575
|
+
p.name = 7
|
576
|
+
p.save
|
577
|
+
p.reload
|
578
|
+
p.increment('name')
|
579
|
+
p.name.should == 8
|
580
|
+
p.save
|
581
|
+
p.reload
|
582
|
+
p.name.should == "8"
|
583
|
+
p.increment('name', 2)
|
584
|
+
p.save
|
585
|
+
p.reload
|
586
|
+
p.name.should == "10"
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
describe HyperBase, '.increment!' do
|
591
|
+
fixtures :pages
|
592
|
+
|
593
|
+
it "should increment an integer value and save" do
|
594
|
+
p = Page.find(:first)
|
595
|
+
p.name = 7
|
596
|
+
p.increment!('name')
|
597
|
+
p.name.should == 8
|
598
|
+
p.reload
|
599
|
+
p.name.should == "8"
|
600
|
+
p.increment!('name', 2)
|
601
|
+
p.reload
|
602
|
+
p.name.should == "10"
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
describe HyperBase, '.decrement' do
|
607
|
+
fixtures :pages
|
608
|
+
|
609
|
+
it "should decrement an integer value" do
|
610
|
+
p = Page.find(:first)
|
611
|
+
p.name = 7
|
612
|
+
p.save
|
613
|
+
p.reload
|
614
|
+
p.decrement('name')
|
615
|
+
p.name.should == 6
|
616
|
+
p.save
|
617
|
+
p.reload
|
618
|
+
p.name.should == "6"
|
619
|
+
p.decrement('name', 2)
|
620
|
+
p.save
|
621
|
+
p.reload
|
622
|
+
p.name.should == "4"
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
describe HyperBase, '.decrement!' do
|
627
|
+
fixtures :pages
|
628
|
+
|
629
|
+
it "should decrement an integer value and save" do
|
630
|
+
p = Page.find(:first)
|
631
|
+
p.name = 7
|
632
|
+
p.decrement!('name')
|
633
|
+
p.name.should == 6
|
634
|
+
p.reload
|
635
|
+
p.name.should == "6"
|
636
|
+
p.decrement!('name', 2)
|
637
|
+
p.reload
|
638
|
+
p.name.should == "4"
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
describe HyperBase, '.update_attribute' do
|
643
|
+
fixtures :pages
|
644
|
+
|
645
|
+
it "should allow a single attribute to be updated" do
|
646
|
+
p = Page.find(:first)
|
647
|
+
p.update_attribute(:name, 'new name value')
|
648
|
+
p.name.should == 'new name value'
|
649
|
+
p.reload
|
650
|
+
p.name.should == 'new name value'
|
651
|
+
end
|
652
|
+
|
653
|
+
it "should save changes to more than the named column because that's the way activerecord works" do
|
654
|
+
p = Page.find(:first)
|
655
|
+
p.name = "name"
|
656
|
+
p.url = "url"
|
657
|
+
p.save!
|
658
|
+
p.url = "new url"
|
659
|
+
p.update_attribute(:name, 'new name value')
|
660
|
+
p.name.should == 'new name value'
|
661
|
+
p.url.should == 'new url'
|
662
|
+
p.reload
|
663
|
+
p.name.should == 'new name value'
|
664
|
+
p.url.should == 'new url'
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
describe HyperBase, '.update_attributes' do
|
669
|
+
fixtures :pages
|
670
|
+
|
671
|
+
it "should allow multiple attributes to be updated" do
|
672
|
+
p = Page.find(:first)
|
673
|
+
p.update_attributes({:name => 'new name value', :url => 'http://new/'})
|
674
|
+
p.name.should == 'new name value'
|
675
|
+
p.url.should == 'http://new/'
|
676
|
+
p.reload
|
677
|
+
p.name.should == 'new name value'
|
678
|
+
p.url.should == 'http://new/'
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
describe HyperBase, '.attributes' do
|
683
|
+
fixtures :pages
|
684
|
+
|
685
|
+
describe '.attributes_with_quotes' do
|
686
|
+
it "should return attributes in expected format" do
|
687
|
+
p = Page.find(:first)
|
688
|
+
attrs = p.attributes_with_quotes
|
689
|
+
attrs.keys.sort.should == ["ROW", "name", "url"]
|
690
|
+
attrs['ROW'].should == 'page_1'
|
691
|
+
attrs['name'].should == 'LOLcats and more'
|
692
|
+
attrs['url'].should == 'http://www.icanhascheezburger.com'
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
describe '.attributes_from_column_definition' do
|
697
|
+
fixtures :pages, :qualified_pages
|
698
|
+
|
699
|
+
it "should return attributes in expected format for scalar columns" do
|
700
|
+
p = Page.find(:first)
|
701
|
+
attrs = p.send(:attributes_from_column_definition)
|
702
|
+
attrs.should == {"name"=>"", "url"=>""}
|
703
|
+
end
|
704
|
+
|
705
|
+
it "should return attributes in expected format for qualified columns" do
|
706
|
+
qp = QualifiedPage.new
|
707
|
+
qp.new_record?.should be_true
|
708
|
+
qp.misc['name'] = 'new page'
|
709
|
+
qp.misc['url']= 'new.com'
|
710
|
+
qp.ROW = 'new_qualified_page'
|
711
|
+
qp.save.should be_true
|
712
|
+
qp.reload
|
713
|
+
attrs = qp.send(:attributes_from_column_definition)
|
714
|
+
attrs.should == {"misc"=>{}, "misc2"=>{}}
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
describe '.attributes_from_column_definition' do
|
719
|
+
fixtures :pages, :qualified_pages
|
720
|
+
|
721
|
+
it "should accept hash assignment to qualified columns" do
|
722
|
+
qp = QualifiedPage.new
|
723
|
+
qp.ROW = 'new_page'
|
724
|
+
qp.new_record?.should be_true
|
725
|
+
value = {'name' => 'new page', 'url' => 'new.com'}
|
726
|
+
qp.misc = value
|
727
|
+
qp.misc.should == value
|
728
|
+
qp.save.should be_true
|
729
|
+
qp.reload
|
730
|
+
qp.misc.should == value
|
731
|
+
qp.misc['another_key'] = "1"
|
732
|
+
qp.misc.should == value.merge({'another_key' => "1"})
|
733
|
+
qp.save.should be_true
|
734
|
+
qp.reload
|
735
|
+
qp.misc.should == value.merge({'another_key' => "1"})
|
736
|
+
end
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
describe HyperBase, '.scanner' do
|
741
|
+
fixtures :pages
|
742
|
+
|
743
|
+
it "should return a scanner object from open_scanner" do
|
744
|
+
scan_spec = Hypertable::ThriftGen::ScanSpec.new
|
745
|
+
scanner = Page.open_scanner(scan_spec)
|
746
|
+
scanner.class.should == Fixnum
|
747
|
+
Page.close_scanner(scanner)
|
748
|
+
end
|
749
|
+
|
750
|
+
it "should yield a scanner object from with_scanner" do
|
751
|
+
scan_spec = Hypertable::ThriftGen::ScanSpec.new
|
752
|
+
Page.with_scanner(scan_spec) do |scanner|
|
753
|
+
scanner.is_a?(Fixnum).should be_true
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
it "should yield a scanner object from with_scanner" do
|
758
|
+
scan_spec = Hypertable::ThriftGen::ScanSpec.new
|
759
|
+
Page.with_scanner(scan_spec) do |scanner|
|
760
|
+
scanner.is_a?(Fixnum).should be_true
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
it "should support native each_cell scanner method" do
|
765
|
+
scan_spec = Hypertable::ThriftGen::ScanSpec.new
|
766
|
+
cell_count = 0
|
767
|
+
Page.with_scanner(scan_spec) do |scanner|
|
768
|
+
Page.each_cell(scanner) do |cell|
|
769
|
+
cell.is_a?(Hypertable::ThriftGen::Cell).should be_true
|
770
|
+
cell_count += 1
|
771
|
+
end
|
772
|
+
end
|
773
|
+
cell_count.should == 4
|
774
|
+
end
|
775
|
+
|
776
|
+
it "should support native each_cell_as_arrays scanner method" do
|
777
|
+
scan_spec = Hypertable::ThriftGen::ScanSpec.new
|
778
|
+
cell_count = 0
|
779
|
+
Page.with_scanner(scan_spec) do |scanner|
|
780
|
+
Page.each_cell_as_arrays(scanner) do |cell|
|
781
|
+
cell.is_a?(Array).should be_true
|
782
|
+
cell_count += 1
|
783
|
+
end
|
784
|
+
end
|
785
|
+
cell_count.should == 4
|
786
|
+
end
|
787
|
+
|
788
|
+
it "should return a scan spec from find_to_scan_spec" do
|
789
|
+
scan_spec = Page.find_to_scan_spec(:all, :limit => 1)
|
790
|
+
scan_spec.is_a?(Hypertable::ThriftGen::ScanSpec).should be_true
|
791
|
+
scan_spec.row_limit.should == 1
|
792
|
+
end
|
793
|
+
|
794
|
+
it "should yield a scanner to a block from find_with_scanner" do
|
795
|
+
cell_count = 0
|
796
|
+
Page.find_with_scanner(:all, :limit => 1) do |scanner|
|
797
|
+
scanner.is_a?(Fixnum).should be_true
|
798
|
+
Page.each_cell_as_arrays(scanner) do |cell|
|
799
|
+
cell.is_a?(Array).should be_true
|
800
|
+
cell_count += 1
|
801
|
+
end
|
802
|
+
end
|
803
|
+
cell_count.should == 2
|
804
|
+
end
|
805
|
+
|
806
|
+
it "should yield each row when calling find_each_row_as_arrays" do
|
807
|
+
cell_count = 0
|
808
|
+
row_count = 0
|
809
|
+
|
810
|
+
Page.find_each_row_as_arrays(:all) do |row|
|
811
|
+
row.is_a?(Array).should be_true
|
812
|
+
row_count += 1
|
813
|
+
cell_count += row.length
|
814
|
+
end
|
815
|
+
|
816
|
+
row_count.should == 2
|
817
|
+
cell_count.should == 4
|
818
|
+
end
|
819
|
+
|
820
|
+
it "should convert an array of cells into a hash" do
|
821
|
+
Page.find_each_row_as_arrays(:all, :limit => 1) do |row|
|
822
|
+
page_hash = Page.convert_cells_to_hashes(row).first
|
823
|
+
page_hash.is_a?(Hash).should be_true
|
824
|
+
page_hash['ROW'].should == "page_1"
|
825
|
+
page_hash['name'].should == "LOLcats and more"
|
826
|
+
page_hash['url'].should == "http://www.icanhascheezburger.com"
|
827
|
+
end
|
828
|
+
end
|
829
|
+
|
830
|
+
it "should yield each row as a HyperRecord object when calling find_each_row" do
|
831
|
+
row_count = 0
|
832
|
+
|
833
|
+
Page.find_each_row(:all) do |row|
|
834
|
+
row.is_a?(Page).should be_true
|
835
|
+
row_count += 1
|
836
|
+
end
|
837
|
+
|
838
|
+
row_count.should == 2
|
839
|
+
end
|
840
|
+
|
841
|
+
it "should support native each_row scanner method"
|
842
|
+
it "should support native each_row_as_arrays scanner method"
|
843
|
+
end
|
844
|
+
|
845
|
+
describe HyperBase, '.row_key_attributes' do
|
846
|
+
it "should assemble a row key in the order that matches row key attributes" do
|
847
|
+
Page.class_eval do
|
848
|
+
row_key_attributes :regex => /^(\w+)_(\d+)_(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})$/, :attribute_names => [:type_prefix, :identifier, :timestamp]
|
849
|
+
end
|
850
|
+
|
851
|
+
Page.assemble_row_key_from_attributes({
|
852
|
+
:type_prefix => 'prefix',
|
853
|
+
:identifier => 12,
|
854
|
+
:timestamp => '2009-11-05_00:00'
|
855
|
+
}).should == 'prefix_12_2009-11-05_00:00'
|
856
|
+
|
857
|
+
Page.class_eval do
|
858
|
+
row_key_attributes :regex => /^(\w+)_(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})_(\d+)$/, :attribute_names => [:type_prefix, :timestamp, :identifier]
|
859
|
+
end
|
860
|
+
|
861
|
+
Page.assemble_row_key_from_attributes({
|
862
|
+
:type_prefix => 'prefix',
|
863
|
+
:identifier => 12,
|
864
|
+
:timestamp => '2009-11-05_00:00'
|
865
|
+
}).should == 'prefix_2009-11-05_00:00_12'
|
866
|
+
end
|
867
|
+
|
868
|
+
it "should extract attributes out of the row key" do
|
869
|
+
Page.class_eval do
|
870
|
+
row_key_attributes :regex => /_(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})$/, :attribute_names => [:timestamp]
|
871
|
+
end
|
872
|
+
|
873
|
+
p = Page.new
|
874
|
+
p.ROW = "apikey_1066_2008-12-25_03:00"
|
875
|
+
p.timestamp.should == '2008-12-25_03:00'
|
876
|
+
end
|
877
|
+
|
878
|
+
it "should return empty string if regex doesn't match row key" do
|
879
|
+
Page.class_eval do
|
880
|
+
row_key_attributes :regex => /will_not_match/, :attribute_names => [:foo]
|
881
|
+
end
|
882
|
+
|
883
|
+
p = Page.new
|
884
|
+
p.ROW = "row key"
|
885
|
+
p.foo.should == ''
|
886
|
+
end
|
887
|
+
|
888
|
+
it "should allow multiple attributes to be extracted from row key" do
|
889
|
+
Page.class_eval do
|
890
|
+
row_key_attributes :regex => /^sponsorship_([a-z0-9]+)_(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})_(\d+)$/, :attribute_names => [:sponsorship_id, :timestamp, :partner_id]
|
891
|
+
end
|
892
|
+
|
893
|
+
p = Page.new
|
894
|
+
p.ROW = "sponsorship_61066_2009-04-12_07:00_166"
|
895
|
+
p.sponsorship_id.should == '61066'
|
896
|
+
p.timestamp.should == '2009-04-12_07:00'
|
897
|
+
p.partner_id.should == '166'
|
898
|
+
end
|
899
|
+
|
900
|
+
it "should return empty string on partial match" do
|
901
|
+
Page.class_eval do
|
902
|
+
row_key_attributes :regex => /^sponsorship_([a-z0-9]+)_(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})_?(\d+)?$/, :attribute_names => [:sponsorship_id, :timestamp, :partner_id]
|
903
|
+
end
|
904
|
+
|
905
|
+
p = Page.new
|
906
|
+
p.ROW = "sponsorship_61066_2009-04-12_07:00"
|
907
|
+
p.sponsorship_id.should == '61066'
|
908
|
+
p.timestamp.should == '2009-04-12_07:00'
|
909
|
+
p.partner_id.should == ''
|
910
|
+
end
|
911
|
+
|
912
|
+
it "should return empty string on partial match in middle" do
|
913
|
+
Page.class_eval do
|
914
|
+
row_key_attributes :regex => /^sponsorship_([a-z0-9]+)_?(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})?_(\d+)$/, :attribute_names => [:sponsorship_id, :timestamp, :partner_id]
|
915
|
+
end
|
916
|
+
|
917
|
+
p = Page.new
|
918
|
+
p.ROW = "sponsorship_61066_166"
|
919
|
+
p.sponsorship_id.should == '61066'
|
920
|
+
p.timestamp.should == ''
|
921
|
+
p.partner_id.should == '166'
|
922
|
+
end
|
923
|
+
|
924
|
+
it "should return empty string on nil ROW key" do
|
925
|
+
Page.class_eval do
|
926
|
+
row_key_attributes :regex => /will_not_match/, :attribute_names => [:foo]
|
927
|
+
end
|
928
|
+
|
929
|
+
p = Page.new
|
930
|
+
p.ROW.should be_nil
|
931
|
+
p.foo.should == ''
|
932
|
+
end
|
933
|
+
|
934
|
+
it "should return correct value even if the ROW key is changed" do
|
935
|
+
Page.class_eval do
|
936
|
+
row_key_attributes :regex => /_(\d{4}-\d{2}-\d{2}_\d{2}:\d{2})$/, :attribute_names => [:timestamp]
|
937
|
+
end
|
938
|
+
|
939
|
+
p = Page.new
|
940
|
+
p.ROW = "apikey_1066_2008-12-25_03:00"
|
941
|
+
p.timestamp.should == '2008-12-25_03:00'
|
942
|
+
p.ROW = "apikey_1066_2008-12-25_12:00"
|
943
|
+
p.timestamp.should == '2008-12-25_12:00'
|
944
|
+
end
|
945
|
+
end
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|