sequel 5.23.0 → 5.28.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +62 -0
  3. data/README.rdoc +1 -1
  4. data/doc/cheat_sheet.rdoc +1 -0
  5. data/doc/postgresql.rdoc +2 -2
  6. data/doc/release_notes/5.24.0.txt +56 -0
  7. data/doc/release_notes/5.25.0.txt +32 -0
  8. data/doc/release_notes/5.26.0.txt +35 -0
  9. data/doc/release_notes/5.27.0.txt +21 -0
  10. data/doc/release_notes/5.28.0.txt +16 -0
  11. data/doc/testing.rdoc +11 -6
  12. data/lib/sequel/adapters/jdbc.rb +7 -1
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -13
  14. data/lib/sequel/adapters/mysql2.rb +0 -1
  15. data/lib/sequel/adapters/shared/mssql.rb +4 -2
  16. data/lib/sequel/adapters/shared/postgres.rb +30 -7
  17. data/lib/sequel/adapters/shared/sqlite.rb +7 -2
  18. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  19. data/lib/sequel/database/logging.rb +7 -1
  20. data/lib/sequel/database/schema_generator.rb +11 -2
  21. data/lib/sequel/database/schema_methods.rb +2 -0
  22. data/lib/sequel/dataset/features.rb +6 -0
  23. data/lib/sequel/dataset/query.rb +15 -2
  24. data/lib/sequel/dataset/sql.rb +17 -4
  25. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  26. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  27. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  28. data/lib/sequel/extensions/pg_enum.rb +4 -1
  29. data/lib/sequel/extensions/pg_json.rb +1 -1
  30. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  31. data/lib/sequel/extensions/pg_range.rb +9 -0
  32. data/lib/sequel/extensions/sql_comments.rb +2 -2
  33. data/lib/sequel/model/base.rb +12 -5
  34. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  35. data/lib/sequel/plugins/caching.rb +3 -0
  36. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  37. data/lib/sequel/plugins/dirty.rb +3 -9
  38. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  39. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  40. data/lib/sequel/plugins/sharding.rb +11 -5
  41. data/lib/sequel/plugins/static_cache.rb +8 -3
  42. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  43. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  44. data/lib/sequel/sql.rb +4 -1
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/postgres_spec.rb +145 -17
  47. data/spec/adapters/sqlite_spec.rb +1 -1
  48. data/spec/bin_spec.rb +1 -1
  49. data/spec/core/database_spec.rb +20 -1
  50. data/spec/core/expression_filters_spec.rb +26 -7
  51. data/spec/core/schema_spec.rb +18 -0
  52. data/spec/core/spec_helper.rb +1 -1
  53. data/spec/core_extensions_spec.rb +1 -1
  54. data/spec/extensions/any_not_empty_spec.rb +23 -0
  55. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  56. data/spec/extensions/dirty_spec.rb +33 -0
  57. data/spec/extensions/exclude_or_null_spec.rb +15 -0
  58. data/spec/extensions/insert_conflict_spec.rb +26 -0
  59. data/spec/extensions/nested_attributes_spec.rb +48 -0
  60. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  61. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  62. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  63. data/spec/extensions/pg_range_spec.rb +35 -21
  64. data/spec/extensions/sharding_spec.rb +8 -0
  65. data/spec/extensions/spec_helper.rb +1 -1
  66. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  67. data/spec/guards_helper.rb +1 -1
  68. data/spec/integration/dataset_test.rb +57 -17
  69. data/spec/integration/plugin_test.rb +1 -1
  70. data/spec/integration/schema_test.rb +9 -0
  71. data/spec/integration/spec_helper.rb +7 -1
  72. data/spec/model/spec_helper.rb +1 -1
  73. metadata +35 -3
@@ -107,12 +107,18 @@ module Sequel
107
107
  # previous row_proc, but calls set_server on the output of that row_proc,
