sequel 5.21.0 → 5.26.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +80 -0
- data/README.rdoc +1 -1
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/postgresql.rdoc +2 -2
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +0 -1
- data/lib/sequel/adapters/shared/mssql.rb +11 -9
- data/lib/sequel/adapters/shared/postgres.rb +42 -12
- data/lib/sequel/adapters/shared/sqlite.rb +16 -2
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/schema_generator.rb +11 -2
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset.rb +4 -2
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/query.rb +4 -0
- data/lib/sequel/dataset/sql.rb +11 -7
- data/lib/sequel/extensions/named_timezones.rb +51 -9
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +88 -17
- data/lib/sequel/extensions/pg_json_ops.rb +124 -0
- data/lib/sequel/extensions/pg_range.rb +12 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/sql_comments.rb +2 -2
- data/lib/sequel/model/base.rb +12 -5
- data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/caching.rb +3 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
- data/lib/sequel/plugins/csv_serializer.rb +26 -9
- data/lib/sequel/plugins/dirty.rb +3 -9
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
- data/lib/sequel/plugins/sharding.rb +11 -5
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/typecast_on_load.rb +3 -2
- data/lib/sequel/sql.rb +18 -4
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +174 -0
- data/spec/bin_spec.rb +2 -2
- data/spec/core/database_spec.rb +50 -0
- data/spec/core/dataset_spec.rb +33 -1
- data/spec/core/expression_filters_spec.rb +32 -3
- data/spec/core/schema_spec.rb +18 -0
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
- data/spec/extensions/pg_json_ops_spec.rb +67 -0
- data/spec/extensions/pg_json_spec.rb +12 -0
- data/spec/extensions/pg_range_spec.rb +90 -9
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +9 -2
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/guards_helper.rb +1 -1
- data/spec/integration/dataset_test.rb +24 -8
- data/spec/integration/plugin_test.rb +27 -0
- data/spec/integration/schema_test.rb +16 -2
- data/spec/model/spec_helper.rb +1 -1
- metadata +32 -2
@@ -340,14 +340,18 @@ module Sequel
|
|
340
340
|
raise Sequel::Error, "invalid array, empty string" if eos?
|
341
341
|
raise Sequel::Error, "invalid array, doesn't start with {" unless scan(/((\[\d+:\d+\])+=)?\{/)
|
342
342
|
|
343
|
+
# :nocov:
|
343
344
|
while !eos?
|
345
|
+
# :nocov:
|
344
346
|
char = scan(/[{}",]|[^{}",]+/)
|
345
347
|
if char == ','
|
346
348
|
# Comma outside quoted string indicates end of current entry
|
347
349
|
new_entry
|
348
350
|
elsif char == '"'
|
349
351
|
raise Sequel::Error, "invalid array, opening quote with existing recorded data" unless @recorded.empty?
|
352
|
+
# :nocov:
|
350
353
|
while true
|
354
|
+
# :nocov:
|
351
355
|
char = scan(/["\\]|[^"\\]+/)
|
352
356
|
if char == '\\'
|
353
357
|
@recorded << getch
|
@@ -12,7 +12,7 @@
|
|
12
12
|
#
|
13
13
|
# Note that wrapping JSON primitives changes the behavior for
|
14
14
|
# JSON false and null values. Because only +false+ and +nil+
|
15
|
-
# in Ruby are considered
|
15
|
+
# in Ruby are considered falsey, wrapping these objects results
|
16
16
|
# in unexpected behavior if you use the values directly in
|
17
17
|
# conditionals:
|
18
18
|
#
|
@@ -208,11 +208,19 @@ module Sequel
|
|
208
208
|
JSONB_PRIMITIVE_WRAPPER_MAPPING.freeze
|
209
209
|
|
210
210
|
JSON_COMBINED_WRAPPER_MAPPING =JSON_WRAPPER_MAPPING.merge(JSON_PRIMITIVE_WRAPPER_MAPPING).freeze
|
211
|
-
JSON_WRAP_CLASSES = JSON_COMBINED_WRAPPER_MAPPING.keys.freeze
|
212
|
-
|
213
211
|
JSONB_COMBINED_WRAPPER_MAPPING =JSONB_WRAPPER_MAPPING.merge(JSONB_PRIMITIVE_WRAPPER_MAPPING).freeze
|
214
212
|
JSONB_WRAP_CLASSES = JSONB_COMBINED_WRAPPER_MAPPING.keys.freeze
|
215
213
|
|
214
|
+
Sequel::Deprecation.deprecate_constant(self, :JSON_WRAPPER_MAPPING)
|
215
|
+
Sequel::Deprecation.deprecate_constant(self, :JSONB_WRAPPER_MAPPING)
|
216
|
+
Sequel::Deprecation.deprecate_constant(self, :JSON_PRIMITIVE_WRAPPER_MAPPING)
|
217
|
+
Sequel::Deprecation.deprecate_constant(self, :JSONB_PRIMITIVE_WRAPPER_MAPPING)
|
218
|
+
Sequel::Deprecation.deprecate_constant(self, :JSON_COMBINED_WRAPPER_MAPPING)
|
219
|
+
Sequel::Deprecation.deprecate_constant(self, :JSONB_COMBINED_WRAPPER_MAPPING)
|
220
|
+
Sequel::Deprecation.deprecate_constant(self, :JSONB_WRAP_CLASSES)
|
221
|
+
|
222
|
+
JSON_WRAP_CLASSES = [Hash, Array, String, Integer, Float, NilClass, TrueClass, FalseClass].freeze
|
223
|
+
|
216
224
|
# Methods enabling Database object integration with the json type.
|
217
225
|
module JSONDatabaseMethods
|
218
226
|
def self.extended(db)
|
@@ -228,6 +236,69 @@ module Sequel
|
|
228
236
|
end
|
229
237
|
end
|
230
238
|
|
239
|
+
# Return the wrapper class for the json type if value is Hash or Array.
|
240
|
+
def self.json_wrapper(value)
|
241
|
+
case value
|
242
|
+
when ::Hash
|
243
|
+
JSONHash
|
244
|
+
when ::Array
|
245
|
+
JSONArray
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Return the wrapper class for the jsonb type if value is Hash or Array.
|
250
|
+
def self.jsonb_wrapper(value)
|
251
|
+
case value
|
252
|
+
when ::Hash
|
253
|
+
JSONBHash
|
254
|
+
when ::Array
|
255
|
+
JSONBArray
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Return the wrapper class for the json type if value is a supported type.
|
260
|
+
def self.json_primitive_wrapper(value)
|
261
|
+
case value
|
262
|
+
when ::Hash
|
263
|
+
JSONHash
|
264
|
+
when ::Array
|
265
|
+
JSONArray
|
266
|
+
when ::String
|
267
|
+
JSONString
|
268
|
+
when ::Integer
|
269
|
+
JSONInteger
|
270
|
+
when ::Float
|
271
|
+
JSONFloat
|
272
|
+
when ::NilClass
|
273
|
+
JSONNull
|
274
|
+
when ::TrueClass
|
275
|
+
JSONTrue
|
276
|
+
when ::FalseClass
|
277
|
+
JSONFalse
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Return the wrapper class for the jsonb type if value is a supported type.
|
282
|
+
def self.jsonb_primitive_wrapper(value)
|
283
|
+
case value
|
284
|
+
when ::Hash
|
285
|
+
JSONBHash
|
286
|
+
when ::Array
|
287
|
+
JSONBArray
|
288
|
+
when ::String
|
289
|
+
JSONBString
|
290
|
+
when ::Integer
|
291
|
+
JSONBInteger
|
292
|
+
when ::Float
|
293
|
+
JSONBFloat
|
294
|
+
when ::NilClass
|
295
|
+
JSONBNull
|
296
|
+
when ::TrueClass
|
297
|
+
JSONBTrue
|
298
|
+
when ::FalseClass
|
299
|
+
JSONBFalse
|
300
|
+
end
|
301
|
+
end
|
231
302
|
|
232
303
|
# Deprecated
|
233
304
|
def self.db_parse_json(s)
|
@@ -326,9 +397,9 @@ module Sequel
|
|
326
397
|
# Wrap the parsed JSON value in the appropriate JSON wrapper class.
|
327
398
|
# Only wrap primitive values if wrap_json_primitives is set.
|
328
399
|
def _wrap_json(value)
|
329
|
-
if klass =
|
400
|
+
if klass = JSONDatabaseMethods.json_wrapper(value)
|
330
401
|
klass.new(value)
|
331
|
-
elsif klass =
|
402
|
+
elsif klass = JSONDatabaseMethods.json_primitive_wrapper(value)
|
332
403
|
if wrap_json_primitives
|
333
404
|
klass.new(value)
|
334
405
|
else
|
@@ -342,9 +413,9 @@ module Sequel
|
|
342
413
|
# Wrap the parsed JSON value in the appropriate JSONB wrapper class.
|
343
414
|
# Only wrap primitive values if wrap_json_primitives is set.
|
344
415
|
def _wrap_jsonb(value)
|
345
|
-
if klass =
|
416
|
+
if klass = JSONDatabaseMethods.jsonb_wrapper(value)
|
346
417
|
klass.new(value)
|
347
|
-
elsif klass =
|
418
|
+
elsif klass = JSONDatabaseMethods.jsonb_primitive_wrapper(value)
|
348
419
|
if wrap_json_primitives
|
349
420
|
klass.new(value)
|
350
421
|
else
|
@@ -413,10 +484,10 @@ module Sequel
|
|
413
484
|
_wrap_json(_parse_json(value))
|
414
485
|
end
|
415
486
|
when *JSON_WRAP_CLASSES
|
416
|
-
|
487
|
+
JSONDatabaseMethods.json_primitive_wrapper(value).new(value)
|
417
488
|
when JSONBObject
|
418
489
|
value = value.__getobj__
|
419
|
-
|
490
|
+
JSONDatabaseMethods.json_primitive_wrapper(value).new(value)
|
420
491
|
else
|
421
492
|
raise Sequel::InvalidValue, "invalid value for json: #{value.inspect}"
|
422
493
|
end
|
@@ -433,11 +504,11 @@ module Sequel
|
|
433
504
|
else
|
434
505
|
_wrap_jsonb(_parse_json(value))
|
435
506
|
end
|
436
|
-
when *
|
437
|
-
|
507
|
+
when *JSON_WRAP_CLASSES
|
508
|
+
JSONDatabaseMethods.jsonb_primitive_wrapper(value).new(value)
|
438
509
|
when JSONObject
|
439
510
|
value = value.__getobj__
|
440
|
-
|
511
|
+
JSONDatabaseMethods.jsonb_primitive_wrapper(value).new(value)
|
441
512
|
else
|
442
513
|
raise Sequel::InvalidValue, "invalid value for jsonb: #{value.inspect}"
|
443
514
|
end
|
@@ -460,7 +531,7 @@ module Sequel
|
|
460
531
|
Postgres::JSONHash.new(v)
|
461
532
|
when Postgres::JSONBObject
|
462
533
|
v = v.__getobj__
|
463
|
-
Postgres::
|
534
|
+
Postgres::JSONDatabaseMethods.json_primitive_wrapper(v).new(v)
|
464
535
|
else
|
465
536
|
Sequel.pg_json_op(v)
|
466
537
|
end
|
@@ -472,7 +543,7 @@ module Sequel
|
|
472
543
|
def pg_json_wrap(v)
|
473
544
|
case v
|
474
545
|
when *Postgres::JSON_WRAP_CLASSES
|
475
|
-
Postgres::
|
546
|
+
Postgres::JSONDatabaseMethods.json_primitive_wrapper(v).new(v)
|
476
547
|
else
|
477
548
|
raise Error, "invalid value passed to Sequel.pg_json_wrap: #{v.inspect}"
|
478
549
|
end
|
@@ -492,7 +563,7 @@ module Sequel
|
|
492
563
|
Postgres::JSONBHash.new(v)
|
493
564
|
when Postgres::JSONObject
|
494
565
|
v = v.__getobj__
|
495
|
-
Postgres::
|
566
|
+
Postgres::JSONDatabaseMethods.jsonb_primitive_wrapper(v).new(v)
|
496
567
|
else
|
497
568
|
Sequel.pg_jsonb_op(v)
|
498
569
|
end
|
@@ -503,8 +574,8 @@ module Sequel
|
|
503
574
|
# other types.
|
504
575
|
def pg_jsonb_wrap(v)
|
505
576
|
case v
|
506
|
-
when *Postgres::
|
507
|
-
Postgres::
|
577
|
+
when *Postgres::JSON_WRAP_CLASSES
|
578
|
+
Postgres::JSONDatabaseMethods.jsonb_primitive_wrapper(v).new(v)
|
508
579
|
else
|
509
580
|
raise Error, "invalid value passed to Sequel.pg_jsonb_wrap: #{v.inspect}"
|
510
581
|
end
|
@@ -73,6 +73,23 @@
|
|
73
73
|
# j.pretty # jsonb_pretty(jsonb_column)
|
74
74
|
# j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
|
75
75
|
#
|
76
|
+
# On PostgreSQL 12+ SQL/JSON functions and operators are supported:
|
77
|
+
#
|
78
|
+
# j.path_exists('$.foo') # (jsonb_column @? '$.foo')
|
79
|
+
# j.path_match('$.foo') # (jsonb_column @@ '$.foo')
|
80
|
+
#
|
81
|
+
# j.path_exists!('$.foo') # jsonb_path_exists(jsonb_column, '$.foo')
|
82
|
+
# j.path_match!('$.foo') # jsonb_path_match(jsonb_column, '$.foo')
|
83
|
+
# j.path_query('$.foo') # jsonb_path_query(jsonb_column, '$.foo')
|
84
|
+
# j.path_query_array('$.foo') # jsonb_path_query_array(jsonb_column, '$.foo')
|
85
|
+
# j.path_query_first('$.foo') # jsonb_path_query_first(jsonb_column, '$.foo')
|
86
|
+
#
|
87
|
+
# For the PostgreSQL 12+ SQL/JSON functions, one argument is required (+path+) and
|
88
|
+
# two more arguments are optional (+vars+ and +silent+). +path+ specifies the JSON path.
|
89
|
+
# +vars+ specifies a hash or a string in JSON format of named variables to be
|
90
|
+
# substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
|
91
|
+
# errors are not suppressed.
|
92
|
+
#
|
76
93
|
# If you are also using the pg_json extension, you should load it before
|
77
94
|
# loading this extension. Doing so will allow you to use the #op method on
|
78
95
|
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
@@ -292,6 +309,8 @@ module Sequel
|
|
292
309
|
CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
|
293
310
|
DELETE_PATH = ["(".freeze, " #- ".freeze, ")".freeze].freeze
|
294
311
|
HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
|
312
|
+
PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
|
313
|
+
PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
|
295
314
|
|
296
315
|
# jsonb expression for deletion of the given argument from the
|
297
316
|
# current jsonb.
|
@@ -362,6 +381,95 @@ module Sequel
|
|
362
381
|
self.class.new(function(:insert, wrap_input_array(path), wrap_input_jsonb(other), insert_after))
|
363
382
|
end
|
364
383
|
|
384
|
+
# Returns whether the JSON path returns any item for the json object.
|
385
|
+
#
|
386
|
+
# json_op.path_exists("$.foo") # (json @? '$.foo')
|
387
|
+
def path_exists(path)
|
388
|
+
bool_op(PATH_EXISTS, path)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns whether the JSON path returns any item for the json object.
|
392
|
+
#
|
393
|
+
# json_op.path_exists!("$.foo")
|
394
|
+
# # jsonb_path_exists(json, '$.foo')
|
395
|
+
#
|
396
|
+
# json_op.path_exists!("$.foo ? ($ > $x)", x: 2)
|
397
|
+
# # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}')
|
398
|
+
#
|
399
|
+
# json_op.path_exists!("$.foo ? ($ > $x)", {x: 2}, true)
|
400
|
+
# # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
401
|
+
def path_exists!(path, vars=nil, silent=nil)
|
402
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_exists, path, vars, silent))
|
403
|
+
end
|
404
|
+
|
405
|
+
# Returns the first item of the result of JSON path predicate check for the json object.
|
406
|
+
# Returns nil if the first item is not true or false.
|
407
|
+
#
|
408
|
+
# json_op.path_match("$.foo") # (json @@ '$.foo')
|
409
|
+
def path_match(path)
|
410
|
+
bool_op(PATH_MATCH, path)
|
411
|
+
end
|
412
|
+
|
413
|
+
# Returns the first item of the result of JSON path predicate check for the json object.
|
414
|
+
# Returns nil if the first item is not true or false and silent is true.
|
415
|
+
#
|
416
|
+
# json_op.path_match!("$.foo")
|
417
|
+
# # jsonb_path_match(json, '$.foo')
|
418
|
+
#
|
419
|
+
# json_op.path_match!("$.foo ? ($ > $x)", x: 2)
|
420
|
+
# # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}')
|
421
|
+
#
|
422
|
+
# json_op.path_match!("$.foo ? ($ > $x)", {x: 2}, true)
|
423
|
+
# # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
424
|
+
def path_match!(path, vars=nil, silent=nil)
|
425
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_match, path, vars, silent))
|
426
|
+
end
|
427
|
+
|
428
|
+
# Returns a set of all jsonb values specified by the JSON path
|
429
|
+
# for the json object.
|
430
|
+
#
|
431
|
+
# json_op.path_query("$.foo")
|
432
|
+
# # jsonb_path_query(json, '$.foo')
|
433
|
+
#
|
434
|
+
# json_op.path_query("$.foo ? ($ > $x)", x: 2)
|
435
|
+
# # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}')
|
436
|
+
#
|
437
|
+
# json_op.path_query("$.foo ? ($ > $x)", {x: 2}, true)
|
438
|
+
# # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
439
|
+
def path_query(path, vars=nil, silent=nil)
|
440
|
+
_path_function(:jsonb_path_query, path, vars, silent)
|
441
|
+
end
|
442
|
+
|
443
|
+
# Returns a jsonb array of all values specified by the JSON path
|
444
|
+
# for the json object.
|
445
|
+
#
|
446
|
+
# json_op.path_query_array("$.foo")
|
447
|
+
# # jsonb_path_query_array(json, '$.foo')
|
448
|
+
#
|
449
|
+
# json_op.path_query_array("$.foo ? ($ > $x)", x: 2)
|
450
|
+
# # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}')
|
451
|
+
#
|
452
|
+
# json_op.path_query_array("$.foo ? ($ > $x)", {x: 2}, true)
|
453
|
+
# # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
454
|
+
def path_query_array(path, vars=nil, silent=nil)
|
455
|
+
JSONBOp.new(_path_function(:jsonb_path_query_array, path, vars, silent))
|
456
|
+
end
|
457
|
+
|
458
|
+
# Returns the first item of the result specified by the JSON path
|
459
|
+
# for the json object.
|
460
|
+
#
|
461
|
+
# json_op.path_query_first("$.foo")
|
462
|
+
# # jsonb_path_query_first(json, '$.foo')
|
463
|
+
#
|
464
|
+
# json_op.path_query_first("$.foo ? ($ > $x)", x: 2)
|
465
|
+
# # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}')
|
466
|
+
#
|
467
|
+
# json_op.path_query_first("$.foo ? ($ > $x)", {x: 2}, true)
|
468
|
+
# # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
469
|
+
def path_query_first(path, vars=nil, silent=nil)
|
470
|
+
JSONBOp.new(_path_function(:jsonb_path_query_first, path, vars, silent))
|
471
|
+
end
|
472
|
+
|
365
473
|
# Return the receiver, since it is already a JSONBOp.
|
366
474
|
def pg_jsonb
|
367
475
|
self
|
@@ -386,6 +494,22 @@ module Sequel
|
|
386
494
|
|
387
495
|
private
|
388
496
|
|
497
|
+
# Internals of the jsonb SQL/JSON path functions.
|
498
|
+
def _path_function(func, path, vars, silent)
|
499
|
+
args = []
|
500
|
+
if vars
|
501
|
+
if vars.is_a?(Hash)
|
502
|
+
vars = vars.to_json
|
503
|
+
end
|
504
|
+
args << vars
|
505
|
+
|
506
|
+
unless silent.nil?
|
507
|
+
args << silent
|
508
|
+
end
|
509
|
+
end
|
510
|
+
SQL::Function.new(func, self, path, *args)
|
511
|
+
end
|
512
|
+
|
389
513
|
# Return a placeholder literal with the given str and args, wrapped
|
390
514
|
# in a boolean expression, used by operators that return booleans.
|
391
515
|
def bool_op(str, other)
|
@@ -402,6 +402,15 @@ module Sequel
|
|
402
402
|
end
|
403
403
|
alias == eql?
|
404
404
|
|
405
|
+
# Make sure equal ranges have the same hash.
|
406
|
+
def hash
|
407
|
+
if @empty
|
408
|
+
@db_type.hash
|
409
|
+
else
|
410
|
+
[@begin, @end, @exclude_begin, @exclude_end, @db_type].hash
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
405
414
|
# Allow PGRange values in case statements, where they return true if they
|
406
415
|
# are equal to each other using eql?, or if this PGRange can be converted
|
407
416
|
# to a Range, delegating to that range.
|
@@ -453,13 +462,14 @@ module Sequel
|
|
453
462
|
end
|
454
463
|
|
455
464
|
ENDLESS_RANGE_NOT_SUPPORTED = RUBY_VERSION < '2.6'
|
465
|
+
STARTLESS_RANGE_NOT_SUPPORTED = RUBY_VERSION < '2.7'
|
456
466
|
|
457
467
|
# Return a ruby Range object for this instance, if one can be created.
|
458
468
|
def to_range
|
459
469
|
return @range if @range
|
460
470
|
raise(Error, "cannot create ruby range for an empty PostgreSQL range") if empty?
|
461
471
|
raise(Error, "cannot create ruby range when PostgreSQL range excludes beginning element") if exclude_begin?
|
462
|
-
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning")
|
472
|
+
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning") if STARTLESS_RANGE_NOT_SUPPORTED && !self.begin
|
463
473
|
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded ending") if ENDLESS_RANGE_NOT_SUPPORTED && !self.end
|
464
474
|
@range = Range.new(self.begin, self.end, exclude_end?)
|
465
475
|
end
|
@@ -468,7 +478,7 @@ module Sequel
|
|
468
478
|
# it must have a beginning and an ending (no unbounded ranges), and it cannot exclude
|
469
479
|
# the beginning element.
|
470
480
|
def valid_ruby_range?
|
471
|
-
!(empty? || exclude_begin? || !self.begin || (ENDLESS_RANGE_NOT_SUPPORTED && !self.end))
|
481
|
+
!(empty? || exclude_begin? || (STARTLESS_RANGE_NOT_SUPPORTED && !self.begin) || (ENDLESS_RANGE_NOT_SUPPORTED && !self.end))
|
472
482
|
end
|
473
483
|
|
474
484
|
# Whether the beginning of the range is unbounded.
|
@@ -7,7 +7,7 @@
|
|
7
7
|
# that when composite fields are retrieved, they are parsed and returned
|
8
8
|
# as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
|
9
9
|
# optionally a custom type. HashRow and ArrayRow are DelegateClasses of
|
10
|
-
#
|
10
|
+
# Hash and Array, so they mostly act like a hash or array, but not
|
11
11
|
# completely (is_a?(Hash) and is_a?(Array) are false). If you want the
|
12
12
|
# actual hash for a HashRow, call HashRow#to_hash, and if you want the
|
13
13
|
# actual array for an ArrayRow, call ArrayRow#to_a. This is done so
|
@@ -228,7 +228,9 @@ module Sequel
|
|
228
228
|
if skip(/\)/)
|
229
229
|
values << nil
|
230
230
|
else
|
231
|
+
# :nocov:
|
231
232
|
until eos?
|
233
|
+
# :nocov:
|
232
234
|
if skip(/"/)
|
233
235
|
values << scan(/(\\.|""|[^"])*/).gsub(/\\(.)|"(")/, '\1\2')
|
234
236
|
skip(/"[,)]/)
|
@@ -9,8 +9,8 @@
|
|
9
9
|
# #
|
10
10
|
#
|
11
11
|
# As you can see, this uses single line SQL comments (--) suffixed
|
12
|
-
# by a newline. This
|
13
|
-
#
|
12
|
+
# by a newline. This plugin transforms all consecutive whitespace
|
13
|
+
# in the comment to a single string:
|
14
14
|
#
|
15
15
|
# ds = DB[:table].comment("Some\r\nComment Here").all
|
16
16
|
# # SELECT * FROM table -- Some Comment Here
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1069,7 +1069,7 @@ module Sequel
|
|
1069
1069
|
@new = true
|
1070
1070
|
@modified = true
|
1071
1071
|
initialize_set(values)
|
1072
|
-
|
1072
|
+
_clear_changed_columns(:initialize)
|
1073
1073
|
yield self if block_given?
|
1074
1074
|
end
|
1075
1075
|
|
@@ -1626,6 +1626,13 @@ module Sequel
|
|
1626
1626
|
def _changed_columns
|
1627
1627
|
@changed_columns ||= []
|
1628
1628
|
end
|
1629
|
+
|
1630
|
+
# Clear the changed columns. Reason is the reason for clearing
|
1631
|
+
# the columns, and should be one of: :initialize, :refresh, :create
|
1632
|
+
# or :update.
|
1633
|
+
def _clear_changed_columns(_reason)
|
1634
|
+
_changed_columns.clear
|
1635
|
+
end
|
1629
1636
|
|
1630
1637
|
# Do the deletion of the object's dataset, and check that the row
|
1631
1638
|
# was actually deleted.
|
@@ -1716,7 +1723,7 @@ module Sequel
|
|
1716
1723
|
# is used for reading newly inserted values from the database
|
1717
1724
|
def _refresh(dataset)
|
1718
1725
|
_refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
|
1719
|
-
|
1726
|
+
_clear_changed_columns(:refresh)
|
1720
1727
|
end
|
1721
1728
|
|
1722
1729
|
# Get the row of column data from the database.
|
@@ -1754,7 +1761,7 @@ module Sequel
|
|
1754
1761
|
@this = nil
|
1755
1762
|
@new = false
|
1756
1763
|
@modified = false
|
1757
|
-
pk ? _save_refresh :
|
1764
|
+
pk ? _save_refresh : _clear_changed_columns(:create)
|
1758
1765
|
after_create
|
1759
1766
|
true
|
1760
1767
|
end
|
@@ -1771,7 +1778,7 @@ module Sequel
|
|
1771
1778
|
cc.clear
|
1772
1779
|
else
|
1773
1780
|
columns_updated = _save_update_all_columns_hash
|
1774
|
-
|
1781
|
+
_clear_changed_columns(:update)
|
1775
1782
|
end
|
1776
1783
|
else # update only the specified columns
|
1777
1784
|
columns = Array(columns)
|
@@ -1798,7 +1805,7 @@ module Sequel
|
|
1798
1805
|
# can be overridden to avoid the refresh.
|
1799
1806
|
def _save_refresh
|
1800
1807
|
_save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
|
1801
|
-
|
1808
|
+
_clear_changed_columns(:create)
|
1802
1809
|
end
|
1803
1810
|
|
1804
1811
|
# Set values to the provided hash. Called after a create,
|