sequel 5.21.0 → 5.26.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +80 -0
  3. data/README.rdoc +1 -1
  4. data/doc/dataset_filtering.rdoc +15 -0
  5. data/doc/opening_databases.rdoc +3 -0
  6. data/doc/postgresql.rdoc +2 -2
  7. data/doc/release_notes/5.22.0.txt +48 -0
  8. data/doc/release_notes/5.23.0.txt +56 -0
  9. data/doc/release_notes/5.24.0.txt +56 -0
  10. data/doc/release_notes/5.25.0.txt +32 -0
  11. data/doc/release_notes/5.26.0.txt +35 -0
  12. data/doc/testing.rdoc +1 -0
  13. data/lib/sequel/adapters/jdbc.rb +7 -1
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  15. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  16. data/lib/sequel/adapters/mysql2.rb +0 -1
  17. data/lib/sequel/adapters/shared/mssql.rb +11 -9
  18. data/lib/sequel/adapters/shared/postgres.rb +42 -12
  19. data/lib/sequel/adapters/shared/sqlite.rb +16 -2
  20. data/lib/sequel/adapters/tinytds.rb +12 -0
  21. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  22. data/lib/sequel/database/logging.rb +7 -1
  23. data/lib/sequel/database/schema_generator.rb +11 -2
  24. data/lib/sequel/database/schema_methods.rb +2 -0
  25. data/lib/sequel/dataset.rb +4 -2
  26. data/lib/sequel/dataset/actions.rb +3 -2
  27. data/lib/sequel/dataset/query.rb +4 -0
  28. data/lib/sequel/dataset/sql.rb +11 -7
  29. data/lib/sequel/extensions/named_timezones.rb +51 -9
  30. data/lib/sequel/extensions/pg_array.rb +4 -0
  31. data/lib/sequel/extensions/pg_json.rb +88 -17
  32. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  33. data/lib/sequel/extensions/pg_range.rb +12 -2
  34. data/lib/sequel/extensions/pg_row.rb +3 -1
  35. data/lib/sequel/extensions/sql_comments.rb +2 -2
  36. data/lib/sequel/model/base.rb +12 -5
  37. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  38. data/lib/sequel/plugins/association_proxies.rb +3 -2
  39. data/lib/sequel/plugins/caching.rb +3 -0
  40. data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
  41. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  42. data/lib/sequel/plugins/dirty.rb +3 -9
  43. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  44. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  45. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  46. data/lib/sequel/plugins/sharding.rb +11 -5
  47. data/lib/sequel/plugins/static_cache.rb +8 -3
  48. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  49. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  50. data/lib/sequel/sql.rb +18 -4
  51. data/lib/sequel/timezones.rb +50 -11
  52. data/lib/sequel/version.rb +1 -1
  53. data/spec/adapters/postgres_spec.rb +174 -0
  54. data/spec/bin_spec.rb +2 -2
  55. data/spec/core/database_spec.rb +50 -0
  56. data/spec/core/dataset_spec.rb +33 -1
  57. data/spec/core/expression_filters_spec.rb +32 -3
  58. data/spec/core/schema_spec.rb +18 -0
  59. data/spec/core/spec_helper.rb +1 -1
  60. data/spec/core_extensions_spec.rb +1 -1
  61. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  62. data/spec/extensions/dirty_spec.rb +33 -0
  63. data/spec/extensions/insert_conflict_spec.rb +103 -0
  64. data/spec/extensions/named_timezones_spec.rb +109 -2
  65. data/spec/extensions/nested_attributes_spec.rb +48 -0
  66. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  67. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  68. data/spec/extensions/pg_json_spec.rb +12 -0
  69. data/spec/extensions/pg_range_spec.rb +90 -9
  70. data/spec/extensions/sharding_spec.rb +8 -0
  71. data/spec/extensions/spec_helper.rb +9 -2
  72. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  73. data/spec/guards_helper.rb +1 -1
  74. data/spec/integration/dataset_test.rb +24 -8
  75. data/spec/integration/plugin_test.rb +27 -0
  76. data/spec/integration/schema_test.rb +16 -2
  77. data/spec/model/spec_helper.rb +1 -1
  78. metadata +32 -2
@@ -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
 
@@ -1086,11 +1088,23 @@ module Sequel
1086
1088
  def self.from_value_pair(l, r)
1087
1089
  case r
1088
1090
  when Range
1089
- expr = new(:>=, l, r.begin)
1091
+ unless r.begin.nil?
1092
+ begin_expr = new(:>=, l, r.begin)
1093
+ end
1090
1094
  unless r.end.nil?
