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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +53 -1
  3. data/doc/association_basics.rdoc +2 -2
  4. data/doc/migration.rdoc +11 -10
  5. data/doc/postgresql.rdoc +71 -0
  6. data/doc/release_notes/5.8.0.txt +170 -0
  7. data/lib/sequel/adapters/jdbc.rb +6 -1
  8. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
  9. data/lib/sequel/adapters/mysql2.rb +2 -1
  10. data/lib/sequel/adapters/postgres.rb +32 -10
  11. data/lib/sequel/adapters/shared/mssql.rb +11 -11
  12. data/lib/sequel/adapters/shared/mysql.rb +51 -6
  13. data/lib/sequel/adapters/shared/oracle.rb +12 -2
  14. data/lib/sequel/adapters/shared/postgres.rb +97 -30
  15. data/lib/sequel/adapters/shared/sqlanywhere.rb +2 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +6 -1
  17. data/lib/sequel/dataset/features.rb +5 -0
  18. data/lib/sequel/dataset/query.rb +48 -19
  19. data/lib/sequel/exceptions.rb +7 -0
  20. data/lib/sequel/extensions/connection_expiration.rb +8 -3
  21. data/lib/sequel/extensions/pg_enum.rb +28 -5
  22. data/lib/sequel/plugins/association_proxies.rb +16 -4
  23. data/lib/sequel/plugins/error_splitter.rb +16 -11
  24. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +260 -0
  25. data/lib/sequel/plugins/subclasses.rb +1 -1
  26. data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
  27. data/lib/sequel/version.rb +2 -2
  28. data/spec/adapters/mysql_spec.rb +0 -1
  29. data/spec/adapters/postgres_spec.rb +169 -4
  30. data/spec/adapters/sqlite_spec.rb +13 -0
  31. data/spec/core/dataset_spec.rb +21 -0
  32. data/spec/extensions/association_proxies_spec.rb +21 -7
  33. data/spec/extensions/connection_expiration_spec.rb +13 -1
  34. data/spec/extensions/pg_auto_constraint_validations_spec.rb +165 -0
  35. data/spec/extensions/pg_enum_spec.rb +26 -22
  36. data/spec/extensions/tactical_eager_loading_spec.rb +11 -0
  37. data/spec/integration/dataset_test.rb +30 -6
  38. data/spec/integration/plugin_test.rb +2 -2
  39. 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 that keeping track of the classes to finalize and freeze
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
@@ -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 = 7
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 = 1
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
@@ -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
- @db.create_enum(:test_enum, %w'a b c d')
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 %w'a b c d'
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]
@@ -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.filter(: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))"
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.filter(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))"
39
- @t.filter{{: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))"
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.filter(: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))"
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.filter(:a=>1)}.must_raise(NoMethodError)
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.filter(:a=>1)}.must_raise(NoMethodError)
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.filter(: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))"
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] -= sleep_time
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