sequel 3.8.0 → 3.9.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.
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