sequel 3.38.0 → 3.39.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/CHANGELOG +62 -0
  2. data/README.rdoc +2 -2
  3. data/bin/sequel +12 -2
  4. data/doc/advanced_associations.rdoc +1 -1
  5. data/doc/association_basics.rdoc +13 -0
  6. data/doc/release_notes/3.39.0.txt +237 -0
  7. data/doc/schema_modification.rdoc +4 -4
  8. data/lib/sequel/adapters/jdbc/derby.rb +1 -0
  9. data/lib/sequel/adapters/mock.rb +5 -0
  10. data/lib/sequel/adapters/mysql.rb +8 -1
  11. data/lib/sequel/adapters/mysql2.rb +10 -3
  12. data/lib/sequel/adapters/postgres.rb +72 -8
  13. data/lib/sequel/adapters/shared/db2.rb +1 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +57 -0
  15. data/lib/sequel/adapters/shared/mysql.rb +95 -19
  16. data/lib/sequel/adapters/shared/oracle.rb +14 -0
  17. data/lib/sequel/adapters/shared/postgres.rb +63 -24
  18. data/lib/sequel/adapters/shared/sqlite.rb +6 -9
  19. data/lib/sequel/connection_pool/sharded_threaded.rb +8 -3
  20. data/lib/sequel/connection_pool/threaded.rb +9 -4
  21. data/lib/sequel/database/query.rb +60 -48
  22. data/lib/sequel/database/schema_generator.rb +13 -6
  23. data/lib/sequel/database/schema_methods.rb +65 -12
  24. data/lib/sequel/dataset/actions.rb +22 -4
  25. data/lib/sequel/dataset/features.rb +5 -0
  26. data/lib/sequel/dataset/graph.rb +2 -3
  27. data/lib/sequel/dataset/misc.rb +2 -2
  28. data/lib/sequel/dataset/query.rb +0 -2
  29. data/lib/sequel/dataset/sql.rb +33 -12
  30. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  31. data/lib/sequel/extensions/eval_inspect.rb +17 -2
  32. data/lib/sequel/extensions/pg_array_ops.rb +15 -5
  33. data/lib/sequel/extensions/pg_interval.rb +2 -2
  34. data/lib/sequel/extensions/pg_row_ops.rb +18 -0
  35. data/lib/sequel/extensions/schema_dumper.rb +3 -11
  36. data/lib/sequel/model/associations.rb +3 -2
  37. data/lib/sequel/model/base.rb +57 -13
  38. data/lib/sequel/model/exceptions.rb +20 -2
  39. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  40. data/lib/sequel/plugins/defaults_setter.rb +15 -1
  41. data/lib/sequel/plugins/dirty.rb +2 -2
  42. data/lib/sequel/plugins/identity_map.rb +12 -8
  43. data/lib/sequel/plugins/subclasses.rb +19 -1
  44. data/lib/sequel/plugins/tree.rb +3 -3
  45. data/lib/sequel/plugins/validation_helpers.rb +24 -4
  46. data/lib/sequel/sql.rb +64 -24
  47. data/lib/sequel/timezones.rb +10 -2
  48. data/lib/sequel/version.rb +1 -1
  49. data/spec/adapters/mssql_spec.rb +26 -25
  50. data/spec/adapters/mysql_spec.rb +57 -23
  51. data/spec/adapters/oracle_spec.rb +34 -49
  52. data/spec/adapters/postgres_spec.rb +226 -128
  53. data/spec/adapters/sqlite_spec.rb +50 -49
  54. data/spec/core/connection_pool_spec.rb +22 -0
  55. data/spec/core/database_spec.rb +53 -47
  56. data/spec/core/dataset_spec.rb +36 -32
  57. data/spec/core/expression_filters_spec.rb +14 -2
  58. data/spec/core/mock_adapter_spec.rb +4 -0
  59. data/spec/core/object_graph_spec.rb +0 -13
  60. data/spec/core/schema_spec.rb +64 -5
  61. data/spec/core_extensions_spec.rb +1 -0
  62. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  63. data/spec/extensions/constraint_validations_spec.rb +316 -0
  64. data/spec/extensions/defaults_setter_spec.rb +24 -0
  65. data/spec/extensions/eval_inspect_spec.rb +9 -0
  66. data/spec/extensions/identity_map_spec.rb +11 -2
  67. data/spec/extensions/pg_array_ops_spec.rb +9 -0
  68. data/spec/extensions/pg_row_ops_spec.rb +11 -1
  69. data/spec/extensions/pg_row_plugin_spec.rb +4 -0
  70. data/spec/extensions/schema_dumper_spec.rb +8 -5
  71. data/spec/extensions/subclasses_spec.rb +14 -0
  72. data/spec/extensions/validation_helpers_spec.rb +15 -2
  73. data/spec/integration/dataset_test.rb +75 -1
  74. data/spec/integration/plugin_test.rb +146 -0
  75. data/spec/integration/schema_test.rb +34 -0
  76. data/spec/model/dataset_methods_spec.rb +38 -0
  77. data/spec/model/hooks_spec.rb +6 -0
  78. data/spec/model/validations_spec.rb +27 -2
  79. metadata +8 -2