1091
- expr = new(:AND, expr, new(r.exclude_end? ? :< : :<=, l, r.end))
1095
+ end_expr = new(r.exclude_end? ? :< : :<=, l, r.end)
1096
+ end
1097
+ if begin_expr
1098
+ if end_expr
1099
+ new(:AND, begin_expr, end_expr)
1100
+ else
1101
+ begin_expr
1102
+ end
1103
+ elsif end_expr
1104
+ end_expr
1105
+ else
1106
+ new(:'=', 1, 1)
1092
1107
  end
1093
- expr
1094
1108
  when ::Array
1095
1109
  r = r.dup.freeze unless r.frozen?
1096
1110
  new(:IN, l, r)
@@ -54,7 +54,14 @@ module Sequel
54
54
  convert_output_datetime_other(v, output_timezone)
55
55
  end
56
56
  else
57
- v.public_send(output_timezone == :utc ? :getutc : :getlocal)
57
+ case output_timezone
58
+ when :utc
59
+ v.getutc
60
+ when :local
61
+ v.getlocal
62
+ else
63
+ convert_output_time_other(v, output_timezone)
64
+ end
58
65
  end
59
66
  else
60
67
  v
@@ -110,7 +117,7 @@ module Sequel
110
117
  # same time and just modifying the timezone.
111
118
  def convert_input_datetime_no_offset(v, input_timezone)
112
119
  case input_timezone
113
- when :utc, nil
120
+ when nil, :utc
114
121
  v # DateTime assumes UTC if no offset is given
115
122
  when :local
116
123
  offset = local_offset_for_datetime(v)
@@ -119,7 +126,7 @@ module Sequel
119
126
  convert_input_datetime_other(v, input_timezone)
120
127
  end
121
128
  end
122
-
129
+
123
130
  # Convert the given +DateTime+ to the given input_timezone that is not supported
124
131
  # by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
125
132
  # Can be overridden in extensions.
@@ -127,6 +134,13 @@ module Sequel
127
134
  raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}"
128
135
  end
129
136
 
137
+ # Convert the given +Time+ to the given input_timezone that is not supported
138
+ # by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
139
+ # Can be overridden in extensions.
140
+ def convert_input_time_other(v, input_timezone)
141
+ raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}"
142
+ end
143
+
130
144
  # Converts the object from a +String+, +Array+, +Date+, +DateTime+, or +Time+ into an
131
145
  # instance of <tt>Sequel.datetime_class</tt>. If given an array or a string that doesn't
132
146
  # contain an offset, assume that the array/string is already in the given +input_timezone+.
@@ -139,12 +153,17 @@ module Sequel
139
153
  else
140
154
  # Correct for potentially wrong offset if string doesn't include offset
141
155
  if v2.is_a?(DateTime)
142
- v2 = convert_input_datetime_no_offset(v2, input_timezone)
156
+ convert_input_datetime_no_offset(v2, input_timezone)
143
157
  else
144
- # Time assumes local time if no offset is given
145
- v2 = v2.getutc + v2.utc_offset if input_timezone == :utc
158
+ case input_timezone
159
+ when nil, :local
160
+ v2
161
+ when :utc
162
+ (v2 + v2.utc_offset).utc
163
+ else
164
+ convert_input_time_other((v2 + v2.utc_offset).utc, input_timezone)
165
+ end
146
166
  end
147
- v2
148
167
  end
149
168
  when Array
150
169
  y, mo, d, h, mi, s, ns, off = v
@@ -155,8 +174,18 @@ module Sequel
155
174
  else
156
175
  convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s), input_timezone)
157
176
  end
177
+ elsif off
178
+ s += Rational(ns, 1000000000) if ns
179
+ Time.new(y, mo, d, h, mi, s, (off*86400).to_i)
158
180
  else
159
- Time.public_send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
181
+ case input_timezone
182
+ when nil, :local
183
+ Time.local(y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
184
+ when :utc
185
+ Time.utc(y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
186
+ else
187
+ convert_input_time_other(Time.utc(y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0)), input_timezone)
188
+ end
160
189
  end
161
190
  when Hash
162
191
  ary = [:year, :month, :day, :hour, :minute, :second, :nanos].map{|x| (v[x] || v[x.to_s]).to_i}
@@ -188,23 +217,33 @@ module Sequel
188
217
  raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}"
189
218
  end
190
219
 
220
+ # Convert the given +Time+ to the given output_timezone that is not supported
221
+ # by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
222
+ # Can be overridden in extensions.
223
+ def convert_output_time_other(v, output_timezone)
224
+ raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}"
225
+ end
226
+
191
227
  # Convert the timezone setter argument. Returns argument given by default,
192
228
  # exists for easier overriding in extensions.
193
229
  def convert_timezone_setter_arg(tz)
194
230
  tz
195
231
  end
196
232
 
