sequel 3.38.0 → 3.39.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 (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