@@ -9,6 +9,9 @@ describe "Sequel::Plugins::DefaultsSetter" do
9
9
  @c.columns :a
10
10
  @pr = proc{|x| db.meta_def(:schema){|*| [[:a, {:ruby_default => x}]]}; c.dataset = c.dataset; c}
11
11
  end
12
+ after do
13
+ Sequel.datetime_class = Time
14
+ end
12
15
 
13
16
  it "should set default value upon initialization" do
14
17
  @pr.call(2).new.values.should == {:a=>2}
@@ -22,6 +25,27 @@ describe "Sequel::Plugins::DefaultsSetter" do
22
25
  @pr.call(nil).new.values.should == {}
23
26
  end
24
27
 
28
+ it "should set a default of false" do
29
+ @pr.call(false).new.values.should == {:a=>false}
30
+ end
31
+
32
+ it "should handle Sequel::CURRENT_DATE default by using the current Date" do
33
+ @pr.call(Sequel::CURRENT_DATE).new.a.should == Date.today
34
+ end
35
+
36
+ it "should handle Sequel::CURRENT_TIMESTAMP default by using the current Time" do
37
+ t = @pr.call(Sequel::CURRENT_TIMESTAMP).new.a
38
+ t.should be_a_kind_of(Time)
39
+ (t - Time.now).should < 1
40
+ end
41
+
42
+ it "should handle Sequel::CURRENT_TIMESTAMP default by using the current DateTime if Sequel.datetime_class is DateTime" do
43
+ Sequel.datetime_class = DateTime
44
+ t = @pr.call(Sequel::CURRENT_TIMESTAMP).new.a
45
+ t.should be_a_kind_of(DateTime)
46
+ (t - DateTime.now).should < 1/86400.0
47
+ end
48
+
25
49
  it "should not override a given value" do
26
50
  @pr.call(2)
27
51
  @c.new('a'=>3).values.should == {:a=>3}
@@ -17,6 +17,13 @@ describe "eval_inspect extension" do
17
17
  Sequel::SQL::ColumnAll.new(:a),
18
18
  Sequel::SQL::ComplexExpression.new(:'=', :b, :a),
19
19
  Sequel::SQL::Constant.new(:a),
20
+ Sequel::CURRENT_DATE,
21
+ Sequel::CURRENT_TIMESTAMP,
22
+ Sequel::CURRENT_TIME,
23
+ Sequel::SQLTRUE,
24
+ Sequel::SQLFALSE,
25
+ Sequel::NULL,
26
+ Sequel::NOTNULL,
20
27
  Sequel::SQL::Function.new(:a, :b, :c),
21
28
  Sequel::SQL::Identifier.new(:a),
22
29
  Sequel::SQL::JoinClause.new(:inner, :b, :c),
@@ -49,6 +56,8 @@ describe "eval_inspect extension" do
49
56
  Sequel::SQL::AliasedExpression.new(Time.utc(2011, 9, 11, 10, 20, 30), :a),
50
57
  Sequel::SQL::AliasedExpression.new(Time.utc(2011, 9, 11, 10, 20, 30, 500000.125), :a),
51
58
  Sequel::SQL::AliasedExpression.new(BigDecimal.new('1.000000000000000000000000000000000000000000000001'), :a),
