sequel 5.7.1 → 5.8.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.
- 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
|