108
108
  # ensuring that objects retrieved by a specific shard know which shard they
109
109
  # are tied to.
110
- def server(s)
111
- ds = super
112
- if rp = row_proc
113
- ds = ds.with_row_proc(proc{|r| rp.call(r).set_server(s)})
110
+ def row_proc
111
+ rp = super
112
+ if rp
113
+ case server = db.pool.send(:pick_server, opts[:server])
114
+ when nil, :default, :read_only
115
+ # nothing
116
+ else
117
+ old_rp = rp
118
+ rp = proc{|r| old_rp.call(r).set_server(server)}
119
+ end
114
120
  end
115
- ds
121
+ rp
116
122
  end
117
123
  end
118
124
  end
@@ -211,18 +211,23 @@ module Sequel
211
211
  # Reload the cache for this model by retrieving all of the instances in the dataset
212
212
  # freezing them, and populating the cached array and hash.
213
213
  def load_cache
214
- a = dataset.all
214
+ @all = load_static_cache_rows
215
215
  h = {}
216
- a.each do |o|
216
+ @all.each do |o|
217
217
  o.errors.freeze
218
218
  h[o.pk.freeze] = o.freeze
219
219
  end
220
- @all = a.freeze
221
220
  @cache = h.freeze
222
221
  end
223
222
 
224
223
  private
225
224
 
225
+ # Load the static cache rows from the database.
226
+ def load_static_cache_rows
227
+ ret = super if defined?(super)
228
+ ret || dataset.all.freeze
229
+ end
230
+
226
231
  # Return the frozen object with the given pk, or nil if no such object exists
227
232
  # in the cache, without issuing a database query.
228
233
  def primary_key_lookup(pk)
@@ -0,0 +1,53 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The static_cache_cache plugin allows for caching the row content for subclasses
6
+ # that use the static cache plugin (or just the current class). Using this plugin
7
+ # can avoid the need to query the database every time loading the plugin into a
8
+ # model, which can save time when you have a lot of models using the static_cache
9
+ # plugin.
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Make all model subclasses that use the static_cache plugin use
14
+ # # the cached values in the given file
15
+ # Sequel::Model.plugin :static_cache_cache, "static_cache.cache"
16
+ #
17
+ # # Make the AlbumType model the cached values in the given file,
18
+ # # should be loaded before the static_cache plugin
19
+ # AlbumType.plugin :static_cache_cache, "static_cache.cache"
20
+ module StaticCacheCache
21
+ def self.configure(model, file)
22
+ model.instance_variable_set(:@static_cache_cache_file, file)
23
+ model.instance_variable_set(:@static_cache_cache, File.exist?(file) ? Marshal.load(File.read(file)) : {})
24
+ end
25
+
26
+ module ClassMethods
27
+ # Dump the in-memory cached rows to the cache file.
28
+ def dump_static_cache_cache
29
+ File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(@static_cache_cache))}
30
+ nil
31
+ end
32
+
33
+ Plugins.inherited_instance_variables(self, :@static_cache_cache_file=>nil, :@static_cache_cache=>nil)
34
+
35
+ private
36
+
37
+ # Load the rows for the model from the cache if available.
38
+ # If not available, load the rows from the database, and
39
+ # then update the cache with the raw rows.
40
+ def load_static_cache_rows
41
+ if rows = Sequel.synchronize{@static_cache_cache[name]}
42
+ rows.map{|row| call(row)}.freeze
43
+ else
44
+ rows = dataset.all.freeze
45
+ raw_rows = rows.map(&:values)
46
+ Sequel.synchronize{@static_cache_cache[name] = raw_rows}
47
+ rows
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -41,7 +41,9 @@ module Sequel
41
41
  # Typecast values using #load_typecast when the values are retrieved
42
42
  # from the database.
43
43
  def call(values)
44
- super.load_typecast
44
+ o = super.load_typecast
45
+ o.send(:_clear_changed_columns, :initialize)
46
+ o
45
47
  end