197
- # Takes a DateTime dt, and returns the correct local offset for that dt, daylight savings included.
233
+ # Takes a DateTime dt, and returns the correct local offset for that dt, daylight savings included, in fraction of a day.
198
234
  def local_offset_for_datetime(dt)
199
235
  time_offset_to_datetime_offset Time.local(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec).utc_offset
200
236
  end
201
237
 
202
238
  # Caches offset conversions to avoid excess Rational math.
203
239
  def time_offset_to_datetime_offset(offset_secs)
204
- @local_offsets ||= {}
205
- @local_offsets[offset_secs] ||= Rational(offset_secs, 86400)
240
+ if offset = Sequel.synchronize{@local_offsets[offset_secs]}
241
+ return offset
242
+ end
243
+ Sequel.synchronize{@local_offsets[offset_secs] = Rational(offset_secs, 86400)}
206
244
  end
207
245
  end
208
246
 
247
+ @local_offsets = {}
209
248
  extend Timezones
210
249
  end
@@ -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 = 21
9
+ MINOR = 26
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -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,8 +368,36 @@ 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
 
381
+ describe "A PostgreSQL database" do
382
+ before do
383
+ @db = DB
384
+ @db.create_table!(:cte_test){Integer :id}
385
+ end
386
+ after do
387
+ @db.drop_table?(:cte_test)
388
+ end
389
+
390
+ it "should give correct results for WITH AS [NOT] MATERIALIZED" do
391
+ @ds = @db[:cte_test]
392
+ @ds.insert(1)
393
+ @ds.insert(2)
394
+
395
+ @db[:t].with(:t, @ds, :materialized=>nil).order(:id).map(:id).must_equal [1, 2]
396
+ @db[:t].with(:t, @ds, :materialized=>true).order(:id).map(:id).must_equal [1, 2]
397
+ @db[:t].with(:t, @ds, :materialized=>false).order(:id).map(:id).must_equal [1, 2]
398
+ end
399
+ end if DB.server_version >= 120000
400
+
355
401
  describe "A PostgreSQL database" do
356
402
  before(:all) do
357
403
  @db = DB
@@ -3303,6 +3349,65 @@ describe 'PostgreSQL json type' do
3303
3349
  @db.from(jo.each_text).select_order_map(:key).must_equal %w'a b'
3304
3350
  @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\}\}/)
3305
3351
 
3352
+ if DB.server_version >= 120000 && json_type == :jsonb
3353
+ @db.get(jo.path_exists('$.b.d.e')).must_equal true
3354
+ @db.get(jo.path_exists('$.b.d.f')).must_equal false
3355
+
3356
+ @db.get(jo.path_exists!('$.b.d.e')).must_equal true
3357
+ @db.get(jo.path_exists!('$.b.d.f')).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)).must_equal true
3361
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 4)).must_equal false
3362
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal true
3363
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal false
3364
+
3365
+ @db.get(jo.path_match('$.b.d.e')).must_be_nil
3366
+ @db.get(jo.path_match('$.b.d.f')).must_be_nil
3367
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match('$.b.d.e')).must_equal true
3368
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match('$.b.d.e')).must_equal false
3369
+
3370
+ proc{@db.get(jo.path_match!('$.b.d.e'))}.must_raise(Sequel::DatabaseError)
3371
+ proc{@db.get(jo.path_match!('$.b.d.f'))}.must_raise(Sequel::DatabaseError)
3372
+ @db.get(jo.path_match!('$.b.d.e', {}, true)).must_be_nil
3373
+ @db.get(jo.path_match!('$.b.d.f', {}, true)).must_be_nil
3374
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match!('$.b.d.e')).must_equal true
3375
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match!('$.b.d.e')).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)).must_equal true
3379
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 4)).must_equal false
3380
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 2}, false)).must_equal true
3381
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 4}, true)).must_equal false
3382
+
3383
+ @db.get(jo.path_query_first('$.b.d.e')).must_equal 3
3384
+ @db.get(jo.path_query_first('$.b.d.f')).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)).must_equal 3
3388
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 4)).must_be_nil
3389
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal 3
3390
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_be_nil
3391
+
3392
+ @db.get(jo.path_query_array('$.b.d.e')).must_equal [3]
3393
+ @db.get(jo.path_query_array('$.b.d.f')).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)).must_equal [3]
3397
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 4)).must_equal []
3398
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal [3]
3399
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal []
3400
+
3401
+ @db.from(jo.path_query('$.b.d.e').as(:a, [:b])).get(:b).must_equal 3
3402
+ @db.from(jo.path_query('$.b.d.f').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).as(:a, [:b])).get(:b).must_equal 3
3406
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 4).as(:a, [:b])).get(:b).must_be_nil
3407
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 2}, true).as(:a, [:b])).get(:b).must_equal 3
3408
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 4}, false).as(:a, [:b])).get(:b).must_be_nil
3409
+ end
3410
+
3306
3411
  Sequel.extension :pg_row_ops
