sequel 5.22.0 → 5.27.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +78 -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.23.0.txt +56 -0
  8. data/doc/release_notes/5.24.0.txt +56 -0
  9. data/doc/release_notes/5.25.0.txt +32 -0
  10. data/doc/release_notes/5.26.0.txt +35 -0
  11. data/doc/release_notes/5.27.0.txt +21 -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 +9 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +30 -7
  19. data/lib/sequel/adapters/shared/sqlite.rb +23 -4
  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/actions.rb +3 -2
  26. data/lib/sequel/dataset/features.rb +6 -0
  27. data/lib/sequel/dataset/sql.rb +17 -4
  28. data/lib/sequel/extensions/named_timezones.rb +51 -9
  29. data/lib/sequel/extensions/pg_array.rb +4 -0
  30. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  31. data/lib/sequel/extensions/pg_enum.rb +4 -1
  32. data/lib/sequel/extensions/pg_json.rb +88 -17
  33. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  34. data/lib/sequel/extensions/pg_range.rb +9 -0
  35. data/lib/sequel/extensions/pg_row.rb +3 -1
  36. data/lib/sequel/extensions/sql_comments.rb +2 -2
  37. data/lib/sequel/model/base.rb +12 -5
  38. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  39. data/lib/sequel/plugins/association_proxies.rb +3 -2
  40. data/lib/sequel/plugins/caching.rb +3 -0
  41. data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
  42. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  43. data/lib/sequel/plugins/dirty.rb +3 -9
  44. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  45. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  46. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  47. data/lib/sequel/plugins/sharding.rb +11 -5
  48. data/lib/sequel/plugins/static_cache.rb +8 -3
  49. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  51. data/lib/sequel/sql.rb +4 -1
  52. data/lib/sequel/timezones.rb +50 -11
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +135 -7
  55. data/spec/adapters/sqlite_spec.rb +1 -1
  56. data/spec/bin_spec.rb +2 -2
  57. data/spec/core/database_spec.rb +50 -0
  58. data/spec/core/dataset_spec.rb +23 -1
  59. data/spec/core/expression_filters_spec.rb +22 -3
  60. data/spec/core/schema_spec.rb +18 -0
  61. data/spec/core/spec_helper.rb +1 -1
  62. data/spec/core_extensions_spec.rb +1 -1
  63. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  64. data/spec/extensions/dirty_spec.rb +33 -0
  65. data/spec/extensions/insert_conflict_spec.rb +103 -0
  66. data/spec/extensions/named_timezones_spec.rb +109 -2
  67. data/spec/extensions/nested_attributes_spec.rb +48 -0
  68. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  69. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  70. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  71. data/spec/extensions/pg_json_spec.rb +12 -0
  72. data/spec/extensions/pg_range_spec.rb +19 -2
  73. data/spec/extensions/sharding_spec.rb +8 -0
  74. data/spec/extensions/spec_helper.rb +9 -2
  75. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  76. data/spec/guards_helper.rb +1 -1
  77. data/spec/integration/dataset_test.rb +25 -0
  78. data/spec/integration/plugin_test.rb +28 -1
  79. data/spec/integration/schema_test.rb +16 -2
  80. data/spec/integration/spec_helper.rb +7 -1
  81. data/spec/model/spec_helper.rb +1 -1
  82. 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
 
@@ -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)
@@ -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 = 22
9
+ MINOR = 27
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
@@ -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