sequel 5.7.1 → 5.8.0

Sign up to get free protection for your applications and to get access to all the features.
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