sql_partitioner 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +61 -0
- data/.rspec +3 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +119 -0
- data/Rakefile +6 -0
- data/lib/sql_partitioner/adapters/ar_adapter.rb +39 -0
- data/lib/sql_partitioner/adapters/base_adapter.rb +18 -0
- data/lib/sql_partitioner/adapters/dm_adapter.rb +24 -0
- data/lib/sql_partitioner/base_partitions_manager.rb +231 -0
- data/lib/sql_partitioner/loader.rb +16 -0
- data/lib/sql_partitioner/lock_wait_timeout_handler.rb +17 -0
- data/lib/sql_partitioner/partition.rb +158 -0
- data/lib/sql_partitioner/partitions_manager.rb +128 -0
- data/lib/sql_partitioner/sql_helper.rb +122 -0
- data/lib/sql_partitioner/time_unit_converter.rb +92 -0
- data/lib/sql_partitioner/version.rb +3 -0
- data/lib/sql_partitioner.rb +14 -0
- data/spec/db_conf.yml +7 -0
- data/spec/lib/spec_helper.rb +52 -0
- data/spec/lib/sql_partitioner/adapters/adapters_spec.rb +82 -0
- data/spec/lib/sql_partitioner/adapters/base_adapter_spec.rb +28 -0
- data/spec/lib/sql_partitioner/base_partitions_manager_spec.rb +442 -0
- data/spec/lib/sql_partitioner/loader_spec.rb +12 -0
- data/spec/lib/sql_partitioner/lock_wait_timeout_spec.rb +136 -0
- data/spec/lib/sql_partitioner/partition_spec.rb +268 -0
- data/spec/lib/sql_partitioner/partitions_manager_spec.rb +275 -0
- data/spec/lib/sql_partitioner/sql_helper_spec.rb +43 -0
- data/spec/lib/sql_partitioner/time_unit_converter_spec.rb +91 -0
- data/sql_partitioner.gemspec +47 -0
- metadata +234 -0
@@ -0,0 +1,442 @@
|
|
1
|
+
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
shared_examples_for "BasePartitionsManager with an adapter" do
|
4
|
+
describe "#initialize_partitioning" do
|
5
|
+
before(:each) do
|
6
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
7
|
+
:adapter => adapter,
|
8
|
+
:current_time => Time.utc(2014,04,18),
|
9
|
+
:table_name => 'test_events',
|
10
|
+
:logger => SPEC_LOGGER
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with some partitions passed" do
|
15
|
+
it "should create the future partition and the partition specified" do
|
16
|
+
@partition_manager.initialize_partitioning({'until_2014_03_17' => 1395014400})
|
17
|
+
SqlPartitioner::Partition.all(adapter, 'test_events').map{|p| [p.name, p.timestamp]}.should == [
|
18
|
+
["until_2014_03_17", 1395014400],
|
19
|
+
["future", "MAXVALUE"]
|
20
|
+
]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with no partition passed" do
|
25
|
+
it "should create the future partition" do
|
26
|
+
@partition_manager.initialize_partitioning({})
|
27
|
+
SqlPartitioner::Partition.all(adapter, 'test_events').map{|p| [p.name, p.timestamp]}.should == [
|
28
|
+
["future", "MAXVALUE"]
|
29
|
+
]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#reorg_future_partition" do
|
35
|
+
before(:each) do
|
36
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
37
|
+
:adapter => adapter,
|
38
|
+
:current_time => Time.utc(2014,04,18),
|
39
|
+
:table_name => 'test_events',
|
40
|
+
:logger => SPEC_LOGGER
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
context "without the future partition passed" do
|
45
|
+
it "should add the future partition and reorganize it" do
|
46
|
+
@partition_manager.initialize_partitioning({})
|
47
|
+
|
48
|
+
@partition_manager.reorg_future_partition({'until_2014_03_17' => 1395014400})
|
49
|
+
SqlPartitioner::Partition.all(adapter, 'test_events').map{|p| [p.name, p.timestamp]}.should == [
|
50
|
+
["until_2014_03_17", 1395014400],
|
51
|
+
["future", "MAXVALUE"]
|
52
|
+
]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#_execute_and_display_partition_info" do
|
58
|
+
before(:each) do
|
59
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
60
|
+
:adapter => adapter,
|
61
|
+
:current_time => Time.utc(2014,04,18),
|
62
|
+
:table_name => 'test_events',
|
63
|
+
:logger => SPEC_LOGGER
|
64
|
+
)
|
65
|
+
end
|
66
|
+
context "with no sql statement to be executed" do
|
67
|
+
it "should return false" do
|
68
|
+
expect(@partition_manager.send(:_execute_and_display_partition_info, nil)).to be false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
context "with sql statement to be executed" do
|
72
|
+
before(:each) do
|
73
|
+
@sql_statement = "SELECT database()"
|
74
|
+
end
|
75
|
+
context "and dry_run == true" do
|
76
|
+
before(:each) do
|
77
|
+
@dry_run = true
|
78
|
+
end
|
79
|
+
it "should return the sql statement" do
|
80
|
+
expect(@partition_manager.send(:_execute_and_display_partition_info, @sql_statement, @dry_run)).to be @sql_statement
|
81
|
+
end
|
82
|
+
end
|
83
|
+
context "and dry_run == false" do
|
84
|
+
before(:each) do
|
85
|
+
@dry_run = false
|
86
|
+
end
|
87
|
+
it "should execute the sql statement & return true" do
|
88
|
+
expect(@partition_manager.send(:_execute_and_display_partition_info, @sql_statement, @dry_run)).to be true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#drop_partitions" do
|
95
|
+
before(:each) do
|
96
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
97
|
+
:adapter => adapter,
|
98
|
+
:current_time => Time.utc(2014,04,16),
|
99
|
+
:table_name => 'test_events',
|
100
|
+
:logger => SPEC_LOGGER
|
101
|
+
)
|
102
|
+
|
103
|
+
@partitions = {'until_2014_03_17' => 1395014400, 'until_2014_04_17' => 1397692800}
|
104
|
+
@partition_manager.initialize_partitioning(@partitions)
|
105
|
+
end
|
106
|
+
|
107
|
+
context "with an attempt to drop a regular partition" do
|
108
|
+
before(:each) do
|
109
|
+
@partition_to_drop = {'until_2014_03_17' => 1395014400}
|
110
|
+
end
|
111
|
+
it "should drop the partition specified" do
|
112
|
+
@partition_manager.drop_partitions(@partition_to_drop.keys)
|
113
|
+
SqlPartitioner::Partition.all(adapter, 'test_events').map{|p| [p.name, p.timestamp]}.should == [
|
114
|
+
['until_2014_04_17', 1397692800],
|
115
|
+
["future", "MAXVALUE"]
|
116
|
+
]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
context "with an attempt to drop the current partition" do
|
120
|
+
before(:each) do
|
121
|
+
@partition_to_drop = {'until_2014_04_17' => 1397692800}
|
122
|
+
end
|
123
|
+
it "should fail to drop the current partition" do
|
124
|
+
lambda do
|
125
|
+
@partition_manager.drop_partitions(@partition_to_drop.keys)
|
126
|
+
end.should raise_error(ArgumentError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
context "with an attempt to drop the future partition" do
|
130
|
+
before(:each) do
|
131
|
+
@partition_to_drop = {SqlPartitioner::Partition::FUTURE_PARTITION_NAME => SqlPartitioner::Partition::FUTURE_PARTITION_VALUE}
|
132
|
+
end
|
133
|
+
it "should fail to drop the future partition" do
|
134
|
+
lambda do
|
135
|
+
@partition_manager.drop_partitions(@partition_to_drop.keys)
|
136
|
+
end.should raise_error(ArgumentError)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
context "with an attempt to drop a non-existing partition" do
|
140
|
+
before(:each) do
|
141
|
+
@partition_to_drop = {'until_2014_02_17' => 1392595200}
|
142
|
+
end
|
143
|
+
it "should fail to drop the non-existent partition" do
|
144
|
+
lambda do
|
145
|
+
@partition_manager.drop_partitions(@partition_to_drop.keys)
|
146
|
+
end.should raise_error(statement_invalid_exception_class)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "#reorg_future_partition" do
|
152
|
+
before(:each) do
|
153
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
154
|
+
:adapter => adapter,
|
155
|
+
:current_time => Time.utc(2014,04,16),
|
156
|
+
:table_name => 'test_events',
|
157
|
+
:logger => SPEC_LOGGER
|
158
|
+
)
|
159
|
+
|
160
|
+
@partitions = {'until_2014_03_17' => 1395014400, 'until_2014_04_17' => 1397692800}
|
161
|
+
@partition_manager.initialize_partitioning(@partitions)
|
162
|
+
end
|
163
|
+
|
164
|
+
context "with reorganizing the future partition into a partition with timestamp < existing partition" do
|
165
|
+
before(:each) do
|
166
|
+
@partition_to_reorg = {'until_2014_02_17' => 1392595200}
|
167
|
+
end
|
168
|
+
it "should fail to reorganize the future partition" do
|
169
|
+
lambda do
|
170
|
+
@partition_manager.reorg_future_partition(@partition_to_reorg)
|
171
|
+
end.should raise_error(statement_invalid_exception_class)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
context "with reorganizing the future partition into one new partition with timestamp == existing partition" do
|
175
|
+
before(:each) do
|
176
|
+
@partition_to_reorg = {'until_2014_04_17' => 1397692800}
|
177
|
+
end
|
178
|
+
it "should fail to reorganize the future partition" do
|
179
|
+
lambda do
|
180
|
+
@partition_manager.reorg_future_partition(@partition_to_reorg)
|
181
|
+
end.should raise_error(statement_invalid_exception_class)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
context "with reorganizing the future partition into one new partition with timestamp > existing partition" do
|
185
|
+
before(:each) do
|
186
|
+
@partition_to_reorg = {'until_2014_05_17' => 1400284800}
|
187
|
+
end
|
188
|
+
it "should succeed in reorganizing the future partition" do
|
189
|
+
@partition_manager.reorg_future_partition(@partition_to_reorg)
|
190
|
+
SqlPartitioner::Partition.all(adapter, 'test_events').map{|p| [p.name, p.timestamp]}.should == [
|
191
|
+
['until_2014_03_17', 1395014400],
|
192
|
+
['until_2014_04_17', 1397692800],
|
193
|
+
['until_2014_05_17', 1400284800],
|
194
|
+
["future", "MAXVALUE"]
|
195
|
+
]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "BasePartitionsManager with ARAdapter" do
|
202
|
+
it_should_behave_like "BasePartitionsManager with an adapter" do
|
203
|
+
let(:statement_invalid_exception_class) do
|
204
|
+
ActiveRecord::StatementInvalid
|
205
|
+
end
|
206
|
+
let(:adapter) do
|
207
|
+
SqlPartitioner::ARAdapter.new(ActiveRecord::Base.connection)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# TODO: Find a way to make this test also work for DM
|
212
|
+
describe "#_execute" do
|
213
|
+
before(:each) do
|
214
|
+
@options = {
|
215
|
+
:adapter => SqlPartitioner::ARAdapter.new(ActiveRecord::Base.connection),
|
216
|
+
:current_time => Time.utc(2014,04,18),
|
217
|
+
:table_name => 'test_events',
|
218
|
+
:logger => SPEC_LOGGER
|
219
|
+
}
|
220
|
+
@sql_statement = "SELECT @@local.lock_wait_timeout AS lock_wait_timeout"
|
221
|
+
end
|
222
|
+
|
223
|
+
context "with a timeout" do
|
224
|
+
before(:each) do
|
225
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
226
|
+
@options.merge(:lock_wait_timeout => 1)
|
227
|
+
)
|
228
|
+
end
|
229
|
+
it "should return the result after changing lock_wait_timeout" do
|
230
|
+
result = @partition_manager.send(:_execute, @sql_statement)
|
231
|
+
result.each_hash{|hash| hash["lock_wait_timeout"].should == "1"}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
context "with no timeout" do
|
235
|
+
before(:each) do
|
236
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(@options)
|
237
|
+
end
|
238
|
+
it "should return the result without changing lock_wait_timeout" do
|
239
|
+
result = @partition_manager.send(:_execute, @sql_statement)
|
240
|
+
result.each_hash{|hash| hash["lock_wait_timeout"].should == "31536000" }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe "BasePartitionsManager with DM Adapter" do
|
247
|
+
it_should_behave_like "BasePartitionsManager with an adapter" do
|
248
|
+
let(:statement_invalid_exception_class) do
|
249
|
+
DataObjects::SQLError
|
250
|
+
end
|
251
|
+
let(:adapter) do
|
252
|
+
SqlPartitioner::DMAdapter.new(DataMapper.repository.adapter)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe "BasePartitionsManager" do
|
258
|
+
before(:each) do
|
259
|
+
@adapter = Struct.new(:schema_name).new("sql_partitioner_test")
|
260
|
+
|
261
|
+
@partition_manager = SqlPartitioner::BasePartitionsManager.new(
|
262
|
+
:adapter => @adapter,
|
263
|
+
:current_time => Time.utc(2014,04,18),
|
264
|
+
:table_name => 'test_events',
|
265
|
+
:logger => SPEC_LOGGER
|
266
|
+
)
|
267
|
+
end
|
268
|
+
|
269
|
+
describe "#log" do
|
270
|
+
before(:each) do
|
271
|
+
@to_log = "Log this!"
|
272
|
+
end
|
273
|
+
it "should not log a prefix with no prefix" do
|
274
|
+
@partition_manager.logger.should_receive(:info).with(@to_log)
|
275
|
+
@partition_manager.log(@to_log, prefix = false)
|
276
|
+
end
|
277
|
+
it "should log a prefix with a prefix" do
|
278
|
+
@partition_manager.logger.should_receive(:info).with("[SqlPartitioner::BasePartitionsManager]#{@to_log}")
|
279
|
+
@partition_manager.log(@to_log, prefix = true)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#_validate_partition_data" do
|
284
|
+
context "when input is not valid" do
|
285
|
+
it "should raise error when future partition is not pointing to proper value" do
|
286
|
+
lambda {
|
287
|
+
@partition_manager.send(:_validate_partition_data, {
|
288
|
+
SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_NAME => 1
|
289
|
+
}
|
290
|
+
)
|
291
|
+
}.should raise_error(ArgumentError, /future partition name/)
|
292
|
+
end
|
293
|
+
it "should raise error when non-future-partition is pointing to future-partition value" do
|
294
|
+
lambda {
|
295
|
+
@partition_manager.send(:_validate_partition_data, {
|
296
|
+
"name" => SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_VALUE
|
297
|
+
}
|
298
|
+
)
|
299
|
+
}.should raise_error(ArgumentError, /future partition name/)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
context "when input is valid" do
|
303
|
+
it "should return true" do
|
304
|
+
expect(@partition_manager.send(:_validate_partition_data, {
|
305
|
+
SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_NAME =>
|
306
|
+
SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_VALUE
|
307
|
+
}
|
308
|
+
)).to be true
|
309
|
+
expect(@partition_manager.send(:_validate_partition_data, { "any_string" => 123 })).to be true
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe "#_validate_positive_fixnum" do
|
315
|
+
context "when input is not valid" do
|
316
|
+
it "should raise error with a String" do
|
317
|
+
lambda {
|
318
|
+
@partition_manager.send(:_validate_positive_fixnum, :timestamp, 'H')
|
319
|
+
}.should raise_error(ArgumentError, /expected to be Fixnum but instead was String/)
|
320
|
+
end
|
321
|
+
it "should raise error with nil" do
|
322
|
+
lambda {
|
323
|
+
@partition_manager.send(:_validate_positive_fixnum, :timestamp, nil)
|
324
|
+
}.should raise_error(ArgumentError, /expected to be Fixnum but instead was NilClass/)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "should raise error with negative integer" do
|
328
|
+
lambda {
|
329
|
+
@partition_manager.send(:_validate_positive_fixnum, :timestamp, -1)
|
330
|
+
}.should raise_error(ArgumentError, /timestamp should be > 0/)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
context "when input is valid" do
|
334
|
+
it "should return true" do
|
335
|
+
expect(@partition_manager.send(:_validate_positive_fixnum, :timestamp, 10)).to be true
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe "#_validate_class" do
|
341
|
+
context "when class is not correct" do
|
342
|
+
it "should raise error when a String provided when Integer expected" do
|
343
|
+
lambda {
|
344
|
+
@partition_manager.send(:_validate_class, :timestamp, 'string', Integer)
|
345
|
+
}.should raise_error(ArgumentError, /expected to be Integer but instead was String/)
|
346
|
+
end
|
347
|
+
it "should raise error when nil provided when Fixnum expected" do
|
348
|
+
lambda {
|
349
|
+
@partition_manager.send(:_validate_class, :timestamp, nil, Fixnum)
|
350
|
+
}.should raise_error(ArgumentError, /expected to be Fixnum but instead was NilClass/)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
context "when class is as expected" do
|
354
|
+
it "should return true" do
|
355
|
+
expect(@partition_manager.send(:_validate_class, :timestamp, 'example', String)).to be true
|
356
|
+
expect(@partition_manager.send(:_validate_class, :timestamp, {:a => 1}, Hash)).to be true
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
describe "#_validate_timestamp" do
|
362
|
+
it "should return true if the future partition value is passed" do
|
363
|
+
@partition_manager.send(:_validate_timestamp, SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_VALUE)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe "#_validate_partition_name" do
|
368
|
+
context "when input is not valid" do
|
369
|
+
it "should raise error when not String" do
|
370
|
+
lambda {
|
371
|
+
@partition_manager.send(:_validate_partition_name, 1)
|
372
|
+
}.should raise_error(ArgumentError, /expected to be String/)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
context "when input is valid" do
|
376
|
+
it "should return true" do
|
377
|
+
expect(@partition_manager.send(:_validate_partition_name, "bar")).to be true
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe "#_validate_partition_names" do
|
383
|
+
context "when input is not valid" do
|
384
|
+
it "should raise error when not an array is passed" do
|
385
|
+
lambda {
|
386
|
+
@partition_manager.send(:_validate_partition_names, {})
|
387
|
+
}.should raise_error(ArgumentError, /expected to be Array/)
|
388
|
+
end
|
389
|
+
it "should raise error when not an array of strings is passed" do
|
390
|
+
lambda {
|
391
|
+
@partition_manager.send(:_validate_partition_names, [123])
|
392
|
+
}.should raise_error(ArgumentError, /expected to be String/)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
context "when input is valid" do
|
396
|
+
it "should return true" do
|
397
|
+
expect(@partition_manager.send(:_validate_partition_names, ["bar"])).to be true
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
describe "#_validate_partition_names_allowed_to_drop" do
|
403
|
+
before(:each) do
|
404
|
+
@current_partition = Struct.new(:partition_name, :partition_description).new("current", @partition_manager.current_timestamp.to_i + 1)
|
405
|
+
@adapter.should_receive(:select).and_return([@current_partition])
|
406
|
+
end
|
407
|
+
context "when input is not valid" do
|
408
|
+
it "should raise error with name of the future partition" do
|
409
|
+
lambda {
|
410
|
+
@partition_manager.send(:_validate_partition_names_allowed_to_drop, ["future"])
|
411
|
+
}.should raise_error(ArgumentError, /current and future partition can never be dropped/)
|
412
|
+
end
|
413
|
+
it "should raise error with name of current partition" do
|
414
|
+
lambda {
|
415
|
+
@partition_manager.send(:_validate_partition_names_allowed_to_drop, [@current_partition.partition_name])
|
416
|
+
}.should raise_error(ArgumentError, /current and future partition can never be dropped/)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
context "when input is valid" do
|
420
|
+
it "should return true" do
|
421
|
+
expect(@partition_manager.send(:_validate_partition_names_allowed_to_drop, ["bar"])).to be true
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
describe "#name_from_timestamp" do
|
427
|
+
context "with future partition timestamp" do
|
428
|
+
it "should return future partition name" do
|
429
|
+
result = @partition_manager.send(:name_from_timestamp, SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_VALUE)
|
430
|
+
expect(result).to eq(SqlPartitioner::BasePartitionsManager::FUTURE_PARTITION_NAME)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
context "with a partition timestamp" do
|
434
|
+
it "should return future partition name" do
|
435
|
+
ts = Time.utc(2014, 01, 15)
|
436
|
+
|
437
|
+
result = @partition_manager.send(:name_from_timestamp, ts.to_i)
|
438
|
+
expect(result).to eq("until_2014_01_15")
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe "Loader" do
|
4
|
+
describe "#require_or_skip" do
|
5
|
+
it "should call require & return true if a related constant is defined" do
|
6
|
+
expect(SqlPartitioner::Loader.require_or_skip('sql_partitioner/adapters/ar_adapter', 'ActiveRecord')). to be true
|
7
|
+
end
|
8
|
+
it "should not call require & return false if a related constant is not defined" do
|
9
|
+
expect(SqlPartitioner::Loader.require_or_skip('gratin/potato_peeler', 'Potatoes')). to be false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require File.expand_path("../spec_helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
shared_examples_for "LockWaitTimeoutHandler" do
|
4
|
+
describe ".with_lock_wait_timeout" do
|
5
|
+
before(:each) do
|
6
|
+
@with_lock_wait_timeout = 1
|
7
|
+
end
|
8
|
+
context "with a new lock_wait_timeout value" do
|
9
|
+
before(:each) do
|
10
|
+
@orig_local_lock_wait_timeout = adapter.select("SELECT @@local.lock_wait_timeout").first
|
11
|
+
@orig_global_lock_wait_timeout = adapter.select("SELECT @@global.lock_wait_timeout").first
|
12
|
+
|
13
|
+
@orig_local_lock_wait_timeout.should_not == @with_lock_wait_timeout
|
14
|
+
@orig_global_lock_wait_timeout.should_not == @with_lock_wait_timeout
|
15
|
+
end
|
16
|
+
it "should set and reset lock_wait_timeout" do
|
17
|
+
step = 0
|
18
|
+
SqlPartitioner::LockWaitTimeoutHandler.with_lock_wait_timeout(adapter, @with_lock_wait_timeout) do
|
19
|
+
adapter.select("SELECT @@local.lock_wait_timeout").first.to_s.should == @with_lock_wait_timeout.to_s
|
20
|
+
adapter.select("SELECT @@global.lock_wait_timeout").first.to_s.should_not == @with_lock_wait_timeout.to_s
|
21
|
+
|
22
|
+
adapter.execute("SELECT 1 FROM DUAL")
|
23
|
+
step += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
adapter.select("SELECT @@local.lock_wait_timeout").first.to_s.should == @orig_local_lock_wait_timeout.to_s
|
27
|
+
adapter.select("SELECT @@global.lock_wait_timeout").first.to_s.should == @orig_global_lock_wait_timeout.to_s
|
28
|
+
step.should == 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
context "with a second db connection" do
|
32
|
+
before(:each) do
|
33
|
+
@adapter_2 = get_adapter_2.call
|
34
|
+
end
|
35
|
+
after(:each) do
|
36
|
+
close_adapter_2_connection.call
|
37
|
+
end
|
38
|
+
context "and a new lock_wait_timeout getting set by the first db connection" do
|
39
|
+
before(:each) do
|
40
|
+
@orig_local_lock_wait_timeout = @adapter_2.select("SELECT @@local.lock_wait_timeout").first.to_s
|
41
|
+
@orig_global_lock_wait_timeout = @adapter_2.select("SELECT @@global.lock_wait_timeout").first.to_s
|
42
|
+
|
43
|
+
@orig_local_lock_wait_timeout.should_not == @with_lock_wait_timeout.to_s
|
44
|
+
@orig_global_lock_wait_timeout.should_not == @with_lock_wait_timeout.to_s
|
45
|
+
end
|
46
|
+
it "should not affect the lock_wait_timeout value of the second db connection" do
|
47
|
+
step = 0
|
48
|
+
SqlPartitioner::LockWaitTimeoutHandler.with_lock_wait_timeout(adapter, @with_lock_wait_timeout) do
|
49
|
+
@adapter_2.select("SELECT @@local.lock_wait_timeout").first.to_s.should_not == @with_lock_wait_timeout.to_s
|
50
|
+
@adapter_2.select("SELECT @@global.lock_wait_timeout").first.to_s.should_not == @with_lock_wait_timeout.to_s
|
51
|
+
|
52
|
+
adapter.execute("SELECT 1 FROM DUAL")
|
53
|
+
step += 1
|
54
|
+
end
|
55
|
+
step.should == 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "and the second db connection holding a lock" do
|
59
|
+
it "should timeout quickly" do
|
60
|
+
@adapter_2.execute("LOCK TABLE test_events WRITE")
|
61
|
+
|
62
|
+
step = 0
|
63
|
+
begin
|
64
|
+
lambda do
|
65
|
+
SqlPartitioner::LockWaitTimeoutHandler.with_lock_wait_timeout(adapter, @with_lock_wait_timeout) do
|
66
|
+
step += 1
|
67
|
+
adapter.execute("LOCK TABLE test_events WRITE")
|
68
|
+
end
|
69
|
+
end.should raise_error(sql_error_class, /Lock wait timeout exceeded/)
|
70
|
+
ensure
|
71
|
+
@adapter_2.execute("UNLOCK TABLES")
|
72
|
+
step.should == 1
|
73
|
+
step += 1
|
74
|
+
end
|
75
|
+
step.should == 2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "LockWaitTimeoutHandler with ARAdapter" do
|
83
|
+
it_should_behave_like "LockWaitTimeoutHandler" do
|
84
|
+
let(:sql_error_class) do
|
85
|
+
ActiveRecord::StatementInvalid
|
86
|
+
end
|
87
|
+
let(:adapter) do
|
88
|
+
SqlPartitioner::ARAdapter.new(ActiveRecord::Base.connection)
|
89
|
+
end
|
90
|
+
let(:get_adapter_2) do
|
91
|
+
lambda {
|
92
|
+
# TODO: Figure out how to establish the connection only once using before(:all)
|
93
|
+
# TODO: Not sure how to get a second db connection without a model
|
94
|
+
class WaitLockTimeoutTest < ActiveRecord::Base; end
|
95
|
+
|
96
|
+
db_conf = ActiveRecord::Base.connection.instance_variable_get(:@config)
|
97
|
+
# if the options passed are identical to the default repository, then no new connection is
|
98
|
+
# opened but the existing one gets reused. Hence we merge some random stuff
|
99
|
+
WaitLockTimeoutTest.establish_connection(db_conf.merge(:stuff => "stuff"))
|
100
|
+
|
101
|
+
SqlPartitioner::ARAdapter.new(WaitLockTimeoutTest.connection)
|
102
|
+
}
|
103
|
+
end
|
104
|
+
let (:close_adapter_2_connection) do
|
105
|
+
lambda {
|
106
|
+
WaitLockTimeoutTest.connection.disconnect!
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "LockWaitTimeoutHandler with DMAdapter" do
|
113
|
+
it_should_behave_like "LockWaitTimeoutHandler" do
|
114
|
+
let(:sql_error_class) do
|
115
|
+
DataObjects::SQLError
|
116
|
+
end
|
117
|
+
let(:adapter) do
|
118
|
+
SqlPartitioner::DMAdapter.new(DataMapper.repository.adapter)
|
119
|
+
end
|
120
|
+
let(:get_adapter_2) do
|
121
|
+
lambda {
|
122
|
+
# if the options passed are identical to the default repository, then no new connection is
|
123
|
+
# opened but the existing one gets reused. Hence we merge some random stuff
|
124
|
+
DataMapper.setup(:lock_events_connection, DataMapper.repository.adapter.options.merge("foobar" => 'something_random'))
|
125
|
+
SqlPartitioner::DMAdapter.new(DataMapper.repository(:lock_events_connection).adapter)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
let (:close_adapter_2_connection) do
|
129
|
+
lambda {
|
130
|
+
# @second_db_connection.disconnect!
|
131
|
+
# Not pretty to access a protected method if there is a cleaner way to close the connection, please let me know.
|
132
|
+
DataMapper.repository(:lock_events_connection).adapter.send(:open_connection).dispose
|
133
|
+
}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|