sequel 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +6 -28
  3. data/bin/sequel +7 -2
  4. data/doc/release_notes/3.9.0.txt +233 -0
  5. data/lib/sequel/adapters/ado.rb +4 -8
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/dbi.rb +3 -3
  8. data/lib/sequel/adapters/do.rb +7 -13
  9. data/lib/sequel/adapters/jdbc.rb +10 -16
  10. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  11. data/lib/sequel/adapters/mysql.rb +10 -23
  12. data/lib/sequel/adapters/odbc.rb +6 -10
  13. data/lib/sequel/adapters/postgres.rb +0 -5
  14. data/lib/sequel/adapters/shared/mssql.rb +17 -9
  15. data/lib/sequel/adapters/shared/mysql.rb +16 -7
  16. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  17. data/lib/sequel/adapters/sqlite.rb +2 -1
  18. data/lib/sequel/connection_pool.rb +67 -349
  19. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  20. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  21. data/lib/sequel/connection_pool/single.rb +29 -0
  22. data/lib/sequel/connection_pool/threaded.rb +150 -0
  23. data/lib/sequel/core.rb +46 -15
  24. data/lib/sequel/database.rb +11 -9
  25. data/lib/sequel/dataset/convenience.rb +23 -0
  26. data/lib/sequel/dataset/graph.rb +2 -2
  27. data/lib/sequel/dataset/query.rb +9 -5
  28. data/lib/sequel/dataset/sql.rb +87 -12
  29. data/lib/sequel/extensions/inflector.rb +8 -1
  30. data/lib/sequel/extensions/schema_dumper.rb +3 -4
  31. data/lib/sequel/model/associations.rb +5 -43
  32. data/lib/sequel/model/base.rb +9 -2
  33. data/lib/sequel/model/default_inflections.rb +1 -1
  34. data/lib/sequel/model/exceptions.rb +11 -1
  35. data/lib/sequel/model/inflections.rb +8 -1
  36. data/lib/sequel/model/plugins.rb +2 -12
  37. data/lib/sequel/plugins/active_model.rb +5 -0
  38. data/lib/sequel/plugins/association_dependencies.rb +1 -1
  39. data/lib/sequel/plugins/many_through_many.rb +1 -1
  40. data/lib/sequel/plugins/optimistic_locking.rb +65 -0
  41. data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
  42. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  43. data/lib/sequel/sql.rb +2 -2
  44. data/lib/sequel/timezones.rb +2 -2
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/mssql_spec.rb +19 -0
  47. data/spec/adapters/mysql_spec.rb +4 -0
  48. data/spec/adapters/postgres_spec.rb +180 -0
  49. data/spec/adapters/spec_helper.rb +15 -1
  50. data/spec/core/connection_pool_spec.rb +119 -78
  51. data/spec/core/database_spec.rb +41 -50
  52. data/spec/core/dataset_spec.rb +115 -4
  53. data/spec/extensions/active_model_spec.rb +40 -34
  54. data/spec/extensions/boolean_readers_spec.rb +1 -1
  55. data/spec/extensions/migration_spec.rb +43 -38
  56. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  57. data/spec/extensions/schema_dumper_spec.rb +4 -4
  58. data/spec/extensions/single_table_inheritance_spec.rb +19 -11
  59. data/spec/integration/dataset_test.rb +44 -1
  60. data/spec/integration/plugin_test.rb +39 -0
  61. data/spec/integration/prepared_statement_test.rb +58 -7
  62. data/spec/integration/spec_helper.rb +4 -0
  63. data/spec/model/eager_loading_spec.rb +24 -0
  64. data/spec/model/validations_spec.rb +5 -1
  65. metadata +114 -106
@@ -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
- @dataset.filter(:id => @d1.select(:id)).sql.should ==
424
- "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
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 (begin
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
- ActiveModel::Lint.test(@m)
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
@@ -8,7 +8,7 @@ describe Sequel::Model, "BooleanReaders plugin" do
8
8
  end
9
9
 
10
10
  @c = Class.new(Sequel::Model(@db[:items]))
11
- @p =lambda do
11
+ @p = proc do
12
12
  @columns = [:id, :b, :y]
13
13
  def columns; @columns; end
14
14
  end
@@ -158,13 +158,17 @@ context "Sequel::Migrator" do
158
158
  end
159
159
  @db = dbc.new
160
160
 
161
- File.open('001_create_sessions.rb', 'w') {|f| f << MIGRATION_001}
162
- File.open('002_create_nodes.rb', 'w') {|f| f << MIGRATION_002}
163
- File.open('003_create_users.rb', 'w') {|f| f << MIGRATION_003}
164
- File.open('005_5_create_attributes.rb', 'w') {|f| f << MIGRATION_005}
165
- Dir.mkdir("alt_app")
166
- File.open('alt_app/001_create_alt_basic.rb', 'w') {|f| f << ALT_MIGRATION_001}
167
- File.open('alt_app/003_create_alt_advanced.rb', 'w') {|f| f << ALT_MIGRATION_003}
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('001_create_sessions.rb')
179
- File.delete('002_create_nodes.rb')
180
- File.delete('003_create_users.rb')
181
- File.delete('005_5_create_attributes.rb')
182
- File.delete("alt_app/001_create_alt_basic.rb")
183
- File.delete("alt_app/003_create_alt_advanced.rb")
184
- Dir.rmdir("alt_app")
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('.', 1..1).should == ['./001_create_sessions.rb']
189
- Sequel::Migrator.migration_files('.', 1..3).should == ['./001_create_sessions.rb', './002_create_nodes.rb', './003_create_users.rb']
190
- Sequel::Migrator.migration_files('.', 3..6).should == ['./003_create_users.rb', './005_5_create_attributes.rb']
191
- Sequel::Migrator.migration_files('.', 7..8).should == []
192
- Sequel::Migrator.migration_files('alt_app', 1..1).should == ['alt_app/001_create_alt_basic.rb']
193
- Sequel::Migrator.migration_files('alt_app', 1..3).should == ['alt_app/001_create_alt_basic.rb','alt_app/003_create_alt_advanced.rb']
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('.').should == 5
198
- Sequel::Migrator.latest_migration_version('alt_app').should == 3
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('.', 3, 0, :up).should == [CreateSessions, CreateNodes, CreateUsers]
203
- Sequel::Migrator.migration_classes('alt_app', 3, 0, :up).should == [CreateAltBasic, CreateAltAdvanced]
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('.', 0, 5, :down).should == [CreateAttributes, CreateUsers, CreateNodes, CreateSessions]
208
- Sequel::Migrator.migration_classes('alt_app', 0, 3, :down).should == [CreateAltAdvanced, CreateAltBasic]
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('.', 3, 1, :up).should == [CreateNodes, CreateUsers]
213
- Sequel::Migrator.migration_classes('alt_app', 3, 2, :up).should == [CreateAltAdvanced]
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('.', 2, 5, :down).should == [CreateAttributes, CreateUsers]
218
- Sequel::Migrator.migration_classes('alt_app', 2, 4, :down).should == [CreateAltAdvanced]
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, '.', 3, 2)
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, '.', 5)
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, '.', 1, 5)
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, '.', 0, 5)
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, '.', 3, 2).should == 3
324
- Sequel::Migrator.apply(@db, '.', 0).should == 0
325
- Sequel::Migrator.apply(@db, '.').should == 5
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