46
48
 
47
49
  # Freeze typecast on load columns when freezing model class.
@@ -63,7 +65,6 @@ module Sequel
63
65
  set_column_value("#{c}=", v)
64
66
  end
65
67
  end
66
- _changed_columns.clear
67
68
  self
68
69
  end
69
70
 
@@ -788,8 +788,10 @@ module Sequel
788
788
  def coerce(other)
789
789
  if other.is_a?(Numeric)
790
790
  [SQL::NumericExpression.new(:NOOP, other), self]
791
- else
791
+ elsif defined?(super)
792
792
  super
793
+ else
794
+ [self, other]
793
795
  end
794
796
  end
795
797
 
@@ -1315,6 +1317,7 @@ module Sequel
1315
1317
  CURRENT_DATE = Constant.new(:CURRENT_DATE)
1316
1318
  CURRENT_TIME = Constant.new(:CURRENT_TIME)
1317
1319
  CURRENT_TIMESTAMP = Constant.new(:CURRENT_TIMESTAMP)
1320
+ DEFAULT = Constant.new(:DEFAULT)
1318
1321
  SQLTRUE = TRUE = BooleanConstant.new(true)
1319
1322
  SQLFALSE = FALSE = BooleanConstant.new(false)
1320
1323
  NULL = BooleanConstant.new(nil)
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 23
9
+ MINOR = 28
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -70,7 +70,7 @@ describe "PostgreSQL", '#create_table' do
70
70
 
71
71
  it "should create an unlogged table" do
72
72
  @db.create_table(:unlogged_dolls, :unlogged => true){text :name}
73
- end
73
+ end if DB.server_version >= 90100
74
74
 
75
75
  it "should create a table inheriting from another table" do
76
76
  @db.create_table(:unlogged_dolls){text :name}
@@ -227,6 +227,24 @@ describe "PostgreSQL", '#create_table' do
227
227
  @db.convert_serial_to_identity(:tmp_dolls, :column=>:id)
228
228
  end if DB.server_version >= 100002 && DB.get{current_setting('is_superuser')} == 'on'
229
229
 
230
+ it "should support creating generated columns" do
231
+ @db.create_table(:tmp_dolls){Integer :a; Integer :b; Integer :c, :generated_always_as=>Sequel[:a] * 2 + :b + 1}
232
+ @db[:tmp_dolls].insert(:a=>100, :b=>10)
233
+ @db[:tmp_dolls].select_order_map([:a, :b, :c]).must_equal [[100, 10, 211]]
234
+ end if DB.server_version >= 120000
235
+
236
+ it "should support deferred primary key and unique constraints on columns" do
237
+ @db.create_table(:tmp_dolls){primary_key :id, :primary_key_deferrable=>true; Integer :i, :unique=>true, :unique_deferrable=>true}
238
+ @db[:tmp_dolls].insert(:i=>10)
239
+ DB.transaction do
240
+ @db[:tmp_dolls].insert(:id=>1, :i=>1)
241
+ @db[:tmp_dolls].insert(:id=>10, :i=>10)
242
+ @db[:tmp_dolls].where(:i=>1).update(:id=>2)
243
+ @db[:tmp_dolls].where(:id=>10).update(:i=>2)
244
+ end
245
+ @db[:tmp_dolls].select_order_map([:id, :i]).must_equal [[1, 10], [2, 1], [10, 2]]
246
+ end if DB.server_version >= 90000
247
+
230
248
  it "should support pg_loose_count extension" do
231
249
  @db.extension :pg_loose_count
232
250
  @db.create_table(:tmp_dolls){text :name}
@@ -350,6 +368,14 @@ describe "PostgreSQL", 'INSERT ON CONFLICT' do
350
368
  @ds.insert_conflict(:constraint=>:ic_test_a_uidx, :update=>{:b=>6}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4).must_be_nil