59
+ Sequel::SQL::AliasedExpression.new(Sequel::CURRENT_DATE, :a),
60
+ Sequel::SQL::AliasedExpression.new(Sequel::CURRENT_TIMESTAMP, :a),
52
61
  ].each do |o|
53
62
  v = eval(o.inspect)
54
63
  v.should == o
@@ -55,8 +55,17 @@ describe "Sequel::Plugins::IdentityMap" do
55
55
  @c.identity_map_key(1).should_not == @c.identity_map_key(2)
56
56
  end
57
57
 
58
- it "#identity_map_key should be different if the pk is nil" do
59
- @c.identity_map_key(nil).should_not == @c.identity_map_key(nil)
58
+ it "#identity_map_key should be nil for an empty pk values" do
59
+ @c.identity_map_key(nil).should == nil
60
+ @c.identity_map_key([]).should == nil
61
+ @c.identity_map_key([nil]).should == nil
62
+ end
63
+
64
+ it "#load should work even if model doesn't have a primary key" do
65
+ c = Class.new(@c)
66
+ c.no_primary_key
67
+ proc{c.with_identity_map{c.load({})}}.should_not raise_error
68
+ c.with_identity_map{c.load({}).should_not equal(c.load({}))}
60
69
  end
61
70
 
62
71
  it "#load should return an object if there is no current identity map" do
@@ -100,4 +100,13 @@ describe "Sequel::Postgres::ArrayOp" do
100
100
  it "should allow transforming PGArray instances into ArrayOp instances" do
101
101
  @db.literal(Sequel.pg_array([1,2]).op.push(3)).should == "(ARRAY[1,2] || 3)"
102
102
  end
103
+
104
+ it "should wrap array arguments in PGArrays" do
105
+ @db.literal(@a.contains([1, 2])).should == "(a @> ARRAY[1,2])"
106
+ @db.literal(@a.contained_by([1, 2])).should == "(a <@ ARRAY[1,2])"
107
+ @db.literal(@a.overlaps([1, 2])).should == "(a && ARRAY[1,2])"
108
+ @db.literal(@a.push([1, 2])).should == "(a || ARRAY[1,2])"
109
+ @db.literal(@a.concat([1, 2])).should == "(a || ARRAY[1,2])"
110
+ @db.literal(@a.unshift([1, 2])).should == "(ARRAY[1,2] || a)"
111
+ end
103
112
  end
@@ -22,7 +22,7 @@ describe "Sequel::Postgres::PGRowOp" do
22
22
  @db.literal(@a[1][:b]).should == "(a[1]).b"
23
23
  end
24
24
 
25
- it "#splat should return a splatted argument" do
25
+ it "#splat should return a splatted argument inside parentheses" do
26
26
  @db.literal(@a.splat).should == "(a.*)"
27
27
  end
28
28
 
@@ -34,6 +34,16 @@ describe "Sequel::Postgres::PGRowOp" do
34
34
  proc{@a[:a].splat(:b)}.should raise_error(Sequel::Error)
35
35
  end
36
36
 
37
+ it "#* should reference all members of the composite type as separate columns if given no arguments" do
38
+ @db.literal(@a.*).should == "(a).*"
39
+ @db.literal(@a[:b].*).should == "((a).b).*"
40
+ end
41
+
42
+ it "#* should use a multiplication operation if any arguments are given" do
43
+ @db.literal(@a.*(1)).should == "(a * 1)"
44
+ @db.literal(@a[:b].*(1)).should == "((a).b * 1)"
45
+ end
46
+
37
47
  it "#pg_row should be callable on literal strings" do
38
48
  @db.literal(Sequel.lit('a').pg_row[:b]).should == "(a).b"
39
49
  end
@@ -42,4 +42,8 @@ describe "Sequel::Plugins::PgRow" do
42
42
  @db.bound_variable_arg(1, nil).should == 1
43
43
  @db.bound_variable_arg(Sequel.pg_array([@c.load(:street=>'123 Foo St', :city=>'Bar City')]), nil).should == '{"(\\"123 Foo St\\",\\"Bar City\\")"}'
44
44
  end
