sequel 5.23.0 → 5.28.0

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