351
369
  @ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
352
370
  end
371
+
372
+ it "Dataset#insert_conflict should support table aliases" do
373
+ @ds = @db[Sequel[:ic_test].as(:foo)]
374
+ @ds.insert(1, 2, 5)
375
+ proc{@ds.insert(1, 3, 4)}.must_raise Sequel::UniqueConstraintViolation
376
+ @ds.insert_conflict(:target=>:a, :update=>{:b=>Sequel[:foo][:c] + Sequel[:excluded][:c]}).insert(1, 7, 10)
377
+ @ds.all.must_equal [{:a=>1, :b=>15, :c=>5, :c_is_unique=>false}]
378
+ end
353
379
  end if DB.server_version >= 90500
354
380
 
355
381
  describe "A PostgreSQL database" do
@@ -589,10 +615,6 @@ describe "A PostgreSQL dataset" do
589
615
  @d.from{generate_series(1,3,1).as(:a)}.select{(a.sql_number % 2).as(:a)}.from_self.get{mode.function.within_group(:a)}.must_equal 1
590
616
  end if DB.server_version >= 90400
591
617
 
592
- it "should support filtered aggregate functions" do
593
- @d.from{generate_series(1,3,1).as(:a)}.select{(a.sql_number % 2).as(:a)}.from_self.get{count(:a).filter(:a=>1)}.must_equal 2
594
- end if DB.server_version >= 90400
595
-
596
618
  it "should support functions with ordinality" do
597
619
  @d.from{generate_series(1,10,3).with_ordinality}.select_map([:generate_series, :ordinality]).must_equal [[1, 1], [4, 2], [7, 3], [10, 4]]
598
620
  end if DB.server_version >= 90400
@@ -1246,7 +1268,7 @@ describe "A PostgreSQL database" do
1246
1268
  end
1247
1269
 
1248
1270
  it "should support indexes with index type" do
1249
- @db.create_table(:posts){point :p; index :p, :type => 'gist'}
1271
+ @db.create_table(:posts){box :geom; index :geom, :type => 'gist'}
1250
1272
  end
1251
1273
 
1252
1274
  it "should support unique indexes with index type" do
@@ -2678,6 +2700,8 @@ describe 'PostgreSQL array handling' do
2678
2700
  if @db.server_version >= 90000
2679
2701
  @ds.get(Sequel.pg_array(:i5).join).must_equal '15'
2680
2702
  @ds.get(Sequel.pg_array(:i5).join(':')).must_equal '1:5'
2703
+ end
2704
+ if @db.server_version >= 90100
2681
2705
  @ds.get(Sequel.pg_array(:i5).join(':', '*')).must_equal '1:*:5'
2682
2706
  end
2683
2707
  if @db.server_version >= 90300
@@ -3323,6 +3347,65 @@ describe 'PostgreSQL json type' do
3323
3347
  @db.from(jo.each_text).select_order_map(:key).must_equal %w'a b'
3324
3348
  @db.from(jo.each_text).order(:key).where(:key=>'b').get(:value).gsub(' ', '').must_match(/\{"d":\{"e":3\},"c":2\}|\{"c":2,"d":\{"e":3\}\}/)
3325
3349
 
