sequel 5.22.0 → 5.27.0

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