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.
- 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
|