sequel 5.7.1 → 5.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +53 -1
- data/doc/association_basics.rdoc +2 -2
- data/doc/migration.rdoc +11 -10
- data/doc/postgresql.rdoc +71 -0
- data/doc/release_notes/5.8.0.txt +170 -0
- data/lib/sequel/adapters/jdbc.rb +6 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
- data/lib/sequel/adapters/mysql2.rb +2 -1
- data/lib/sequel/adapters/postgres.rb +32 -10
- data/lib/sequel/adapters/shared/mssql.rb +11 -11
- data/lib/sequel/adapters/shared/mysql.rb +51 -6
- data/lib/sequel/adapters/shared/oracle.rb +12 -2
- data/lib/sequel/adapters/shared/postgres.rb +97 -30
- data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -2
- data/lib/sequel/adapters/shared/sqlite.rb +6 -1
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/query.rb +48 -19
- data/lib/sequel/exceptions.rb +7 -0
- data/lib/sequel/extensions/connection_expiration.rb +8 -3
- data/lib/sequel/extensions/pg_enum.rb +28 -5
- data/lib/sequel/plugins/association_proxies.rb +16 -4
- data/lib/sequel/plugins/error_splitter.rb +16 -11
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +260 -0
- data/lib/sequel/plugins/subclasses.rb +1 -1
- data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mysql_spec.rb +0 -1
- data/spec/adapters/postgres_spec.rb +169 -4
- data/spec/adapters/sqlite_spec.rb +13 -0
- data/spec/core/dataset_spec.rb +21 -0
- data/spec/extensions/association_proxies_spec.rb +21 -7
- data/spec/extensions/connection_expiration_spec.rb +13 -1
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +165 -0
- data/spec/extensions/pg_enum_spec.rb +26 -22
- data/spec/extensions/tactical_eager_loading_spec.rb +11 -0
- data/spec/integration/dataset_test.rb +30 -6
- data/spec/integration/plugin_test.rb +2 -2
- metadata +6 -2
@@ -21,7 +21,7 @@ module Sequel
|
|
21
21
|
# You can also finalize the associations and then freeze the classes
|
22
22
|
# in all descendent classes. Doing so is a recommended practice after
|
23
23
|
# all models have been defined in production and testing, and this makes
|
24
|
-
# it easier
|
24
|
+
# it easier than keeping track of the classes to finalize and freeze
|
25
25
|
# manually:
|
26
26
|
#
|
27
27
|
# c.freeze_descendants
|
@@ -94,7 +94,7 @@ module Sequel
|
|
94
94
|
def load_associated_objects(opts, dynamic_opts=OPTS, &block)
|
95
95
|
dynamic_opts = load_association_objects_options(dynamic_opts, &block)
|
96
96
|
name = opts[:name]
|
97
|
-
if (!associations.include?(name) || dynamic_opts[:eager_reload]) && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
97
|
+
if (!associations.include?(name) || dynamic_opts[:eager_reload]) && opts[:allow_eager] != false && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
98
98
|
begin
|
99
99
|
retrieved_by.send(:eager_load, retrieved_with.reject(&:frozen?), name=>dynamic_opts[:eager] || {})
|
100
100
|
rescue Sequel::UndefinedAssociation
|
data/lib/sequel/version.rb
CHANGED
@@ -5,10 +5,10 @@ module Sequel
|
|
5
5
|
MAJOR = 5
|
6
6
|
# The minor version of Sequel. Bumped for every non-patch level
|
7
7
|
# release, generally around once a month.
|
8
|
-
MINOR =
|
8
|
+
MINOR = 8
|
9
9
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
10
10
|
# releases that fix regressions from previous versions.
|
11
|
-
TINY =
|
11
|
+
TINY = 0
|
12
12
|
|
13
13
|
# The version of Sequel you are using, as a string (e.g. "2.11.0")
|
14
14
|
VERSION = [MAJOR, MINOR, TINY].join('.').freeze
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -185,7 +185,6 @@ describe "A MySQL dataset" do
|
|
185
185
|
it "should have explain output" do
|
186
186
|
@d.explain.must_be_kind_of(String)
|
187
187
|
@d.explain(:extended=>true).must_be_kind_of(String)
|
188
|
-
@d.explain.wont_equal @d.explain(:extended=>true)
|
189
188
|
end
|
190
189
|
|
191
190
|
it "should correctly literalize strings with comment backslashes in them" do
|
@@ -93,6 +93,17 @@ describe "PostgreSQL", '#create_table' do
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
it "should have #check_constraints method for getting check constraints" do
|
97
|
+
@db.create_table(:tmp_dolls) do
|
98
|
+
Integer :i
|
99
|
+
Integer :j
|
100
|
+
constraint(:ic, Sequel[:i] > 2)
|
101
|
+
constraint(:jc, Sequel[:j] > 2)
|
102
|
+
constraint(:ijc, Sequel[:i] - Sequel[:j] > 2)
|
103
|
+
end
|
104
|
+
@db.check_constraints(:tmp_dolls).must_equal(:ic=>{:definition=>"CHECK ((i > 2))", :columns=>[:i]}, :jc=>{:definition=>"CHECK ((j > 2))", :columns=>[:j]}, :ijc=>{:definition=>"CHECK (((i - j) > 2))", :columns=>[:i, :j]})
|
105
|
+
end
|
106
|
+
|
96
107
|
it "should not allow to pass both :temp and :unlogged" do
|
97
108
|
proc do
|
98
109
|
@db.create_table(:temp_unlogged_dolls, :temp => true, :unlogged => true){text :name}
|
@@ -420,6 +431,7 @@ describe "A PostgreSQL database" do
|
|
420
431
|
|
421
432
|
it "should parse foreign keys for tables in a schema" do
|
422
433
|
@db.foreign_key_list(Sequel[:public][:testfk]).must_equal [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>Sequel.qualify(:public, :testfk), :name=>:testfk_i_fkey}]
|
434
|
+
@db.foreign_key_list(Sequel[:public][:testfk], :schema=>false).must_equal [{:on_delete=>:no_action, :on_update=>:no_action, :columns=>[:i], :key=>[:id], :deferrable=>false, :table=>:testfk, :name=>:testfk_i_fkey, :schema=>:public}]
|
423
435
|
end
|
424
436
|
|
425
437
|
it "should return uuid fields as strings" do
|
@@ -440,6 +452,32 @@ describe "A PostgreSQL database" do
|
|
440
452
|
end if uses_pg && DB.server_version >= 90000
|
441
453
|
end
|
442
454
|
|
455
|
+
describe "A PostgreSQL database " do
|
456
|
+
after do
|
457
|
+
DB.drop_table?(:b, :a)
|
458
|
+
end
|
459
|
+
|
460
|
+
it "should parse foreign keys referencing current table using :reverse option" do
|
461
|
+
DB.create_table!(:a) do
|
462
|
+
primary_key :id
|
463
|
+
Integer :i
|
464
|
+
Integer :j
|
465
|
+
foreign_key :a_id, :a, :foreign_key_constraint_name=>:a_a
|
466
|
+
unique [:i, :j]
|
467
|
+
end
|
468
|
+
DB.create_table!(:b) do
|
469
|
+
foreign_key :a_id, :a, :foreign_key_constraint_name=>:a_a
|
470
|
+
Integer :c
|
471
|
+
Integer :d
|
472
|
+
foreign_key [:c, :d], :a, :key=>[:j, :i], :name=>:a_c_d
|
473
|
+
end
|
474
|
+
DB.foreign_key_list(:a, :reverse=>true).must_equal [
|
475
|
+
{:name=>:a_a, :columns=>[:a_id], :key=>[:id], :on_update=>:no_action, :on_delete=>:no_action, :deferrable=>false, :table=>:a, :schema=>:public},
|
476
|
+
{:name=>:a_a, :columns=>[:a_id], :key=>[:id], :on_update=>:no_action, :on_delete=>:no_action, :deferrable=>false, :table=>:b, :schema=>:public},
|
477
|
+
{:name=>:a_c_d, :columns=>[:c, :d], :key=>[:j, :i], :on_update=>:no_action, :on_delete=>:no_action, :deferrable=>false, :table=>:b, :schema=>:public}]
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
443
481
|
describe "A PostgreSQL database with domain types" do
|
444
482
|
before(:all) do
|
445
483
|
@db = DB
|
@@ -646,8 +684,14 @@ describe "A PostgreSQL dataset" do
|
|
646
684
|
@db.transaction(:isolation=>:serializable, :deferrable=>false, :read_only=>false){}
|
647
685
|
end if DB.server_version >= 90100
|
648
686
|
|
687
|
+
it "should support parsing partial indexes with :include_partial option" do
|
688
|
+
@db.add_index :test, [:name, :value], :where=>(Sequel[:value] > 10), :name=>:tnv_partial
|
689
|
+
@db.indexes(:test)[:tnv_partial].must_be_nil
|
690
|
+
@db.indexes(:test, :include_partial=>true)[:tnv_partial].must_equal(:columns=>[:name, :value], :unique=>false, :deferrable=>nil)
|
691
|
+
end
|
692
|
+
|
649
693
|
it "should support creating indexes concurrently" do
|
650
|
-
@db.add_index :test, [:name, :value], :concurrently=>true
|
694
|
+
@db.add_index :test, [:name, :value], :concurrently=>true, :name=>'tnv0'
|
651
695
|
end
|
652
696
|
|
653
697
|
it "should support dropping indexes only if they already exist" do
|
@@ -1336,7 +1380,7 @@ describe "Postgres::Database schema qualified tables" do
|
|
1336
1380
|
h.last.first.must_equal :j
|
1337
1381
|
|
1338
1382
|
@db.indexes(:schema_test).must_equal(:public_test_sti=>{:unique=>false, :columns=>[:j], :deferrable=>nil})
|
1339
|
-
@db.foreign_key_list(:schema_test).must_equal [{:on_update=>:no_action, :columns=>[:j], :deferrable=>false, :key=>[:id], :table=>:schema_test, :on_delete=>:no_action, :name=>:schema_test_j_fkey}]
|
1383
|
+
@db.foreign_key_list(:schema_test).must_equal [{:on_update=>:no_action, :columns=>[:j], :deferrable=>false, :key=>[:id], :table=>:schema_test, :on_delete=>:no_action, :name=>:schema_test_j_fkey, :schema=>:public}]
|
1340
1384
|
ensure
|
1341
1385
|
@db.drop_table?(Sequel[:public][:schema_test])
|
1342
1386
|
end
|
@@ -3758,7 +3802,8 @@ end if uses_pg && DB.server_version >= 90000
|
|
3758
3802
|
describe 'PostgreSQL enum types' do
|
3759
3803
|
before do
|
3760
3804
|
@db = DB
|
3761
|
-
@
|
3805
|
+
@initial_enum_values = %w'a b c d'
|
3806
|
+
@db.create_enum(:test_enum, @initial_enum_values)
|
3762
3807
|
|
3763
3808
|
@db.create_table!(:test_enumt) do
|
3764
3809
|
test_enum :t
|
@@ -3772,7 +3817,7 @@ describe 'PostgreSQL enum types' do
|
|
3772
3817
|
it "should return correct entries in the schema" do
|
3773
3818
|
s = @db.schema(:test_enumt)
|
3774
3819
|
s.first.last[:type].must_equal :enum
|
3775
|
-
s.first.last[:enum_values].must_equal
|
3820
|
+
s.first.last[:enum_values].must_equal @initial_enum_values
|
3776
3821
|
end
|
3777
3822
|
|
3778
3823
|
it "should add array parsers for enum values" do
|
@@ -3793,6 +3838,13 @@ describe 'PostgreSQL enum types' do
|
|
3793
3838
|
@db.add_enum_value(:test_enum, 'a', :if_not_exists=>true) if @db.server_version >= 90300
|
3794
3839
|
@db.schema(:test_enumt, :reload=>true).first.last[:enum_values].must_equal %w'a f g b c d e'
|
3795
3840
|
end if DB.server_version >= 90100
|
3841
|
+
|
3842
|
+
it "should rename existing enum" do
|
3843
|
+
@db.rename_enum(:test_enum, :new_enum)
|
3844
|
+
@db.schema(:test_enumt, :reload=>true).first.last[:db_type].must_equal 'new_enum'
|
3845
|
+
@db.schema(:test_enumt, :reload=>true).first.last[:enum_values].must_equal @initial_enum_values
|
3846
|
+
@db.rename_enum(:new_enum, :test_enum)
|
3847
|
+
end
|
3796
3848
|
end
|
3797
3849
|
|
3798
3850
|
describe "PostgreSQL stored procedures for datasets" do
|
@@ -3833,3 +3885,116 @@ describe "PostgreSQL stored procedures for datasets" do
|
|
3833
3885
|
@ds.call(:all).must_equal [{:id=>1, :numb=>100}]
|
3834
3886
|
end
|
3835
3887
|
end if DB.adapter_scheme == :jdbc
|
3888
|
+
|
3889
|
+
describe "pg_auto_constraint_validations plugin" do
|
3890
|
+
before(:all) do
|
3891
|
+
@db = DB
|
3892
|
+
@db.create_table!(:test1) do
|
3893
|
+
Integer :id, :primary_key=>true
|
3894
|
+
Integer :i, :unique=>true, :null=>false
|
3895
|
+
constraint :valid_i, Sequel[:i] < 10
|
3896
|
+
constraint(:valid_i_id, Sequel[:i] + Sequel[:id] < 20)
|
3897
|
+
end
|
3898
|
+
@db.create_table!(:test2) do
|
3899
|
+
Integer :test2_id, :primary_key=>true
|
3900
|
+
foreign_key :test1_id, :test1
|
3901
|
+
index [:test1_id], :unique=>true, :where=>(Sequel[:test1_id] < 10)
|
3902
|
+
end
|
3903
|
+
@c1 = Sequel::Model(:test1)
|
3904
|
+
@c2 = Sequel::Model(:test2)
|
3905
|
+
@c1.plugin :update_primary_key
|
3906
|
+
@c1.plugin :pg_auto_constraint_validations
|
3907
|
+
@c2.plugin :pg_auto_constraint_validations
|
3908
|
+
@c1.unrestrict_primary_key
|
3909
|
+
@c2.unrestrict_primary_key
|
3910
|
+
end
|
3911
|
+
before do
|
3912
|
+
@c2.dataset.delete
|
3913
|
+
@c1.dataset.delete
|
3914
|
+
@c1.insert(:id=>1, :i=>2)
|
3915
|
+
@c2.insert(:test2_id=>3, :test1_id=>1)
|
3916
|
+
end
|
3917
|
+
after(:all) do
|
3918
|
+
@db.drop_table?(:test2, :test1)
|
3919
|
+
end
|
3920
|
+
|
3921
|
+
it "should handle check constraint failures as validation errors when creating" do
|
3922
|
+
o = @c1.new(:id=>5, :i=>12)
|
3923
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3924
|
+
o.errors.must_equal(:i=>['is invalid'])
|
3925
|
+
end
|
3926
|
+
|
3927
|
+
it "should handle check constraint failures as validation errors when updating" do
|
3928
|
+
o = @c1.new(:id=>5, :i=>3)
|
3929
|
+
o.save
|
3930
|
+
proc{o.update(:i=>12)}.must_raise Sequel::ValidationFailed
|
3931
|
+
o.errors.must_equal(:i=>['is invalid'])
|
3932
|
+
end
|
3933
|
+
|
3934
|
+
it "should handle unique constraint failures as validation errors when creating" do
|
3935
|
+
o = @c1.new(:id=>5, :i=>2)
|
3936
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3937
|
+
o.errors.must_equal(:i=>['is already taken'])
|
3938
|
+
end
|
3939
|
+
|
3940
|
+
it "should handle unique constraint failures as validation errors when updating" do
|
3941
|
+
o = @c1.new(:id=>5, :i=>3)
|
3942
|
+
o.save
|
3943
|
+
proc{o.update(:i=>2)}.must_raise Sequel::ValidationFailed
|
3944
|
+
o.errors.must_equal(:i=>['is already taken'])
|
3945
|
+
end
|
3946
|
+
|
3947
|
+
it "should handle unique constraint failures as validation errors for partial unique indexes" do
|
3948
|
+
@c1.create(:id=>2, :i=>3)
|
3949
|
+
@c2.create(:test2_id=>6, :test1_id=>2)
|
3950
|
+
o = @c2.new(:test2_id=>5, :test1_id=>2)
|
3951
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3952
|
+
o.errors.must_equal(:test1_id=>['is already taken'])
|
3953
|
+
end
|
3954
|
+
|
3955
|
+
it "should handle not null constraint failures as validation errors when creating" do
|
3956
|
+
o = @c1.new(:id=>5)
|
3957
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3958
|
+
o.errors.must_equal(:i=>['is not present'])
|
3959
|
+
end
|
3960
|
+
|
3961
|
+
it "should handle not null constraint failures as validation errors when updating" do
|
3962
|
+
o = @c1.new(:id=>5, :i=>3)
|
3963
|
+
o.save
|
3964
|
+
proc{o.update(:i=>nil)}.must_raise Sequel::ValidationFailed
|
3965
|
+
o.errors.must_equal(:i=>['is not present'])
|
3966
|
+
end
|
3967
|
+
|
3968
|
+
it "should handle foreign key constraint failures as validation errors when creating" do
|
3969
|
+
o = @c2.new(:test2_id=>4, :test1_id=>2)
|
3970
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3971
|
+
o.errors.must_equal(:test1_id=>['is invalid'])
|
3972
|
+
end
|
3973
|
+
|
3974
|
+
it "should handle foreign key constraint failures as validation errors when updating" do
|
3975
|
+
o = @c2.first
|
3976
|
+
proc{o.update(:test1_id=>2)}.must_raise Sequel::ValidationFailed
|
3977
|
+
o.errors.must_equal(:test1_id=>['is invalid'])
|
3978
|
+
end
|
3979
|
+
|
3980
|
+
it "should handle foreign key constraint failures in other tables as validation errors when updating" do
|
3981
|
+
o = @c1[1]
|
3982
|
+
proc{o.update(:id=>2)}.must_raise Sequel::ValidationFailed
|
3983
|
+
o.errors.must_equal(:id=>['cannot be changed currently'])
|
3984
|
+
end
|
3985
|
+
|
3986
|
+
it "should handle multi-column constraint failures as validation errors" do
|
3987
|
+
c = Class.new(@c1)
|
3988
|
+
o = c.new(:id=>18, :i=>8)
|
3989
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3990
|
+
[{[:i, :id]=>['is invalid']}, {[:id, :i]=>['is invalid']}].must_include o.errors
|
3991
|
+
end
|
3992
|
+
|
3993
|
+
it "should handle multi-column constraint failures as validation errors when using the error_splitter plugin" do
|
3994
|
+
c = Class.new(@c1)
|
3995
|
+
c.plugin :error_splitter
|
3996
|
+
o = c.new(:id=>18, :i=>8)
|
3997
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
3998
|
+
o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
|
3999
|
+
end
|
4000
|
+
end if DB.respond_to?(:error_info)
|
@@ -580,6 +580,19 @@ describe "A SQLite database" do
|
|
580
580
|
proc{@db[:test2].insert(:name=>'a')}.must_raise(Sequel::ConstraintViolation, Sequel::UniqueConstraintViolation)
|
581
581
|
end
|
582
582
|
|
583
|
+
it "should not ignore adding new constraints when adding not null constraints" do
|
584
|
+
@db.alter_table :test2 do
|
585
|
+
set_column_not_null :value
|
586
|
+
add_constraint(:value_range1, :value => 3..5)
|
587
|
+
add_constraint(:value_range2, :value => 0..9)
|
588
|
+
end
|
589
|
+
|
590
|
+
@db[:test2].insert(:value => 4)
|
591
|
+
proc{@db[:test2].insert(:value => 1)}.must_raise(Sequel::ConstraintViolation)
|
592
|
+
proc{@db[:test2].insert(:value => nil)}.must_raise(Sequel::ConstraintViolation)
|
593
|
+
@db[:test2].select_order_map(:value).must_equal [4]
|
594
|
+
end
|
595
|
+
|
583
596
|
it "should show unique constraints in Database#indexes" do
|
584
597
|
@db.alter_table(:test2){add_unique_constraint :name}
|
585
598
|
@db.indexes(:test2).values.first[:columns].must_equal [:name]
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -4784,6 +4784,27 @@ describe "Dataset#lock_style and for_update" do
|
|
4784
4784
|
end
|
4785
4785
|
end
|
4786
4786
|
|
4787
|
+
describe "Dataset#nowait" do
|
4788
|
+
before do
|
4789
|
+
@ds = Sequel.mock.dataset.from(:t).for_update
|
4790
|
+
end
|
4791
|
+
|
4792
|
+
it "should raise an error if not supported" do
|
4793
|
+
proc{@ds.nowait}.must_raise Sequel::Error
|
4794
|
+
end
|
4795
|
+
|
4796
|
+
it "should use the nowait SYNTAX if supported" do
|
4797
|
+
@ds = @ds.with_extend do
|
4798
|
+
def supports_nowait?; true end
|
4799
|
+
def select_lock_sql(sql) super; sql << " NOWAIT" if @opts[:nowait] end
|
4800
|
+
end
|
4801
|
+
@ds.sql.must_equal "SELECT * FROM t FOR UPDATE"
|
4802
|
+
3.times do
|
4803
|
+
@ds.nowait.sql.must_equal "SELECT * FROM t FOR UPDATE NOWAIT"
|
4804
|
+
end
|
4805
|
+
end
|
4806
|
+
end
|
4807
|
+
|
4787
4808
|
describe "Dataset#skip_locked" do
|
4788
4809
|
before do
|
4789
4810
|
@ds = Sequel.mock.dataset.from(:t).for_update
|
@@ -23,9 +23,23 @@ describe "Sequel::Plugins::AssociationProxies" do
|
|
23
23
|
@i.associations.has_key?(:tags).must_equal true
|
24
24
|
end
|
25
25
|
|
26
|
+
if RUBY_VERSION < '2.6'
|
27
|
+
deprecated "should issue deprecation warning when using filter on association proxy on ruby <2.6" do
|
28
|
+
@i.associations.has_key?(:tags).must_equal false
|
29
|
+
@t.filter{|x| false}.sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND 'f')"
|
30
|
+
@i.associations.has_key?(:tags).must_equal false
|
31
|
+
end
|
32
|
+
else
|
33
|
+
it "should treat filter on association proxy as array method on ruby 2.6+" do
|
34
|
+
@i.associations.has_key?(:tags).must_equal false
|
35
|
+
@t.filter{|x| false}.must_equal []
|
36
|
+
@i.associations.has_key?(:tags).must_equal true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
26
40
|
it "should send method calls to the association dataset if sent a non-array method" do
|
27
41
|
@i.associations.has_key?(:tags).must_equal false
|
28
|
-
@t.
|
42
|
+
@t.where(:a=>1).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
|
29
43
|
@i.associations.has_key?(:tags).must_equal false
|
30
44
|
end
|
31
45
|
|
@@ -35,8 +49,8 @@ describe "Sequel::Plugins::AssociationProxies" do
|
|
35
49
|
end
|
36
50
|
@i.associations.has_key?(:tags).must_equal false
|
37
51
|
@t.where(:a=>1).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
|
38
|
-
@t.
|
39
|
-
@t.
|
52
|
+
@t.where(Sequel.lit('a = 1')).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
|
53
|
+
@t.where{{:a=>1}}.sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
|
40
54
|
|
41
55
|
@i.associations.has_key?(:tags).must_equal false
|
42
56
|
Item.plugin :association_proxies do |opts|
|
@@ -63,16 +77,16 @@ describe "Sequel::Plugins::AssociationProxies" do
|
|
63
77
|
Item.db.sqls.length.must_equal 0
|
64
78
|
@i.tags(:reload=>true).select{|x| false}.must_equal []
|
65
79
|
Item.db.sqls.length.must_equal 1
|
66
|
-
@t.
|
80
|
+
@t.where(:a=>1).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
|
67
81
|
Item.db.sqls.length.must_equal 0
|
68
82
|
end
|
69
83
|
|
70
84
|
it "should not return a proxy object for associations that do not return an array" do
|
71
85
|
Item.many_to_one :tag
|
72
|
-
proc{@i.tag.
|
86
|
+
proc{@i.tag.where(:a=>1)}.must_raise(NoMethodError)
|
73
87
|
|
74
88
|
Tag.one_to_one :item
|
75
|
-
proc{Tag.load(:id=>1, :item_id=>2).item.
|
89
|
+
proc{Tag.load(:id=>1, :item_id=>2).item.where(:a=>1)}.must_raise(NoMethodError)
|
76
90
|
end
|
77
91
|
|
78
92
|
it "should work correctly in subclasses" do
|
@@ -80,7 +94,7 @@ describe "Sequel::Plugins::AssociationProxies" do
|
|
80
94
|
i.associations.has_key?(:tags).must_equal false
|
81
95
|
i.tags.select{|x| false}.must_equal []
|
82
96
|
i.associations.has_key?(:tags).must_equal true
|
83
|
-
i.tags.
|
97
|
+
i.tags.where(:a=>1).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
|
84
98
|
end
|
85
99
|
|
86
100
|
end
|
@@ -79,6 +79,17 @@ connection_expiration_specs = shared_description do
|
|
79
79
|
@db.pool.instance_variable_get(:@connection_expiration_timestamps).size.must_equal 0
|
80
80
|
end
|
81
81
|
|
82
|
+
it "should not vary expiration timestamps by default" do
|
83
|
+
c1 = @db.synchronize{|c| c}
|
84
|
+
@db.pool.instance_variable_get(:@connection_expiration_timestamps)[c1].last.must_equal 2
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should support #connection_expiration_random_delay to vary expiration timestamps" do
|
88
|
+
@db.pool.connection_expiration_random_delay = 1
|
89
|
+
c1 = @db.synchronize{|c| c}
|
90
|
+
@db.pool.instance_variable_get(:@connection_expiration_timestamps)[c1].last.wont_equal 2
|
91
|
+
end
|
92
|
+
|
82
93
|
def multiple_connections
|
83
94
|
q, q1 = Queue.new, Queue.new
|
84
95
|
c1 = nil
|
@@ -101,7 +112,8 @@ connection_expiration_specs = shared_description do
|
|
101
112
|
# Set the timestamp back in time to simulate sleep / passage of time.
|
102
113
|
def simulate_sleep(conn, sleep_time = 3)
|
103
114
|
timestamps = @db.pool.instance_variable_get(:@connection_expiration_timestamps)
|
104
|
-
timestamps[conn]
|
115
|
+
timer, max = timestamps[conn]
|
116
|
+
timestamps[conn] = [timer - sleep_time, max]
|
105
117
|
@db.pool.instance_variable_set(:@connection_expiration_timestamps, timestamps)
|
106
118
|
end
|
107
119
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe "pg_auto_constraint_validations plugin" do
|
4
|
+
def create_model(ds)
|
5
|
+
@ds = ds
|
6
|
+
@ds.send(:columns=, [:id, :i])
|
7
|
+
@db.fetch = @metadata_results.dup
|
8
|
+
c = Sequel::Model(@ds)
|
9
|
+
c.plugin :pg_auto_constraint_validations
|
10
|
+
c
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
info = @info = {:schema=>'public', :table=>'items'}
|
15
|
+
error = @error = []
|
16
|
+
@db = Sequel.mock(:host=>'postgres')
|
17
|
+
def @db.schema(*) [[:i, {}], [:id, {}]] end
|
18
|
+
@set_error = lambda{|ec, ei| @db.fetch = @db.autoid = @db.numrows = ec; info.merge!(ei)}
|
19
|
+
@db.define_singleton_method(:error_info){|e| info}
|
20
|
+
@metadata_results = [
|
21
|
+
[{:constraint=>'items_i_check', :column=>'i', :definition=>'CHECK i'}, {:constraint=>'items_i_id_check', :column=>'i', :definition=>'CHECK i + id < 20'}, {:constraint=>'items_i_id_check', :column=>'id', :definition=>'CHECK i + id < 20'}],
|
22
|
+
[{:name=>'items_i_uidx', :unique=>true, :column=>'i', :deferrable=>false}, {:name=>'items_i2_idx', :unique=>false, :column=>'i', :deferrable=>false}],
|
23
|
+
[{:name=>'items_i_fk', :column=>'i', :on_update=>'a', :on_delete=>'a', :table=>'items2', :refcolumn=>'id', :schema=>'public'}],
|
24
|
+
[{:name=>'items2_i_fk', :column=>'id', :on_update=>'a', :on_delete=>'a', :table=>'items2', :refcolumn=>'i', :schema=>'public'}],
|
25
|
+
[{:nspname=>'public', :relname=>'items'}]
|
26
|
+
]
|
27
|
+
@c = create_model(@db[:items])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should handle check constraint failures as validation errors when creating" do
|
31
|
+
o = @c.new(:i=>12)
|
32
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check']
|
33
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
34
|
+
o.errors.must_equal(:i=>['is invalid'])
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should handle check constraint failures as validation errors when updating" do
|
38
|
+
o = @c.load(:i=>3)
|
39
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check']
|
40
|
+
proc{o.update(:i=>12)}.must_raise Sequel::ValidationFailed
|
41
|
+
o.errors.must_equal(:i=>['is invalid'])
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should handle unique constraint failures as validation errors when creating" do
|
45
|
+
o = @c.new(:i=>2)
|
46
|
+
@set_error[Sequel::UniqueConstraintViolation, :constraint=>'items_i_uidx']
|
47
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
48
|
+
o.errors.must_equal(:i=>['is already taken'])
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should handle unique constraint failures as validation errors when updating" do
|
52
|
+
o = @c.load(:id=>5, :i=>3)
|
53
|
+
@set_error[Sequel::UniqueConstraintViolation, :constraint=>'items_i_uidx']
|
54
|
+
proc{o.update(:i=>2)}.must_raise Sequel::ValidationFailed
|
55
|
+
o.errors.must_equal(:i=>['is already taken'])
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should handle not null constraint failures as validation errors when creating" do
|
59
|
+
o = @c.new(:i=>5)
|
60
|
+
@set_error[Sequel::NotNullConstraintViolation, :column=>'i']
|
61
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
62
|
+
o.errors.must_equal(:i=>['is not present'])
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should handle not null constraint failures as validation errors when updating" do
|
66
|
+
o = @c.load(:i=>3)
|
67
|
+
@set_error[Sequel::NotNullConstraintViolation, :column=>'i']
|
68
|
+
proc{o.update(:i=>nil)}.must_raise Sequel::ValidationFailed
|
69
|
+
o.errors.must_equal(:i=>['is not present'])
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should handle foreign key constraint failures as validation errors when creating" do
|
73
|
+
o = @c.new(:i=>3)
|
74
|
+
@set_error[Sequel::ForeignKeyConstraintViolation, :constraint=>'items_i_fk', :message_primary=>'insert or']
|
75
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
76
|
+
o.errors.must_equal(:i=>['is invalid'])
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should handle foreign key constraint failures as validation errors when updating" do
|
80
|
+
o = @c.load(:i=>1)
|
81
|
+
@set_error[Sequel::ForeignKeyConstraintViolation, :constraint=>'items_i_fk', :message_primary=>'insert or']
|
82
|
+
proc{o.update(:i=>3)}.must_raise Sequel::ValidationFailed
|
83
|
+
o.errors.must_equal(:i=>['is invalid'])
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should handle foreign key constraint failures in other tables as validation errors when updating" do
|
87
|
+
o = @c.load(:i=>1)
|
88
|
+
@set_error[Sequel::ForeignKeyConstraintViolation, :constraint=>'items2_i_fk', :message_primary=>'update or', :schema=>'public', :table=>'items2']
|
89
|
+
proc{o.update(:i=>3)}.must_raise Sequel::ValidationFailed
|
90
|
+
o.errors.must_equal(:i=>['cannot be changed currently'])
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should handle symbol, string, and identifier table names" do
|
94
|
+
[@db[:items], @db.from('items'), @db.from{items}, @db.from{public[:items]}].each do |ds|
|
95
|
+
c = create_model(ds)
|
96
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check']
|
97
|
+
o = c.new(:i=>3)
|
98
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
99
|
+
o.errors.must_equal(:i=>['is invalid'])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should skip handling of other table types such as subqueries and functions" do
|
104
|
+
[@db.from{foo(:bar)}, @db[:a, :b]].each do |ds|
|
105
|
+
@db.fetch = @metadata_results.dup
|
106
|
+
@c.dataset = ds
|
107
|
+
o = @c.new(:i=>3)
|
108
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check']
|
109
|
+
proc{o.save}.must_raise Sequel::CheckConstraintViolation
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should skip handling if the error_info method is not supported" do
|
114
|
+
@db.singleton_class.send(:remove_method, :error_info)
|
115
|
+
c = create_model(@db[:items])
|
116
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check']
|
117
|
+
o = c.new(:i=>3)
|
118
|
+
proc{o.save}.must_raise Sequel::CheckConstraintViolation
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should not handle constraint failures if they can't be converted" do
|
122
|
+
o = @c.new(:i=>12)
|
123
|
+
@set_error[Sequel::NotNullConstraintViolation, {}]
|
124
|
+
proc{o.save}.must_raise Sequel::NotNullConstraintViolation
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should reraise original exception if there is an error" do
|
128
|
+
o = @c.new(:i=>12)
|
129
|
+
def o.add_pg_constraint_validation_error; end
|
130
|
+
@set_error[Sequel::NotNullConstraintViolation, :column=>'i']
|
131
|
+
proc{o.save}.must_raise Sequel::NotNullConstraintViolation
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should not handle constraint failures if schema or table do not match" do
|
135
|
+
o = @c.new(:i=>12)
|
136
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check', :schema=>'x']
|
137
|
+
proc{o.save}.must_raise Sequel::CheckConstraintViolation
|
138
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check', :schema=>'public', :table=>'x']
|
139
|
+
proc{o.save}.must_raise Sequel::CheckConstraintViolation
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should handle constraint failures when disabling insert returning" do
|
143
|
+
c = create_model(@db[:items].disable_insert_returning)
|
144
|
+
o = c.new(:i=>12)
|
145
|
+
o.id = 1
|
146
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_check']
|
147
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
148
|
+
o.errors.must_equal(:i=>['is invalid'])
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should handle multi-column constraint failures as validation errors" do
|
152
|
+
o = @c.new(:i=>12)
|
153
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
|
154
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
155
|
+
o.errors.must_equal([:i, :id]=>['is invalid'])
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should handle multi-column constraint failures as validation errors when using the error_splitter plugin" do
|
159
|
+
@c.plugin :error_splitter
|
160
|
+
o = @c.new(:i=>12)
|
161
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
|
162
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
163
|
+
o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
|
164
|
+
end
|
165
|
+
end
|