sequel 3.38.0 → 3.39.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +62 -0
- data/README.rdoc +2 -2
- data/bin/sequel +12 -2
- data/doc/advanced_associations.rdoc +1 -1
- data/doc/association_basics.rdoc +13 -0
- data/doc/release_notes/3.39.0.txt +237 -0
- data/doc/schema_modification.rdoc +4 -4
- data/lib/sequel/adapters/jdbc/derby.rb +1 -0
- data/lib/sequel/adapters/mock.rb +5 -0
- data/lib/sequel/adapters/mysql.rb +8 -1
- data/lib/sequel/adapters/mysql2.rb +10 -3
- data/lib/sequel/adapters/postgres.rb +72 -8
- data/lib/sequel/adapters/shared/db2.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +57 -0
- data/lib/sequel/adapters/shared/mysql.rb +95 -19
- data/lib/sequel/adapters/shared/oracle.rb +14 -0
- data/lib/sequel/adapters/shared/postgres.rb +63 -24
- data/lib/sequel/adapters/shared/sqlite.rb +6 -9
- data/lib/sequel/connection_pool/sharded_threaded.rb +8 -3
- data/lib/sequel/connection_pool/threaded.rb +9 -4
- data/lib/sequel/database/query.rb +60 -48
- data/lib/sequel/database/schema_generator.rb +13 -6
- data/lib/sequel/database/schema_methods.rb +65 -12
- data/lib/sequel/dataset/actions.rb +22 -4
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/graph.rb +2 -3
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/query.rb +0 -2
- data/lib/sequel/dataset/sql.rb +33 -12
- data/lib/sequel/extensions/constraint_validations.rb +451 -0
- data/lib/sequel/extensions/eval_inspect.rb +17 -2
- data/lib/sequel/extensions/pg_array_ops.rb +15 -5
- data/lib/sequel/extensions/pg_interval.rb +2 -2
- data/lib/sequel/extensions/pg_row_ops.rb +18 -0
- data/lib/sequel/extensions/schema_dumper.rb +3 -11
- data/lib/sequel/model/associations.rb +3 -2
- data/lib/sequel/model/base.rb +57 -13
- data/lib/sequel/model/exceptions.rb +20 -2
- data/lib/sequel/plugins/constraint_validations.rb +198 -0
- data/lib/sequel/plugins/defaults_setter.rb +15 -1
- data/lib/sequel/plugins/dirty.rb +2 -2
- data/lib/sequel/plugins/identity_map.rb +12 -8
- data/lib/sequel/plugins/subclasses.rb +19 -1
- data/lib/sequel/plugins/tree.rb +3 -3
- data/lib/sequel/plugins/validation_helpers.rb +24 -4
- data/lib/sequel/sql.rb +64 -24
- data/lib/sequel/timezones.rb +10 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +26 -25
- data/spec/adapters/mysql_spec.rb +57 -23
- data/spec/adapters/oracle_spec.rb +34 -49
- data/spec/adapters/postgres_spec.rb +226 -128
- data/spec/adapters/sqlite_spec.rb +50 -49
- data/spec/core/connection_pool_spec.rb +22 -0
- data/spec/core/database_spec.rb +53 -47
- data/spec/core/dataset_spec.rb +36 -32
- data/spec/core/expression_filters_spec.rb +14 -2
- data/spec/core/mock_adapter_spec.rb +4 -0
- data/spec/core/object_graph_spec.rb +0 -13
- data/spec/core/schema_spec.rb +64 -5
- data/spec/core_extensions_spec.rb +1 -0
- data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
- data/spec/extensions/constraint_validations_spec.rb +316 -0
- data/spec/extensions/defaults_setter_spec.rb +24 -0
- data/spec/extensions/eval_inspect_spec.rb +9 -0
- data/spec/extensions/identity_map_spec.rb +11 -2
- data/spec/extensions/pg_array_ops_spec.rb +9 -0
- data/spec/extensions/pg_row_ops_spec.rb +11 -1
- data/spec/extensions/pg_row_plugin_spec.rb +4 -0
- data/spec/extensions/schema_dumper_spec.rb +8 -5
- data/spec/extensions/subclasses_spec.rb +14 -0
- data/spec/extensions/validation_helpers_spec.rb +15 -2
- data/spec/integration/dataset_test.rb +75 -1
- data/spec/integration/plugin_test.rb +146 -0
- data/spec/integration/schema_test.rb +34 -0
- data/spec/model/dataset_methods_spec.rb +38 -0
- data/spec/model/hooks_spec.rb +6 -0
- data/spec/model/validations_spec.rb +27 -2
- 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
|
59
|
-
@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=>'
|
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
|
596
|
-
@d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d
|
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=>'
|
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, \"
|
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(:
|
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(:
|
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(:
|
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
|