45
+
46
+ it "should allow inserting just this model value" do
47
+ @c2.insert_sql(@c.load(:street=>'123', :city=>'Bar')).should == "INSERT INTO company VALUES (ROW('123', 'Bar')::address)"
48
+ end
45
49
  end
@@ -1,5 +1,6 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
2
 
3
+
3
4
  describe "Sequel::Schema::Generator dump methods" do
4
5
  before do
5
6
  @d = Sequel::Database.new
@@ -588,22 +589,24 @@ END_MIG
588
589
  [:c7, {:db_type=>'date', :default=>"'2008-10-29'", :type=>:date, :allow_null=>true}],
589
590
  [:c8, {:db_type=>'datetime', :default=>"'2008-10-29 10:20:30'", :type=>:datetime, :allow_null=>true}],
590
591
  [:c9, {:db_type=>'time', :default=>"'10:20:30'", :type=>:time, :allow_null=>true}],
591
- [:c10, {:db_type=>'interval', :default=>"'6 weeks'", :type=>:interval, :allow_null=>true}]]
592
+ [:c10, {:db_type=>'foo', :default=>"'6 weeks'", :type=>nil, :allow_null=>true}],
593
+ [:c11, {:db_type=>'date', :default=>"CURRENT_DATE", :type=>:date, :allow_null=>true}],
594
+ [:c12, {:db_type=>'timestamp', :default=>"now()", :type=>:datetime, :allow_null=>true}]]
592
595
  s.each{|_, c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
593
596
  s
594
597
  end
595
- @d.dump_table_schema(:t4).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal.new(\"0.1005E3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.parse(\"2008-10-29\")\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n Time :c9, :default=>Sequel::SQLTime.parse(\"10:20:30\"), :only_time=>true\n String :c10\nend"
596
- @d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal.new(\"0.1005E3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.parse(\"2008-10-29\")\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n column :c9, \"time\", :default=>Sequel::SQLTime.parse(\"10:20:30\")\n column :c10, \"interval\", :default=>Sequel::LiteralString.new(\"'6 weeks'\")\nend"
598
+ @d.dump_table_schema(:t4).gsub(/[+-]\d\d\d\d"\)/, '")').gsub(/\.0+/, '.0').should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal.new(\"0.1005E3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.new(2008, 10, 29)\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30.0\")\n Time :c9, :default=>Sequel::SQLTime.parse(\"10:20:30.0\"), :only_time=>true\n String :c10\n Date :c11, :default=>Sequel::CURRENT_DATE\n DateTime :c12, :default=>Sequel::CURRENT_TIMESTAMP\nend"
599
+ @d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d\d\d"\)/, '")').gsub(/\.0+/, '.0').should == "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal.new(\"0.1005E3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.new(2008, 10, 29)\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30.0\")\n column :c9, \"time\", :default=>Sequel::SQLTime.parse(\"10:20:30.0\")\n column :c10, \"foo\", :default=>Sequel::LiteralString.new(\"'6 weeks'\")\n column :c11, \"date\", :default=>Sequel::CURRENT_DATE\n column :c12, \"timestamp\", :default=>Sequel::CURRENT_TIMESTAMP\nend"
597
600
  end
598
601
 
599
602
  it "should not use a literal string as a fallback if using MySQL with the :same_db option" do
600
603
  @d.meta_def(:database_type){:mysql}
601
604
  @d.meta_def(:schema) do |t, *os|
602
- s = [[:c10, {:db_type=>'interval', :default=>"'6 weeks'", :type=>:interval, :allow_null=>true}]]
605
+ s = [[:c10, {:db_type=>'foo', :default=>"'6 weeks'", :type=>nil, :allow_null=>true}]]
603
606
  s.each{|_, c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
604
607
  s
605
608
  end
606
- @d.dump_table_schema(:t5, :same_db=>true).should == "create_table(:t5) do\n column :c10, \"interval\"\nend"
609
+ @d.dump_table_schema(:t5, :same_db=>true).should == "create_table(:t5) do\n column :c10, \"foo\"\nend"
607
610
  end
608
611
 
609
612
  it "should convert unknown database types to strings" do
@@ -49,4 +49,18 @@ describe Sequel::Model, "Subclasses plugin" do
49
49
  ssc1.descendents.should == [sssc1]
50
50
  sssc1.descendents.should == []
51
51
  end
52
+
53
+ specify "plugin block should be called with each subclass created" do
54
+ c = Class.new(Sequel::Model)
55
+ a = []
56
+ c.plugin(:subclasses){|sc| a << sc}
57
+ sc1 = Class.new(c)
58
+ a.should == [sc1]
59
+ sc2 = Class.new(c)
60
+ a.should == [sc1, sc2]
61
+ sc3 = Class.new(sc1)
62
+ a.should == [sc1, sc2, sc3]
63
+ sc4 = Class.new(sc3)
64
+ a.should == [sc1, sc2, sc3, sc4]
65
+ end
52
66
  end
@@ -340,7 +340,7 @@ describe "Sequel::Plugins::ValidationHelpers" do
340
340
 
341
341
  ds1 = @c.dataset.filter([[:username, '0records']])
342
342
  ds2 = ds1.exclude(:id=>1)
343
- @c.dataset.should_receive(:filter).with([[:username, '0records']]).twice.and_return(ds1)
343
+ @c.dataset.should_receive(:where).with([[:username, '0records']]).twice.and_return(ds1)
344
344
  ds1.should_receive(:exclude).with(:id=>1).once.and_return(ds2)
345
345
 
346
346
  @user = @c.load(:id=>1, :username => "0records", :password => "anothertest")
@@ -378,7 +378,7 @@ describe "Sequel::Plugins::ValidationHelpers" do
378
378
 
379
379
  ds1 = @c.dataset.filter([[:username, '0records'], [:password, 'anothertest']])
380
380
  ds2 = ds1.exclude(:id=>1)
381
- @c.dataset.should_receive(:filter).with([[:username, '0records'], [:password, 'anothertest']]).twice.and_return(ds1)
381
+ @c.dataset.should_receive(:where).with([[:username, '0records'], [:password, 'anothertest']]).twice.and_return(ds1)
382
382
  ds1.should_receive(:exclude).with(:id=>1).once.and_return(ds2)
383
383
 
384
384
  @user = @c.load(:id=>1, :username => "0records", :password => "anothertest")
@@ -402,6 +402,19 @@ describe "Sequel::Plugins::ValidationHelpers" do
402
402
  "SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND active AND (id != 3)) LIMIT 1"]
403
403
  end
404
404
 
405
+ it "should support validates_unique with a custom filter" do
406
+ @c.columns(:id, :username, :password)
407
+ @c.set_dataset MODEL_DB[:items]
408
+ @c.set_validations{validates_unique(:username, :where=>proc{|ds, obj, cols| ds.where(cols.map{|c| [Sequel.function(:lower, c), obj.send(c).downcase]})})}
409
+ @c.dataset._fetch = {:v=>0}
410
+
411
+ MODEL_DB.reset
412
+ @c.new(:username => "0RECORDS", :password => "anothertest").should be_valid
413
+ @c.load(:id=>3, :username => "0RECORDS", :password => "anothertest").should be_valid
414
+ MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (lower(username) = '0records') LIMIT 1",
415
+ "SELECT COUNT(*) AS count FROM items WHERE ((lower(username) = '0records') AND (id != 3)) LIMIT 1"]
416
+ end
417
+
405
418
  it "should support :only_if_modified option for validates_unique, and not check uniqueness for existing records if values haven't changed" do
406
419
  @c.columns(:id, :username, :password)
407
420
  @c.set_dataset MODEL_DB[:items]
@@ -428,6 +428,9 @@ describe "Dataset UNION, EXCEPT, and INTERSECT" do
428
428
  @ds2.insert(:number=>10)
429
429
  @ds2.insert(:number=>30)
430
430
  end
431
+ after do
432
+ INTEGRATION_DB.drop_table?(:i1, :i2, :i3)
433
+ end
431
434
 
432
435
  specify "should give the correct results for simple UNION, EXCEPT, and INTERSECT" do
433
436
  @ds1.union(@ds2).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30'
@@ -1399,7 +1402,7 @@ describe "Dataset string methods" do
1399
1402
  @ds.filter(Sequel.expr(:b).like('baR')).all.should == []
1400
1403
  @ds.filter(Sequel.expr(:a).like('FOO', 'BAR')).all.should == []
1401
1404
  @ds.exclude(Sequel.expr(:a).like('Foo')).all.should == [{:a=>'foo', :b=>'bar'}]
1402
- @ds.exclude(Sequel.expr(:a).like('baR')).all.should == [{:a=>'foo', :b=>'bar'}]
1405
+ @ds.exclude(Sequel.expr(:b).like('baR')).all.should == [{:a=>'foo', :b=>'bar'}]
1403
1406
  @ds.exclude(Sequel.expr(:a).like('FOO', 'BAR')).all.should == [{:a=>'foo', :b=>'bar'}]
1404
1407
  end
1405
1408
 
@@ -1413,6 +1416,45 @@ describe "Dataset string methods" do
1413
1416
  @ds.exclude(Sequel.expr(:a).ilike('FOO', 'BAR')).all.should == []
1414
1417
  end
1415
1418
 
1419
+ if INTEGRATION_DB.dataset.supports_regexp?
1420
+ it "#like with regexp return matching rows" do
1421
+ @ds.insert('foo', 'bar')
1422
+ @ds.filter(Sequel.expr(:a).like(/fo/)).all.should == [{:a=>'foo', :b=>'bar'}]
1423
+ @ds.filter(Sequel.expr(:a).like(/fo$/)).all.should == []
1424
+ @ds.filter(Sequel.expr(:a).like(/fo/, /ar/)).all.should == [{:a=>'foo', :b=>'bar'}]
1425
+ @ds.exclude(Sequel.expr(:a).like(/fo/)).all.should == []
1426
+ @ds.exclude(Sequel.expr(:a).like(/fo$/)).all.should == [{:a=>'foo', :b=>'bar'}]
1427
+ @ds.exclude(Sequel.expr(:a).like(/fo/, /ar/)).all.should == []
1428
+ end
1429
+
1430
+ it "#like with regexp should be case sensitive if regexp is case sensitive" do
1431
+ @ds.insert('foo', 'bar')
1432
+ @ds.filter(Sequel.expr(:a).like(/Fo/)).all.should == []
1433
+ @ds.filter(Sequel.expr(:b).like(/baR/)).all.should == []
1434
+ @ds.filter(Sequel.expr(:a).like(/FOO/, /BAR/)).all.should == []
1435
+ @ds.exclude(Sequel.expr(:a).like(/Fo/)).all.should == [{:a=>'foo', :b=>'bar'}]
1436
+ @ds.exclude(Sequel.expr(:b).like(/baR/)).all.should == [{:a=>'foo', :b=>'bar'}]
1437
+ @ds.exclude(Sequel.expr(:a).like(/FOO/, /BAR/)).all.should == [{:a=>'foo', :b=>'bar'}]
1438
+
1439
+ @ds.filter(Sequel.expr(:a).like(/Fo/i)).all.should == [{:a=>'foo', :b=>'bar'}]
1440
+ @ds.filter(Sequel.expr(:b).like(/baR/i)).all.should == [{:a=>'foo', :b=>'bar'}]
1441
+ @ds.filter(Sequel.expr(:a).like(/FOO/i, /BAR/i)).all.should == [{:a=>'foo', :b=>'bar'}]
1442
+ @ds.exclude(Sequel.expr(:a).like(/Fo/i)).all.should == []
1443
+ @ds.exclude(Sequel.expr(:b).like(/baR/i)).all.should == []
1444
+ @ds.exclude(Sequel.expr(:a).like(/FOO/i, /BAR/i)).all.should == []
1445
+ end
1446
+
1447
+ it "#ilike with regexp should return matching rows, in a case insensitive manner" do
1448
+ @ds.insert('foo', 'bar')
1449
+ @ds.filter(Sequel.expr(:a).ilike(/Fo/)).all.should == [{:a=>'foo', :b=>'bar'}]
1450
+ @ds.filter(Sequel.expr(:b).ilike(/baR/)).all.should == [{:a=>'foo', :b=>'bar'}]
1451
+ @ds.filter(Sequel.expr(:a).ilike(/FOO/, /BAR/)).all.should == [{:a=>'foo', :b=>'bar'}]
1452
+ @ds.exclude(Sequel.expr(:a).ilike(/Fo/)).all.should == []
1453
+ @ds.exclude(Sequel.expr(:b).ilike(/baR/)).all.should == []
1454
+ @ds.exclude(Sequel.expr(:a).ilike(/FOO/, /BAR/)).all.should == []
1455
+ end
1456
+ end
1457
+
1416
1458
  it "should work with strings created with Sequel.join" do
1417
1459
  @ds.insert('foo', 'bar')
1418
1460
  @ds.get(Sequel.join([:a, "bar"])).should == 'foobar'
@@ -1520,3 +1562,35 @@ if INTEGRATION_DB.dataset.supports_modifying_joins?
1520
1562
  end
1521
1563
  end
1522
1564
  end
1565
+
1566
+ describe "Emulated functions" do
1567
+ before(:all) do
1568
+ @db = INTEGRATION_DB
1569
+ @db.create_table!(:a){String :a}
1570
+ @ds = @db[:a]
1571
+ end
1572
+ after(:all) do
1573
+ @db.drop_table?(:a)
1574
+ end
1575
+ after do
1576
+ @ds.delete
1577
+ end
1578
+
1579
+ specify "Sequel.char_length should return the length of characters in the string" do
1580
+ @ds.get(Sequel.char_length(:a)).should == nil
1581
+ @ds.insert(:a=>'foo')
1582
+ @ds.get(Sequel.char_length(:a)).should == 3
1583
+ # Check behavior with leading/trailing blanks
1584
+ @ds.update(:a=>' foo22 ')
1585
+ @ds.get(Sequel.char_length(:a)).should == 7
1586
+ end
1587
+
1588
+ specify "Sequel.trim should return the string with spaces trimmed from both sides" do
1589
+ @ds.get(Sequel.trim(:a)).should == nil
1590
+ @ds.insert(:a=>'foo')
1591
+ @ds.get(Sequel.trim(:a)).should == 'foo'
1592
+ # Check behavior with leading/trailing blanks
1593
+ @ds.update(:a=>' foo22 ')
1594
+ @ds.get(Sequel.trim(:a)).should == 'foo22'
1595
+ end
1596
+ end
@@ -1617,3 +1617,149 @@ describe "Caching plugins" do
1617
1617
  it_should_behave_like "a caching plugin"
1618
1618
  end
1619
1619
  end
1620
+
1621
+ describe "Sequel::Plugins::ConstraintValidations" do
1622
+ before(:all) do
1623
+ @db = INTEGRATION_DB
1624
+ @db.extension(:constraint_validations)
1625
+ @db.create_constraint_validations_table
1626
+ @ds = @db[:cv_test]
1627
+ @regexp = regexp = @db.dataset.supports_regexp?
1628
+ @validate_block = proc do
1629
+ presence :pre, :name=>:p
1630
+ exact_length 5, :exactlen, :name=>:el
1631
+ min_length 5, :minlen, :name=>:minl
1632
+ max_length 5, :maxlen, :name=>:maxl
1633
+ length_range 3..5, :lenrange, :name=>:lr
1634
+ if regexp
1635
+ format /^foo\d+/, :form, :name=>:f
1636
+ end
1637
+ like 'foo%', :lik, :name=>:l
1638
+ ilike 'foo%', :ilik, :name=>:il
1639
+ includes %w'abc def', :inc, :name=>:i
1640
+ unique :uniq, :name=>:u
1641
+ max_length 6, :minlen, :name=>:maxl2
1642
+ end
1643
+ @valid_row = {:pre=>'a', :exactlen=>'12345', :minlen=>'12345', :maxlen=>'12345', :lenrange=>'1234', :lik=>'fooabc', :ilik=>'FooABC', :inc=>'abc', :uniq=>'u'}
1644
+ @violations = [
1645
+ [:pre, [nil, '', ' ']],
1646
+ [:exactlen, [nil, '', '1234', '123456']],
1647
+ [:minlen, [nil, '', '1234']],
1648
+ [:maxlen, [nil, '123456']],
1649
+ [:lenrange, [nil, '', '12', '123456']],
1650
+ [:lik, [nil, '', 'fo', 'fotabc', 'FOOABC']],
1651
+ [:ilik, [nil, '', 'fo', 'fotabc']],
1652
+ [:inc, [nil, '', 'ab', 'abcd']],
1653
+ ]
1654
+
1655
+ if @regexp
1656
+ @valid_row[:form] = 'foo1'
1657
+ @violations << [:form, [nil, '', 'foo', 'fooa']]
1658
+ end
1659
+ end
1660
+ after(:all) do
1661
+ @db.drop_constraint_validations_table
1662
+ end
1663
+
1664
+ shared_examples_for "constraint validations" do
1665
+ cspecify "should set up constraints that work even outside the model", :mysql do
1666
+ proc{@ds.insert(@valid_row)}.should_not raise_error
1667
+
1668
+ # Test for unique constraint
1669
+ proc{@ds.insert(@valid_row)}.should raise_error(Sequel::DatabaseError)
1670
+
1671
+ @ds.delete
1672
+ @violations.each do |col, vals|
1673
+ try = @valid_row.dup
1674
+ vals += ['1234567'] if col == :minlen
1675
+ vals.each do |val|
1676
+ try[col] = val
1677
+ proc{@ds.insert(try)}.should raise_error(Sequel::DatabaseError)
1678
+ end
1679
+ end
1680
+
1681
+ # Test for dropping of constraint
1682
+ @db.alter_table(:cv_test){validate{drop :maxl2}}
1683
+ proc{@ds.insert(@valid_row.merge(:minlen=>'1234567'))}.should_not raise_error
1684
+ end
1685
+
1686
+ it "should set up automatic validations inside the model" do
1687
+ c = Class.new(Sequel::Model(@ds))
1688
+ c.plugin :constraint_validations
1689
+ c.delete
1690
+ proc{c.create(@valid_row)}.should_not raise_error
1691
+
1692
+ # Test for unique validation
1693
+ c.new(@valid_row).should_not be_valid
1694
+
1695
+ c.delete
1696
+ @violations.each do |col, vals|
1697
+ try = @valid_row.dup
1698
+ vals.each do |val|
1699
+ try[col] = val
1700
+ c.new(try).should_not be_valid
1701
+ end
1702
+ end
1703
+ end
1704
+ end
1705
+
1706
+ describe "via create_table" do
1707
+ before(:all) do
1708
+ regexp = @regexp
1709
+ validate_block = @validate_block
1710
+ @db.create_table!(:cv_test) do
1711
+ primary_key :id
1712
+ String :pre
1713
+ String :exactlen
1714
+ String :minlen
1715
+ String :maxlen
1716
+ String :lenrange
1717
+ if regexp
1718
+ String :form
1719
+ end
1720
+ String :lik
1721
+ String :ilik
1722
+ String :inc
1723
+ String :uniq, :null=>false
1724
+ validate(&validate_block)
1725
+ end
1726
+ end
1727
+ after(:all) do
1728
+ @db.drop_table?(:cv_test)
1729
+ @db.drop_constraint_validations_for(:table=>:cv_test)
1730
+ end
1731
+
1732
+ it_should_behave_like "constraint validations"
1733
+ end
1734
+
1735
+ describe "via alter_table" do
1736
+ before(:all) do
1737
+ regexp = @regexp
1738
+ validate_block = @validate_block
1739
+ @db.create_table!(:cv_test) do
1740
+ primary_key :id
1741
+ String :lik
1742
+ String :ilik
1743
+ String :inc
1744
+ String :uniq, :null=>false
1745
+ end
1746
+ @db.alter_table(:cv_test) do
1747
+ add_column :pre, String
1748
+ add_column :exactlen, String
1749
+ add_column :minlen, String
1750
+ add_column :maxlen, String
1751
+ add_column :lenrange, String
1752
+ if regexp
1753
+ add_column :form, String
1754
+ end
1755
+ validate(&validate_block)
1756
+ end
1757
+ end
1758
+ after(:all) do
1759
+ @db.drop_table?(:cv_test)
1760
+ @db.drop_constraint_validations_for(:table=>:cv_test)
1761
+ end
1762
+
1763
+ it_should_behave_like "constraint validations"
1764
+ end
1765
+ end