3350
+ if DB.server_version >= 120000 && json_type == :jsonb
3351
+ @db.get(jo.path_exists('$.b.d.e')).must_equal true
3352
+ @db.get(jo.path_exists('$.b.d.f')).must_equal false
3353
+
3354
+ @db.get(jo.path_exists!('$.b.d.e')).must_equal true
3355
+ @db.get(jo.path_exists!('$.b.d.f')).must_equal false
3356
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal true
3357
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal false
3358
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 2)).must_equal true
3359
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 4)).must_equal false
3360
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal true
3361
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal false
3362
+
3363
+ @db.get(jo.path_match('$.b.d.e')).must_be_nil
3364
+ @db.get(jo.path_match('$.b.d.f')).must_be_nil
3365
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match('$.b.d.e')).must_equal true
3366
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match('$.b.d.e')).must_equal false
3367
+
3368
+ proc{@db.get(jo.path_match!('$.b.d.e'))}.must_raise(Sequel::DatabaseError)
3369
+ proc{@db.get(jo.path_match!('$.b.d.f'))}.must_raise(Sequel::DatabaseError)
3370
+ @db.get(jo.path_match!('$.b.d.e', {}, true)).must_be_nil
3371
+ @db.get(jo.path_match!('$.b.d.f', {}, true)).must_be_nil
3372
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match!('$.b.d.e')).must_equal true
3373
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match!('$.b.d.e')).must_equal false
3374
+ @db.get(jo.path_match!('$.b.d.e > $x', '{"x":2}')).must_equal true
3375
+ @db.get(jo.path_match!('$.b.d.e > $x', '{"x":4}')).must_equal false
3376
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 2)).must_equal true
3377
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 4)).must_equal false
3378
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 2}, false)).must_equal true
3379
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 4}, true)).must_equal false
3380
+
3381
+ @db.get(jo.path_query_first('$.b.d.e')).must_equal 3
3382
+ @db.get(jo.path_query_first('$.b.d.f')).must_be_nil
3383
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal 3
3384
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":4}')).must_be_nil
3385
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 2)).must_equal 3
3386
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 4)).must_be_nil
3387
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal 3
3388
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_be_nil
3389
+
3390
+ @db.get(jo.path_query_array('$.b.d.e')).must_equal [3]
3391
+ @db.get(jo.path_query_array('$.b.d.f')).must_equal []
3392
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal [3]
3393
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal []
3394
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 2)).must_equal [3]
3395
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 4)).must_equal []
3396
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal [3]
3397
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal []
3398
+
3399
+ @db.from(jo.path_query('$.b.d.e').as(:a, [:b])).get(:b).must_equal 3
3400
+ @db.from(jo.path_query('$.b.d.f').as(:a, [:b])).get(:b).must_be_nil
3401
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":2}').as(:a, [:b])).get(:b).must_equal 3
3402
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":4}').as(:a, [:b])).get(:b).must_be_nil
3403
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 2).as(:a, [:b])).get(:b).must_equal 3
3404
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 4).as(:a, [:b])).get(:b).must_be_nil
3405
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 2}, true).as(:a, [:b])).get(:b).must_equal 3
3406
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 4}, false).as(:a, [:b])).get(:b).must_be_nil
3407
+ end
3408
+
3326
3409
  Sequel.extension :pg_row_ops
3327
3410
  @db.create_table!(:items) do
3328
3411
  Integer :a
@@ -3602,19 +3685,19 @@ describe 'PostgreSQL range types' do
3602
3685
  end if uses_pg_or_jdbc
3603
3686
 
3604
3687
  it 'handle endless ranges' do
3605
- @db.get(Sequel.cast(eval('1...'), :int4range)).must_be :==, eval('1...')
3606
- @db.get(Sequel.cast(eval('1...'), :int4range)).wont_be :==, eval('2...')
3607
- @db.get(Sequel.cast(eval('1...'), :int4range)).wont_be :==, eval('1..')
3608
- @db.get(Sequel.cast(eval('2...'), :int4range)).must_be :==, eval('2...')
3609
- @db.get(Sequel.cast(eval('2...'), :int4range)).wont_be :==, eval('2..')
3610
- @db.get(Sequel.cast(eval('2...'), :int4range)).wont_be :==, eval('1...')
3688
+ @db.get(Sequel.cast(eval('(1...)'), :int4range)).must_be :==, eval('(1...)')
3689
+ @db.get(Sequel.cast(eval('(1...)'), :int4range)).wont_be :==, eval('(2...)')
3690
+ @db.get(Sequel.cast(eval('(1...)'), :int4range)).wont_be :==, eval('(1..)')
3691
+ @db.get(Sequel.cast(eval('(2...)'), :int4range)).must_be :==, eval('(2...)')
3692
+ @db.get(Sequel.cast(eval('(2...)'), :int4range)).wont_be :==, eval('(2..)')
3693
+ @db.get(Sequel.cast(eval('(2...)'), :int4range)).wont_be :==, eval('(1...)')
3611
3694
  end if RUBY_VERSION >= '2.6'