3307
3412
  @db.create_table!(:items) do
3308
3413
  Integer :a
@@ -3581,6 +3686,30 @@ describe 'PostgreSQL range types' do
3581
3686
  @ds.filter(h).call(:delete, @ra).must_equal 1
3582
3687
  end if uses_pg_or_jdbc
3583
3688
 
3689
+ it 'handle endless ranges' do
3690
+ @db.get(Sequel.cast(eval('1...'), :int4range)).must_be :==, eval('1...')
3691
+ @db.get(Sequel.cast(eval('1...'), :int4range)).wont_be :==, eval('2...')
3692
+ @db.get(Sequel.cast(eval('1...'), :int4range)).wont_be :==, eval('1..')
3693
+ @db.get(Sequel.cast(eval('2...'), :int4range)).must_be :==, eval('2...')
3694
+ @db.get(Sequel.cast(eval('2...'), :int4range)).wont_be :==, eval('2..')
3695
+ @db.get(Sequel.cast(eval('2...'), :int4range)).wont_be :==, eval('1...')
3696
+ end if RUBY_VERSION >= '2.6'
3697
+
3698
+ it 'handle startless ranges' do
3699
+ @db.get(Sequel.cast(eval('...1'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3700
+ @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 2, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3701
+ @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_end=>true, :db_type=>"int4range")
3702
+ @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
3703
+ end if RUBY_VERSION >= '2.7'
3704
+
3705
+ it 'handle startless ranges' do
3706
+ @db.get(Sequel.cast(eval('nil...nil'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, nil, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3707
+ @db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, nil, :exclude_begin=>true, :db_type=>"int4range")
3708
+ @db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, nil, :exclude_end=>true, :db_type=>"int4range")
3709
+ @db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(1, nil, :exclude_begin=>true, :db_type=>"int4range")
3710
+ @db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
3711
+ end if RUBY_VERSION >= '2.7'
3712
+
3584
3713
  it 'parse default values for schema' do
3585
3714
  @db.create_table!(:items) do
3586
3715
  Integer :j
@@ -4301,4 +4430,49 @@ describe "pg_auto_constraint_validations plugin" do
4301
4430
  proc{o.save}.must_raise Sequel::ValidationFailed
4302
4431
  o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
4303
4432
  end
4433
+
4434
+ it "should handle dumping cached metadata and loading metadata from cache" do
4435
+ cache_file = "spec/files/pgacv-#{$$}.cache"
4436
+ begin
4437
+ c = Class.new(Sequel::Model)
4438
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
4439
+ c1 = Class.new(c)
4440
+ def c1.name; 'Foo' end
4441
+ c1.dataset = DB[:test1]
4442
+ c2 = Class.new(c)
4443
+ def c2.name; 'Bar' end
4444
+ c2.dataset = DB[:test2]
4445
+ c1.unrestrict_primary_key
4446
+ c2.unrestrict_primary_key
4447
+
4448
+ o = c1.new(:id=>5, :i=>12)
4449
+ proc{o.save}.must_raise Sequel::ValidationFailed
4450
+ o.errors.must_equal(:i=>['is invalid'])
4451
+ o = c2.new(:test2_id=>4, :test1_id=>2)
4452
+ proc{o.save}.must_raise Sequel::ValidationFailed
4453
+ o.errors.must_equal(:test1_id=>['is invalid'])
4454
+
4455
+ c.dump_pg_auto_constraint_validations_cache
4456
+
4457
+ c = Class.new(Sequel::Model)
4458
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
4459
+ c1 = Class.new(c)
4460
+ def c1.name; 'Foo' end
4461
+ c1.dataset = DB[:test1]
4462
+ c2 = Class.new(c)
4463
+ def c2.name; 'Bar' end
4464
+ c2.dataset = DB[:test2]
4465
+ c1.unrestrict_primary_key
4466
+ c2.unrestrict_primary_key
4467
+
4468
+ o = c1.new(:id=>5, :i=>12)
4469
+ proc{o.save}.must_raise Sequel::ValidationFailed
4470
+ o.errors.must_equal(:i=>['is invalid'])
4471
+ o = c2.new(:test2_id=>4, :test1_id=>2)
4472
+ proc{o.save}.must_raise Sequel::ValidationFailed
4473
+ o.errors.must_equal(:test1_id=>['is invalid'])
4474
+ ensure
4475
+ File.delete(cache_file) if File.file?(cache_file)
4476
+ end
4477
+ end
4304
4478
  end if DB.respond_to?(:error_info)