sequel 3.8.0 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +48 -0
- data/Rakefile +6 -28
- data/bin/sequel +7 -2
- data/doc/release_notes/3.9.0.txt +233 -0
- data/lib/sequel/adapters/ado.rb +4 -8
- data/lib/sequel/adapters/amalgalite.rb +1 -1
- data/lib/sequel/adapters/dbi.rb +3 -3
- data/lib/sequel/adapters/do.rb +7 -13
- data/lib/sequel/adapters/jdbc.rb +10 -16
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/mysql.rb +10 -23
- data/lib/sequel/adapters/odbc.rb +6 -10
- data/lib/sequel/adapters/postgres.rb +0 -5
- data/lib/sequel/adapters/shared/mssql.rb +17 -9
- data/lib/sequel/adapters/shared/mysql.rb +16 -7
- data/lib/sequel/adapters/shared/sqlite.rb +5 -0
- data/lib/sequel/adapters/sqlite.rb +2 -1
- data/lib/sequel/connection_pool.rb +67 -349
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +46 -15
- data/lib/sequel/database.rb +11 -9
- data/lib/sequel/dataset/convenience.rb +23 -0
- data/lib/sequel/dataset/graph.rb +2 -2
- data/lib/sequel/dataset/query.rb +9 -5
- data/lib/sequel/dataset/sql.rb +87 -12
- data/lib/sequel/extensions/inflector.rb +8 -1
- data/lib/sequel/extensions/schema_dumper.rb +3 -4
- data/lib/sequel/model/associations.rb +5 -43
- data/lib/sequel/model/base.rb +9 -2
- data/lib/sequel/model/default_inflections.rb +1 -1
- data/lib/sequel/model/exceptions.rb +11 -1
- data/lib/sequel/model/inflections.rb +8 -1
- data/lib/sequel/model/plugins.rb +2 -12
- data/lib/sequel/plugins/active_model.rb +5 -0
- data/lib/sequel/plugins/association_dependencies.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +65 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
- data/lib/sequel/plugins/validation_helpers.rb +2 -2
- data/lib/sequel/sql.rb +2 -2
- data/lib/sequel/timezones.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +19 -0
- data/spec/adapters/mysql_spec.rb +4 -0
- data/spec/adapters/postgres_spec.rb +180 -0
- data/spec/adapters/spec_helper.rb +15 -1
- data/spec/core/connection_pool_spec.rb +119 -78
- data/spec/core/database_spec.rb +41 -50
- data/spec/core/dataset_spec.rb +115 -4
- data/spec/extensions/active_model_spec.rb +40 -34
- data/spec/extensions/boolean_readers_spec.rb +1 -1
- data/spec/extensions/migration_spec.rb +43 -38
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- data/spec/extensions/schema_dumper_spec.rb +4 -4
- data/spec/extensions/single_table_inheritance_spec.rb +19 -11
- data/spec/integration/dataset_test.rb +44 -1
- data/spec/integration/plugin_test.rb +39 -0
- data/spec/integration/prepared_statement_test.rb +58 -7
- data/spec/integration/spec_helper.rb +4 -0
- data/spec/model/eager_loading_spec.rb +24 -0
- data/spec/model/validations_spec.rb +5 -1
- metadata +114 -106
data/spec/core/dataset_spec.rb
CHANGED
@@ -267,6 +267,33 @@ context "A dataset with multiple tables in its FROM clause" do
|
|
267
267
|
end
|
268
268
|
end
|
269
269
|
|
270
|
+
context "Dataset#unused_table_alias" do
|
271
|
+
before do
|
272
|
+
@ds = Sequel::Dataset.new(nil).from(:test)
|
273
|
+
end
|
274
|
+
|
275
|
+
specify "should return given symbol if it hasn't already been used" do
|
276
|
+
@ds.unused_table_alias(:blah).should == :blah
|
277
|
+
end
|
278
|
+
|
279
|
+
specify "should return a symbol specifying an alias that hasn't already been used if it has already been used" do
|
280
|
+
@ds.unused_table_alias(:test).should == :test_0
|
281
|
+
@ds.from(:test, :test_0).unused_table_alias(:test).should == :test_1
|
282
|
+
@ds.from(:test, :test_0).cross_join(:test_1).unused_table_alias(:test).should == :test_2
|
283
|
+
end
|
284
|
+
|
285
|
+
specify "should return an appropriate symbol if given other forms of identifiers" do
|
286
|
+
@ds.unused_table_alias('test').should == :test_0
|
287
|
+
@ds.unused_table_alias(:b__t___test).should == :test_0
|
288
|
+
@ds.unused_table_alias(:b__test).should == :test_0
|
289
|
+
@ds.unused_table_alias(:test.qualify(:b)).should == :test_0
|
290
|
+
@ds.unused_table_alias(:b.as(:test)).should == :test_0
|
291
|
+
@ds.unused_table_alias(:b.as(:test.identifier)).should == :test_0
|
292
|
+
@ds.unused_table_alias(:b.as('test')).should == :test_0
|
293
|
+
@ds.unused_table_alias(:test.identifier).should == :test_0
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
270
297
|
context "Dataset#exists" do
|
271
298
|
before do
|
272
299
|
@ds1 = Sequel::Dataset.new(nil).from(:test)
|
@@ -419,9 +446,62 @@ context "Dataset#where" do
|
|
419
446
|
specify "should accept a subquery" do
|
420
447
|
@dataset.filter('gdp > ?', @d1.select(:avg.sql_function(:gdp))).sql.should ==
|
421
448
|
"SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
|
422
|
-
|
423
|
-
|
424
|
-
|
449
|
+
end
|
450
|
+
|
451
|
+
specify "should handle all types of IN/NOT IN queries" do
|
452
|
+
@dataset.filter(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
|
453
|
+
@dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
|
454
|
+
@dataset.filter(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id IN (1, 2))"
|
455
|
+
@dataset.filter([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
|
456
|
+
@dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
457
|
+
@dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
|
458
|
+
|
459
|
+
@dataset.exclude(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id NOT IN (SELECT id FROM test WHERE (region = 'Asia')))"
|
460
|
+
@dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
461
|
+
@dataset.exclude(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id NOT IN (1, 2))"
|
462
|
+
@dataset.exclude([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
|
463
|
+
@dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
464
|
+
@dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
|
465
|
+
end
|
466
|
+
|
467
|
+
specify "should handle IN/NOT IN queries with multiple columns and an array where the database doesn't support it" do
|
468
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
469
|
+
@dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
470
|
+
@dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
|
471
|
+
@dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
472
|
+
@dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
|
473
|
+
end
|
474
|
+
|
475
|
+
specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
|
476
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
477
|
+
d1 = @d1.select(:id1, :id2)
|
478
|
+
def d1.fetch_rows(sql)
|
479
|
+
@sql_used = sql
|
480
|
+
@columns = [:id1, :id2]
|
481
|
+
yield(:id1=>1, :id2=>2)
|
482
|
+
yield(:id1=>3, :id2=>4)
|
483
|
+
end
|
484
|
+
d1.instance_variable_get(:@sql_used).should == nil
|
485
|
+
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
|
486
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
487
|
+
d1.instance_variable_set(:@sql_used, nil)
|
488
|
+
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
|
489
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
490
|
+
end
|
491
|
+
|
492
|
+
specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
|
493
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
494
|
+
d1 = @d1.select(:id1, :id2)
|
495
|
+
def d1.fetch_rows(sql)
|
496
|
+
@sql_used = sql
|
497
|
+
@columns = [:id1, :id2]
|
498
|
+
end
|
499
|
+
d1.instance_variable_get(:@sql_used).should == nil
|
500
|
+
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
501
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
502
|
+
d1.instance_variable_set(:@sql_used, nil)
|
503
|
+
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
504
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
425
505
|
end
|
426
506
|
|
427
507
|
specify "should accept a subquery for an EXISTS clause" do
|
@@ -832,6 +912,13 @@ context "Dataset#literal" do
|
|
832
912
|
@dataset.literal(d).should == s
|
833
913
|
end
|
834
914
|
|
915
|
+
specify "should literalize Date properly, even if to_s is overridden" do
|
916
|
+
d = Date.today
|
917
|
+
def d.to_s; "adsf" end
|
918
|
+
s = d.strftime("'%Y-%m-%d'")
|
919
|
+
@dataset.literal(d).should == s
|
920
|
+
end
|
921
|
+
|
835
922
|
specify "should literalize Time, DateTime, Date properly if SQL standard format is required" do
|
836
923
|
@dataset.meta_def(:requires_sql_standard_datetimes?){true}
|
837
924
|
|
@@ -1276,6 +1363,21 @@ context "Dataset#limit" do
|
|
1276
1363
|
specify "should include an offset if a second argument is given" do
|
1277
1364
|
@dataset.limit(6, 10).sql.should ==
|
1278
1365
|
'SELECT * FROM test LIMIT 6 OFFSET 10'
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
specify "should convert regular strings to integers" do
|
1369
|
+
@dataset.limit('6', 'a() - 1').sql.should ==
|
1370
|
+
'SELECT * FROM test LIMIT 6 OFFSET 0'
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
specify "should not convert literal strings to integers" do
|
1374
|
+
@dataset.limit('6'.lit, 'a() - 1'.lit).sql.should ==
|
1375
|
+
'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
specify "should not convert other objects" do
|
1379
|
+
@dataset.limit(6, :a.sql_function - 1).sql.should ==
|
1380
|
+
'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
|
1279
1381
|
end
|
1280
1382
|
|
1281
1383
|
specify "should work with fixed sql datasets" do
|
@@ -1852,6 +1954,11 @@ context "Dataset#join_table" do
|
|
1852
1954
|
proc{@d.join(:categories, :a=>:d).delete_sql}.should raise_error(Sequel::InvalidOperation)
|
1853
1955
|
proc{@d.join(:categories, :a=>:d).truncate_sql}.should raise_error(Sequel::InvalidOperation)
|
1854
1956
|
end
|
1957
|
+
|
1958
|
+
specify "should raise an error if an invalid option is passed" do
|
1959
|
+
proc{@d.join(:c, [:id], nil)}.should raise_error(Sequel::Error)
|
1960
|
+
proc{@d.join(:c, [:id], :c.qualify(:d))}.should raise_error(Sequel::Error)
|
1961
|
+
end
|
1855
1962
|
end
|
1856
1963
|
|
1857
1964
|
context "Dataset#[]=" do
|
@@ -2683,6 +2790,10 @@ context "Dataset#update_sql" do
|
|
2683
2790
|
@ds.update_sql("a = b").should == "UPDATE items SET a = b"
|
2684
2791
|
end
|
2685
2792
|
|
2793
|
+
specify "should handle implicitly qualified symbols" do
|
2794
|
+
@ds.update_sql(:items__a=>:b).should == "UPDATE items SET items.a = b"
|
2795
|
+
end
|
2796
|
+
|
2686
2797
|
specify "should accept hash with string keys" do
|
2687
2798
|
@ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
|
2688
2799
|
end
|
@@ -3036,7 +3147,7 @@ context Sequel::Dataset::UnnumberedArgumentMapper do
|
|
3036
3147
|
end
|
3037
3148
|
|
3038
3149
|
specify "should submitted the SQL to the database with placeholders and bind variables" do
|
3039
|
-
@ps.each{|p| p.call(:n=>1)}
|
3150
|
+
@ps.each{|p| p.prepared_sql; p.call(:n=>1)}
|
3040
3151
|
@db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1],
|
3041
3152
|
["SELECT * FROM items WHERE (num = ?)", 1],
|
3042
3153
|
["SELECT * FROM items WHERE (num = ?) LIMIT 1", 1],
|
@@ -1,47 +1,53 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
-
if (
|
3
|
-
require 'active_model'
|
4
|
-
true
|
5
|
-
rescue LoadError
|
6
|
-
end)
|
2
|
+
if RUBY_PLATFORM !~ /(win|w)32|java$/
|
7
3
|
describe "ActiveModel plugin" do
|
8
|
-
before do
|
9
|
-
@c = Class.new(Sequel::Model) do
|
10
|
-
def delete; end
|
11
|
-
end
|
12
|
-
@c.plugin :active_model
|
13
|
-
@m = @c.new
|
14
|
-
@o = @c.load({})
|
15
|
-
end
|
16
|
-
|
17
4
|
specify "should be compliant to the ActiveModel spec" do
|
18
5
|
s = ''
|
19
6
|
IO.popen('-') do |f|
|
20
7
|
if f
|
21
8
|
s = f.read
|
22
9
|
else
|
23
|
-
|
10
|
+
begin
|
11
|
+
require 'active_model'
|
12
|
+
rescue LoadError
|
13
|
+
puts "0 failures, 0 errors"
|
14
|
+
else
|
15
|
+
require 'test/unit'
|
16
|
+
require "test/unit/ui/console/testrunner"
|
17
|
+
class AMLintTest < Test::Unit::TestCase
|
18
|
+
def setup
|
19
|
+
@c = Class.new(Sequel::Model) do
|
20
|
+
def delete; end
|
21
|
+
end
|
22
|
+
@c.plugin :active_model
|
23
|
+
@m = @model = @c.new
|
24
|
+
@o = @c.load({})
|
25
|
+
end
|
26
|
+
include ActiveModel::Lint::Tests
|
27
|
+
|
28
|
+
def test_to_model
|
29
|
+
assert_equal @m.to_model.object_id.should, @m.object_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_new_record
|
33
|
+
assert_equal true, @m.new_record?
|
34
|
+
assert_equal false, @o.new_record?
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_destroyed
|
38
|
+
assert_equal false, @m.destroyed?
|
39
|
+
assert_equal false, @o.destroyed?
|
40
|
+
@m.destroy
|
41
|
+
@o.destroy
|
42
|
+
assert_equal true, @m.destroyed?
|
43
|
+
assert_equal true, @o.destroyed?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
Test::Unit::UI::Console::TestRunner.run(AMLintTest)
|
47
|
+
end
|
24
48
|
end
|
25
49
|
end
|
26
50
|
s.should =~ /0 failures, 0 errors/
|
27
51
|
end
|
28
|
-
|
29
|
-
specify "to_model should return self" do
|
30
|
-
@m.to_model.object_id.should == @m.object_id
|
31
|
-
end
|
32
|
-
|
33
|
-
specify "new_record? should be aliased to new" do
|
34
|
-
@m.new_record?.should == true
|
35
|
-
@o.new_record?.should == false
|
36
|
-
end
|
37
|
-
|
38
|
-
specify "new_record? should be aliased to new" do
|
39
|
-
@m.destroyed?.should == false
|
40
|
-
@o.destroyed?.should == false
|
41
|
-
@m.destroy
|
42
|
-
@o.destroy
|
43
|
-
@m.destroyed?.should == true
|
44
|
-
@o.destroyed?.should == true
|
45
|
-
end
|
46
52
|
end
|
47
|
-
end
|
53
|
+
end
|
@@ -158,13 +158,17 @@ context "Sequel::Migrator" do
|
|
158
158
|
end
|
159
159
|
@db = dbc.new
|
160
160
|
|
161
|
-
|
162
|
-
|
163
|
-
File.open(
|
164
|
-
File.open(
|
165
|
-
|
166
|
-
File.open(
|
167
|
-
|
161
|
+
@dirname = "migrate_#{$$}"
|
162
|
+
Dir.mkdir(@dirname)
|
163
|
+
File.open("#{@dirname}/001_create_sessions.rb", 'w') {|f| f << MIGRATION_001}
|
164
|
+
File.open("#{@dirname}/002_create_nodes.rb", 'w') {|f| f << MIGRATION_002}
|
165
|
+
File.open("#{@dirname}/003_create_users.rb", 'w') {|f| f << MIGRATION_003}
|
166
|
+
File.open("#{@dirname}/005_5_create_attributes.rb", 'w') {|f| f << MIGRATION_005}
|
167
|
+
|
168
|
+
@alt_dirname = "migrate_alt_#{$$}"
|
169
|
+
Dir.mkdir(@alt_dirname)
|
170
|
+
File.open("#{@alt_dirname}/001_create_alt_basic.rb", 'w') {|f| f << ALT_MIGRATION_001}
|
171
|
+
File.open("#{@alt_dirname}/003_create_alt_advanced.rb", 'w') {|f| f << ALT_MIGRATION_003}
|
168
172
|
end
|
169
173
|
|
170
174
|
after do
|
@@ -175,47 +179,48 @@ context "Sequel::Migrator" do
|
|
175
179
|
Object.send(:remove_const, "CreateAltBasic") if Object.const_defined?("CreateAltBasic")
|
176
180
|
Object.send(:remove_const, "CreateAltAdvanced") if Object.const_defined?("CreateAltAdvanced")
|
177
181
|
|
178
|
-
File.delete(
|
179
|
-
File.delete(
|
180
|
-
File.delete(
|
181
|
-
File.delete(
|
182
|
-
|
183
|
-
File.delete("
|
184
|
-
|
182
|
+
File.delete("#{@dirname}/001_create_sessions.rb")
|
183
|
+
File.delete("#{@dirname}/002_create_nodes.rb")
|
184
|
+
File.delete("#{@dirname}/003_create_users.rb")
|
185
|
+
File.delete("#{@dirname}/005_5_create_attributes.rb")
|
186
|
+
Dir.rmdir(@dirname)
|
187
|
+
File.delete("#{@alt_dirname}/001_create_alt_basic.rb")
|
188
|
+
File.delete("#{@alt_dirname}/003_create_alt_advanced.rb")
|
189
|
+
Dir.rmdir(@alt_dirname)
|
185
190
|
end
|
186
191
|
|
187
192
|
specify "#migration_files should return the list of files for a specified version range" do
|
188
|
-
Sequel::Migrator.migration_files(
|
189
|
-
Sequel::Migrator.migration_files(
|
190
|
-
Sequel::Migrator.migration_files(
|
191
|
-
Sequel::Migrator.migration_files(
|
192
|
-
Sequel::Migrator.migration_files(
|
193
|
-
Sequel::Migrator.migration_files(
|
193
|
+
Sequel::Migrator.migration_files(@dirname, 1..1).map{|f| File.basename(f)}.should == ['001_create_sessions.rb']
|
194
|
+
Sequel::Migrator.migration_files(@dirname, 1..3).map{|f| File.basename(f)}.should == ['001_create_sessions.rb', '002_create_nodes.rb', '003_create_users.rb']
|
195
|
+
Sequel::Migrator.migration_files(@dirname, 3..6).map{|f| File.basename(f)}.should == ['003_create_users.rb', '005_5_create_attributes.rb']
|
196
|
+
Sequel::Migrator.migration_files(@dirname, 7..8).map{|f| File.basename(f)}.should == []
|
197
|
+
Sequel::Migrator.migration_files(@alt_dirname, 1..1).map{|f| File.basename(f)}.should == ['001_create_alt_basic.rb']
|
198
|
+
Sequel::Migrator.migration_files(@alt_dirname, 1..3).map{|f| File.basename(f)}.should == ['001_create_alt_basic.rb','003_create_alt_advanced.rb']
|
194
199
|
end
|
195
200
|
|
196
201
|
specify "#latest_migration_version should return the latest version available" do
|
197
|
-
Sequel::Migrator.latest_migration_version(
|
198
|
-
Sequel::Migrator.latest_migration_version(
|
202
|
+
Sequel::Migrator.latest_migration_version(@dirname).should == 5
|
203
|
+
Sequel::Migrator.latest_migration_version(@alt_dirname).should == 3
|
199
204
|
end
|
200
205
|
|
201
206
|
specify "#migration_classes should load the migration classes for the specified range for the up direction" do
|
202
|
-
Sequel::Migrator.migration_classes(
|
203
|
-
Sequel::Migrator.migration_classes(
|
207
|
+
Sequel::Migrator.migration_classes(@dirname, 3, 0, :up).should == [CreateSessions, CreateNodes, CreateUsers]
|
208
|
+
Sequel::Migrator.migration_classes(@alt_dirname, 3, 0, :up).should == [CreateAltBasic, CreateAltAdvanced]
|
204
209
|
end
|
205
210
|
|
206
211
|
specify "#migration_classes should load the migration classes for the specified range for the down direction" do
|
207
|
-
Sequel::Migrator.migration_classes(
|
208
|
-
Sequel::Migrator.migration_classes(
|
212
|
+
Sequel::Migrator.migration_classes(@dirname, 0, 5, :down).should == [CreateAttributes, CreateUsers, CreateNodes, CreateSessions]
|
213
|
+
Sequel::Migrator.migration_classes(@alt_dirname, 0, 3, :down).should == [CreateAltAdvanced, CreateAltBasic]
|
209
214
|
end
|
210
215
|
|
211
216
|
specify "#migration_classes should start from current + 1 for the up direction" do
|
212
|
-
Sequel::Migrator.migration_classes(
|
213
|
-
Sequel::Migrator.migration_classes(
|
217
|
+
Sequel::Migrator.migration_classes(@dirname, 3, 1, :up).should == [CreateNodes, CreateUsers]
|
218
|
+
Sequel::Migrator.migration_classes(@alt_dirname, 3, 2, :up).should == [CreateAltAdvanced]
|
214
219
|
end
|
215
220
|
|
216
221
|
specify "#migration_classes should end on current + 1 for the down direction" do
|
217
|
-
Sequel::Migrator.migration_classes(
|
218
|
-
Sequel::Migrator.migration_classes(
|
222
|
+
Sequel::Migrator.migration_classes(@dirname, 2, 5, :down).should == [CreateAttributes, CreateUsers]
|
223
|
+
Sequel::Migrator.migration_classes(@alt_dirname, 2, 4, :down).should == [CreateAltAdvanced]
|
219
224
|
end
|
220
225
|
|
221
226
|
specify "#schema_info_dataset should automatically create the schema_info table" do
|
@@ -266,12 +271,12 @@ context "Sequel::Migrator" do
|
|
266
271
|
end
|
267
272
|
|
268
273
|
specify "should apply migrations correctly in the up direction" do
|
269
|
-
Sequel::Migrator.apply(@db,
|
274
|
+
Sequel::Migrator.apply(@db, @dirname, 3, 2)
|
270
275
|
@db.creates.should == [3333]
|
271
276
|
|
272
277
|
Sequel::Migrator.get_current_migration_version(@db).should == 3
|
273
278
|
|
274
|
-
Sequel::Migrator.apply(@db,
|
279
|
+
Sequel::Migrator.apply(@db, @dirname, 5)
|
275
280
|
@db.creates.should == [3333, 5555]
|
276
281
|
|
277
282
|
Sequel::Migrator.get_current_migration_version(@db).should == 5
|
@@ -286,7 +291,7 @@ context "Sequel::Migrator" do
|
|
286
291
|
end
|
287
292
|
|
288
293
|
specify "should apply migrations correctly in the down direction" do
|
289
|
-
Sequel::Migrator.apply(@db,
|
294
|
+
Sequel::Migrator.apply(@db, @dirname, 1, 5)
|
290
295
|
@db.drops.should == [5555, 3333, 2222]
|
291
296
|
|
292
297
|
Sequel::Migrator.get_current_migration_version(@db).should == 1
|
@@ -297,7 +302,7 @@ context "Sequel::Migrator" do
|
|
297
302
|
end
|
298
303
|
|
299
304
|
specify "should apply migrations up to the latest version if no target is given" do
|
300
|
-
Sequel::Migrator.apply(@db,
|
305
|
+
Sequel::Migrator.apply(@db, @dirname)
|
301
306
|
@db.creates.should == [1111, 2222, 3333, 5555]
|
302
307
|
|
303
308
|
Sequel::Migrator.get_current_migration_version(@db).should == 5
|
@@ -309,7 +314,7 @@ context "Sequel::Migrator" do
|
|
309
314
|
end
|
310
315
|
|
311
316
|
specify "should apply migrations down to 0 version correctly" do
|
312
|
-
Sequel::Migrator.apply(@db,
|
317
|
+
Sequel::Migrator.apply(@db, @dirname, 0, 5)
|
313
318
|
@db.drops.should == [5555, 3333, 2222, 1111]
|
314
319
|
|
315
320
|
Sequel::Migrator.get_current_migration_version(@db).should == 0
|
@@ -320,8 +325,8 @@ context "Sequel::Migrator" do
|
|
320
325
|
end
|
321
326
|
|
322
327
|
specify "should return the target version" do
|
323
|
-
Sequel::Migrator.apply(@db,
|
324
|
-
Sequel::Migrator.apply(@db,
|
325
|
-
Sequel::Migrator.apply(@db,
|
328
|
+
Sequel::Migrator.apply(@db, @dirname, 3, 2).should == 3
|
329
|
+
Sequel::Migrator.apply(@db, @dirname, 0).should == 0
|
330
|
+
Sequel::Migrator.apply(@db, @dirname).should == 5
|
326
331
|
end
|
327
332
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
describe "optimistic_locking plugin" do
|
4
|
+
before do
|
5
|
+
@c = Class.new(Sequel::Model(:people)) do
|
6
|
+
end
|
7
|
+
h = {1=>{:id=>1, :name=>'John', :lock_version=>2}}
|
8
|
+
lv = @lv = "lock_version"
|
9
|
+
@c.dataset.quote_identifiers = false
|
10
|
+
@c.dataset.meta_def(:h){h}
|
11
|
+
@c.dataset.meta_def(:lv){lv}
|
12
|
+
@c.dataset.meta_def(:update) do |opts|
|
13
|
+
case update_sql(opts)
|
14
|
+
when /UPDATE people SET (name|#{lv}) = ('Jim'|'Bob'|\d+), (?:name|#{lv}) = ('Jim'|'Bob'|\d+) WHERE \(\(id = (\d+)\) AND \(#{lv} = (\d+)\)\)/
|
15
|
+
name, nlv = $1 == 'name' ? [$2, $3] : [$3, $2]
|
16
|
+
m = h[$4.to_i]
|
17
|
+
if m && m[:lock_version] == $5.to_i
|
18
|
+
m.merge!(:name=>name.gsub("'", ''), :lock_version=>nlv.to_i)
|
19
|
+
1
|
20
|
+
else
|
21
|
+
0
|
22
|
+
end
|
23
|
+
else
|
24
|
+
puts update_sql(opts)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@c.dataset.instance_eval do
|
28
|
+
def fetch_rows(sql)
|
29
|
+
m = h[1].dup
|
30
|
+
v = m.delete(:lock_version)
|
31
|
+
m[lv.to_sym] = v
|
32
|
+
yield(m)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@c.dataset.meta_def(:delete) do
|
36
|
+
case delete_sql
|
37
|
+
when /DELETE FROM people WHERE \(\(id = (\d+)\) AND \(#{lv} = (\d+)\)\)/
|
38
|
+
m = h[$1.to_i]
|
39
|
+
if m && m[lv.to_sym] == $2.to_i
|
40
|
+
h.delete[$1.to_i]
|
41
|
+
1
|
42
|
+
else
|
43
|
+
0
|
44
|
+
end
|
45
|
+
else
|
46
|
+
puts delete_sql
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@c.columns :id, :name, :lock_version
|
50
|
+
@c.plugin :optimistic_locking
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "should raise an error when updating a stale record" do
|
54
|
+
p1 = @c[1]
|
55
|
+
p2 = @c[1]
|
56
|
+
p1.update(:name=>'Jim')
|
57
|
+
proc{p2.update(:name=>'Bob')}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
58
|
+
end
|
59
|
+
|
60
|
+
specify "should raise an error when destroying a stale record" do
|
61
|
+
p1 = @c[1]
|
62
|
+
p2 = @c[1]
|
63
|
+
p1.update(:name=>'Jim')
|
64
|
+
proc{p2.destroy}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
65
|
+
end
|
66
|
+
|
67
|
+
specify "should not raise an error when updating the same record twice" do
|
68
|
+
p1 = @c[1]
|
69
|
+
p1.update(:name=>'Jim')
|
70
|
+
proc{p1.update(:name=>'Bob')}.should_not raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
specify "should allow changing the lock column via model.lock_column=" do
|
74
|
+
@lv.replace('lv')
|
75
|
+
@c.columns :id, :name, :lv
|
76
|
+
@c.lock_column = :lv
|
77
|
+
p1 = @c[1]
|
78
|
+
p2 = @c[1]
|
79
|
+
p1.update(:name=>'Jim')
|
80
|
+
proc{p2.update(:name=>'Bob')}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
81
|
+
end
|
82
|
+
|
83
|
+
specify "should allow changing the lock column via plugin option" do
|
84
|
+
@lv.replace('lv')
|
85
|
+
@c.columns :id, :name, :lv
|
86
|
+
@c.plugin :optimistic_locking, :lock_column=>:lv
|
87
|
+
p1 = @c[1]
|
88
|
+
p2 = @c[1]
|
89
|
+
p1.update(:name=>'Jim')
|
90
|
+
proc{p2.destroy}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
91
|
+
end
|
92
|
+
|
93
|
+
specify "should work when subclassing" do
|
94
|
+
c = Class.new(@c)
|
95
|
+
p1 = c[1]
|
96
|
+
p2 = c[1]
|
97
|
+
p1.update(:name=>'Jim')
|
98
|
+
proc{p2.update(:name=>'Bob')}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
|
99
|
+
end
|
100
|
+
end
|