3612
3695
 
3613
3696
  it 'handle startless ranges' do
3614
- @db.get(Sequel.cast(eval('...1'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3615
- @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 2, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3616
- @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_end=>true, :db_type=>"int4range")
3617
- @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
3697
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3698
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 2, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3699
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_end=>true, :db_type=>"int4range")
3700
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
3618
3701
  end if RUBY_VERSION >= '2.7'
3619
3702
 
3620
3703
  it 'handle startless ranges' do
@@ -4345,4 +4428,49 @@ describe "pg_auto_constraint_validations plugin" do
4345
4428
  proc{o.save}.must_raise Sequel::ValidationFailed
4346
4429
  o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
4347
4430
  end
4348
- end if DB.respond_to?(:error_info)
4431
+
4432
+ it "should handle dumping cached metadata and loading metadata from cache" do
4433
+ cache_file = "spec/files/pgacv-#{$$}.cache"
4434
+ begin
4435
+ c = Class.new(Sequel::Model)
4436
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
4437
+ c1 = Class.new(c)
4438
+ def c1.name; 'Foo' end
4439
+ c1.dataset = DB[:test1]
4440
+ c2 = Class.new(c)
4441
+ def c2.name; 'Bar' end
4442
+ c2.dataset = DB[:test2]
4443
+ c1.unrestrict_primary_key
4444
+ c2.unrestrict_primary_key
4445
+
4446
+ o = c1.new(:id=>5, :i=>12)
4447
+ proc{o.save}.must_raise Sequel::ValidationFailed
4448
+ o.errors.must_equal(:i=>['is invalid'])
4449
+ o = c2.new(:test2_id=>4, :test1_id=>2)
4450
+ proc{o.save}.must_raise Sequel::ValidationFailed
4451
+ o.errors.must_equal(:test1_id=>['is invalid'])
4452
+
4453
+ c.dump_pg_auto_constraint_validations_cache
4454
+
4455
+ c = Class.new(Sequel::Model)
4456
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
4457
+ c1 = Class.new(c)
4458
+ def c1.name; 'Foo' end
4459
+ c1.dataset = DB[:test1]
4460
+ c2 = Class.new(c)
4461
+ def c2.name; 'Bar' end
4462
+ c2.dataset = DB[:test2]
4463
+ c1.unrestrict_primary_key
4464
+ c2.unrestrict_primary_key
4465
+
4466
+ o = c1.new(:id=>5, :i=>12)
4467
+ proc{o.save}.must_raise Sequel::ValidationFailed
4468
+ o.errors.must_equal(:i=>['is invalid'])
4469
+ o = c2.new(:test2_id=>4, :test1_id=>2)
4470
+ proc{o.save}.must_raise Sequel::ValidationFailed
4471
+ o.errors.must_equal(:test1_id=>['is invalid'])
4472
+ ensure
4473
+ File.delete(cache_file) if File.file?(cache_file)
4474
+ end
4475
+ end
4476
+ end if DB.respond_to?(:error_info) && DB.server_version >= 90300
@@ -623,7 +623,7 @@ describe "SQLite", 'INSERT ON CONFLICT' do
623
623
  @ds.insert_conflict(:target=>:a).insert(1, 3, 4, false)
624
624
  @ds.insert_conflict(:target=>:c, :conflict_where=>:c_is_unique).insert(11, 12, 3, true)
625
625
  @ds.all.must_equal [{:a=>1, :b=>2, :c=>3, :c_is_unique=>false}, {:a=>10, :b=>11, :c=>3, :c_is_unique=>true}]
626
- end
626
+ end unless DB.adapter_scheme == :amalgalite
627
627
 
628
628
  it "Dataset#insert_ignore and insert_conflict should work with multi_insert/import" do
629
629
  @ds.insert(1, 2, 3, false)
@@ -25,7 +25,7 @@ DB2 = Sequel.connect("#{CONN_PREFIX}#{BIN_SPEC_DB2}", :test=>false)
25
25
 
26
26
  ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
27
27
  gem 'minitest'
28
- require 'minitest/autorun'
28
+ require 'minitest/global_expectations/autorun'
29
29
 
30
30
  describe "bin/sequel" do
31
31
  def bin(opts={})
@@ -264,6 +264,25 @@ describe "Database#log_connection_yield" do
264
264
  @o.logs.first.first.must_equal :info
265
265
  @o.logs.first.last.must_match(/\A\(\d\.\d{6}s\) blah; \[1, 2\]\z/)
266
266
  end
267
+
268
+ it "should log without a logger defined by forcing skip_logging? to return false" do
269
+ @db.logger = nil
270
+ @db.extend(Module.new do
271
+ def skip_logging?
272
+ false
273
+ end
274
+
275
+ def log_duration(*)
276
+ self.did_log = true
277
+ end
278
+
279
+ attr_accessor :did_log
280
+ end)
281
+
282
+ @db.log_connection_yield('some sql', @conn) {}
283
+
284
+ @db.did_log.must_equal true
285
+ end
267
286
  end
268
287
 
269
288
  describe "Database#uri" do
@@ -2448,7 +2467,7 @@ describe "Database#typecast_value" do
2448
2467
  @db.typecast_value(:date, 'a')
2449
2468
  true.must_equal false
2450
2469
  rescue Sequel::InvalidValue => e
2451
- e.inspect.must_equal '#<Sequel::InvalidValue: ArgumentError: invalid date>'
2470
+ e.inspect.must_match(/\A#\<Sequel::InvalidValue: (Argument|Date::)Error: invalid date\>\z/)
2452
2471
  end
2453
2472
  end
2454
2473
  end
@@ -214,8 +214,13 @@ describe "Blockless Ruby Filters" do
214
214
  @d.lit(1 + Sequel.lit('?', :x)).must_equal '(1 + x)'
215
215
  end
216
216
 
217
- it "should raise a NoMethodError if coerce is called with a non-Numeric" do
218
- proc{Sequel.expr(:x).coerce(:a)}.must_raise NoMethodError
217
+ it "should not break Date/DateTime equality" do
218
+ (Date.today == Sequel.expr(:x)).must_equal false
219
+ (DateTime.now == Sequel.expr(:x)).must_equal false
220
+ end
221
+
222
+ it "should have coerce return array if called on a non-numeric" do
223
+ Sequel.expr(:x).coerce(:a).must_equal [Sequel.expr(:x), :a]
219
224
  end
220
225
 
221
226
  it "should support AND conditions via &" do
@@ -348,6 +353,11 @@ describe "Blockless Ruby Filters" do
348
353
  @d.l({:x => Sequel::FALSE}).must_equal '(x IS FALSE)'
349
354
  @d.l({:x => Sequel::SQLTRUE}).must_equal '(x IS TRUE)'
350
355
  @d.l({:x => Sequel::SQLFALSE}).must_equal '(x IS FALSE)'
356
+
357
+ @d.l({:x => Sequel::CURRENT_DATE}).must_equal '(x = CURRENT_DATE)'
358
+ @d.l({:x => Sequel::CURRENT_TIME}).must_equal '(x = CURRENT_TIME)'
359
+ @d.l({:x => Sequel::CURRENT_TIMESTAMP}).must_equal '(x = CURRENT_TIMESTAMP)'
360
+ @d.l({:x => Sequel::DEFAULT}).must_equal '(x = DEFAULT)'
351
361
  end
352
362
 
353
363
  it "should support negation of SQL::Constants" do
@@ -527,21 +537,21 @@ describe "Blockless Ruby Filters" do
527
537
  end
528
538
 
529
539
  it "should handle endless ranges" do
530
- endless = eval('1..')
540
+ endless = eval('(1..)')
531
541
  @d.l{x =~ endless}.must_equal '(x >= 1)'
532
542
  @d.l(:x => endless).must_equal '(x >= 1)'
533
543
 
534
- endless = eval('1...')
544
+ endless = eval('(1...)')
535
545
  @d.l{x =~ endless}.must_equal '(x >= 1)'
536
546
  @d.l(:x => endless).must_equal '(x >= 1)'
537
547
  end if RUBY_VERSION >= '2.6'
538
548
 
539
549
  it "should handle startless ranges" do
540
- endless = eval('..1')
550
+ endless = eval('(..1)')
541
551
  @d.l{x =~ endless}.must_equal '(x <= 1)'
542
552
  @d.l(:x => endless).must_equal '(x <= 1)'
543
553
 
544
- endless = eval('...1')
554
+ endless = eval('(...1)')
545
555
  @d.l{x =~ endless}.must_equal '(x < 1)'
546
556
  @d.l(:x => endless).must_equal '(x < 1)'
547
557
  end if RUBY_VERSION >= '2.7'
@@ -736,14 +746,23 @@ describe Sequel::SQL::VirtualRow do
736
746
  @d.l{mode.function.within_group(:a, :b)}.must_equal 'mode() WITHIN GROUP (ORDER BY "a", "b")'
737
747
  end
738
748
 
749
+ it "should handle emualted filtered aggregate function calls" do
750
+ @d.l{count.function.*.filter(Sequel.&(:a, :b))}.must_equal 'count((CASE WHEN ("a" AND "b") THEN 1 ELSE NULL END))'
751
+ @d.l{count.function.*.filter(:a=>1)}.must_equal 'count((CASE WHEN ("a" = 1) THEN 1 ELSE NULL END))'
752
+ @d.l{count(:a).filter{b > 1}}.must_equal 'count((CASE WHEN ("b" > 1) THEN "a" ELSE NULL END))'
753
+ @d.l{count(:a).filter(:a=>1){b > 1}}.must_equal 'count((CASE WHEN (("a" = 1) AND ("b" > 1)) THEN "a" ELSE NULL END))'
754
+ end
755
+
739
756
  it "should handle filtered aggregate function calls" do
757
+ @d = @d.with_extend{def supports_filtered_aggregates?; true end}
740
758
  @d.l{count.function.*.filter(Sequel.&(:a, :b))}.must_equal 'count(*) FILTER (WHERE ("a" AND "b"))'
741
759
  @d.l{count.function.*.filter(:a=>1)}.must_equal 'count(*) FILTER (WHERE ("a" = 1))'
742
760
  @d.l{count.function.*.filter{b > 1}}.must_equal 'count(*) FILTER (WHERE ("b" > 1))'
743
761
  @d.l{count.function.*.filter(:a=>1){b > 1}}.must_equal 'count(*) FILTER (WHERE (("a" = 1) AND ("b" > 1)))'
744
762
  end
745
763
 
746
- it "should handle fitlered ordered-set and hypothetical-set function calls" do
764
+ it "should handle filtered ordered-set and hypothetical-set function calls" do
765
+ @d = @d.with_extend{def supports_filtered_aggregates?; true end}
747
766
  @d.l{mode.function.within_group(:a).filter(:a=>1)}.must_equal 'mode() WITHIN GROUP (ORDER BY "a") FILTER (WHERE ("a" = 1))'
748
767
  end
749
768