sequel 5.24.0 → 5.29.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +58 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +1 -0
- data/doc/postgresql.rdoc +2 -2
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/release_notes/5.27.0.txt +21 -0
- data/doc/release_notes/5.28.0.txt +16 -0
- data/doc/release_notes/5.29.0.txt +22 -0
- data/doc/testing.rdoc +11 -6
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
- data/lib/sequel/adapters/postgres.rb +5 -1
- data/lib/sequel/adapters/shared/mssql.rb +4 -2
- data/lib/sequel/adapters/shared/mysql.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +15 -0
- data/lib/sequel/adapters/shared/sqlite.rb +7 -2
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +1 -1
- data/lib/sequel/database/transactions.rb +3 -3
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/query.rb +15 -2
- data/lib/sequel/dataset/sql.rb +17 -4
- data/lib/sequel/extensions/any_not_empty.rb +45 -0
- data/lib/sequel/extensions/exclude_or_null.rb +68 -0
- data/lib/sequel/extensions/pg_array_ops.rb +10 -6
- data/lib/sequel/extensions/pg_enum.rb +4 -1
- data/lib/sequel/extensions/pg_json.rb +1 -1
- data/lib/sequel/extensions/pg_json_ops.rb +124 -0
- data/lib/sequel/extensions/pg_range.rb +9 -0
- 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/caching.rb +3 -0
- data/lib/sequel/plugins/csv_serializer.rb +26 -9
- data/lib/sequel/plugins/dirty.rb +3 -9
- data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
- data/lib/sequel/plugins/json_serializer.rb +15 -4
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/sharding.rb +11 -5
- data/lib/sequel/plugins/throw_failures.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +3 -2
- data/lib/sequel/sql.rb +4 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +82 -17
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +1 -1
- data/spec/core/dataset_spec.rb +0 -3
- data/spec/core/expression_filters_spec.rb +26 -7
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/extensions/any_not_empty_spec.rb +23 -0
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/caller_logging_spec.rb +1 -1
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/empty_failure_backtraces_spec.rb +60 -0
- data/spec/extensions/exclude_or_null_spec.rb +15 -0
- data/spec/extensions/json_serializer_spec.rb +10 -0
- data/spec/extensions/named_timezones_spec.rb +5 -5
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_array_ops_spec.rb +3 -3
- data/spec/extensions/pg_json_ops_spec.rb +67 -0
- data/spec/extensions/pg_range_spec.rb +35 -21
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/guards_helper.rb +1 -1
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/dataset_test.rb +57 -17
- data/spec/integration/plugin_test.rb +1 -1
- data/spec/integration/schema_test.rb +9 -0
- data/spec/integration/spec_helper.rb +7 -1
- data/spec/model/plugins_spec.rb +2 -2
- data/spec/model/spec_helper.rb +1 -1
- data/spec/sequel_warning.rb +1 -0
- metadata +35 -3
@@ -230,6 +230,12 @@ module Sequel
|
|
230
230
|
supports_cte_in_subqueries?
|
231
231
|
end
|
232
232
|
|
233
|
+
# Whether the dataset supports the FILTER clause for aggregate functions.
|
234
|
+
# If not, support is emulated using CASE.
|
235
|
+
def supports_filtered_aggregates?
|
236
|
+
false
|
237
|
+
end
|
238
|
+
|
233
239
|
# Whether the database supports quoting function names.
|
234
240
|
def supports_quoted_function_names?
|
235
241
|
false
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -329,7 +329,7 @@ module Sequel
|
|
329
329
|
end
|
330
330
|
|
331
331
|
# Set the db, opts, and cache for the copy of the dataset.
|
332
|
-
def
|
332
|
+
def initialize_clone(c, _=nil)
|
333
333
|
@db = c.db
|
334
334
|
@opts = Hash[c.opts]
|
335
335
|
if cols = c.cache_get(:_columns)
|
@@ -338,7 +338,7 @@ module Sequel
|
|
338
338
|
@cache = {}
|
339
339
|
end
|
340
340
|
end
|
341
|
-
alias initialize_clone
|
341
|
+
alias initialize_copy initialize_clone
|
342
342
|
|
343
343
|
# Internal recursive version of unqualified_column_for, handling Strings inside
|
344
344
|
# of other objects.
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -1237,6 +1237,20 @@ module Sequel
|
|
1237
1237
|
self
|
1238
1238
|
end
|
1239
1239
|
|
1240
|
+
# If invert is true, invert the condition.
|
1241
|
+
def _invert_filter(cond, invert)
|
1242
|
+
if invert
|
1243
|
+
SQL::BooleanExpression.invert(cond)
|
1244
|
+
else
|
1245
|
+
cond
|
1246
|
+
end
|
1247
|
+
end
|
1248
|
+
|
1249
|
+
# Add the given filter condition. Arguments:
|
1250
|
+
# clause :: Symbol or which SQL clause to effect, should be :where or :having
|
1251
|
+
# cond :: The filter condition to add
|
1252
|
+
# invert :: Whether the condition should be inverted (true or false)
|
1253
|
+
# combine :: How to combine the condition with an existing condition, should be :AND or :OR
|
1240
1254
|
def add_filter(clause, cond, invert=false, combine=:AND, &block)
|
1241
1255
|
if cond == EMPTY_ARRAY && !block
|
1242
1256
|
raise Error, "must provide an argument to a filtering method if not passing a block"
|
@@ -1256,8 +1270,7 @@ module Sequel
|
|
1256
1270
|
cond = nil
|
1257
1271
|
end
|
1258
1272
|
|
1259
|
-
cond = filter_expr(cond, &block)
|
1260
|
-
cond = SQL::BooleanExpression.invert(cond) if invert
|
1273
|
+
cond = _invert_filter(filter_expr(cond, &block), invert)
|
1261
1274
|
cond = SQL::BooleanExpression.new(combine, @opts[clause], cond) if @opts[clause]
|
1262
1275
|
|
1263
1276
|
if cond.nil?
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -492,11 +492,24 @@ module Sequel
|
|
492
492
|
end
|
493
493
|
|
494
494
|
sql << '('
|
495
|
+
if filter = opts[:filter]
|
496
|
+
filter = filter_expr(filter, &opts[:filter_block])
|
497
|
+
end
|
495
498
|
if opts[:*]
|
496
|
-
|
499
|
+
if filter && !supports_filtered_aggregates?
|
500
|
+
literal_append(sql, Sequel.case({filter=>1}, nil))
|
501
|
+
filter = nil
|
502
|
+
else
|
503
|
+
sql << '*'
|
504
|
+
end
|
497
505
|
else
|
498
506
|
sql << "DISTINCT " if opts[:distinct]
|
499
|
-
|
507
|
+
if filter && !supports_filtered_aggregates?
|
508
|
+
expression_list_append(sql, f.args.map{|arg| Sequel.case({filter=>arg}, nil)})
|
509
|
+
filter = nil
|
510
|
+
else
|
511
|
+
expression_list_append(sql, f.args)
|
512
|
+
end
|
500
513
|
if order = opts[:order]
|
501
514
|
sql << " ORDER BY "
|
502
515
|
expression_list_append(sql, order)
|
@@ -510,9 +523,9 @@ module Sequel
|
|
510
523
|
sql << ')'
|
511
524
|
end
|
512
525
|
|
513
|
-
if filter
|
526
|
+
if filter
|
514
527
|
sql << " FILTER (WHERE "
|
515
|
-
literal_append(sql,
|
528
|
+
literal_append(sql, filter)
|
516
529
|
sql << ')'
|
517
530
|
end
|
518
531
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The any_not_empty extension changes the behavior of Dataset#any?
|
4
|
+
# if called without a block. By default, this method uses the
|
5
|
+
# standard Enumerable behavior of enumerating results and seeing
|
6
|
+
# if any result is not false or nil. With this extension, it
|
7
|
+
# just checks whether the dataset is empty. This approach can
|
8
|
+
# be much faster if the dataset is currently large.
|
9
|
+
#
|
10
|
+
# DB[:table].any?
|
11
|
+
# # SELECT * FROM table
|
12
|
+
#
|
13
|
+
# DB[:table].extension(:any_not_empty).any?
|
14
|
+
# # SELECT 1 as one FROM table LIMIT 1
|
15
|
+
#
|
16
|
+
# You can load this extension into specific datasets:
|
17
|
+
#
|
18
|
+
# ds = DB[:table]
|
19
|
+
# ds = ds.extension(:any_not_empty)
|
20
|
+
#
|
21
|
+
# Or you can load it into all of a database's datasets, which
|
22
|
+
# is probably the desired behavior if you are using this extension:
|
23
|
+
#
|
24
|
+
# DB.extension(:any_not_empty)
|
25
|
+
#
|
26
|
+
# Note that this can result in any? returning a different result if
|
27
|
+
# the dataset has a row_proc that can return false or nil.
|
28
|
+
#
|
29
|
+
# Related module: Sequel::AnyNotEmpty
|
30
|
+
|
31
|
+
#
|
32
|
+
module Sequel
|
33
|
+
module AnyNotEmpty
|
34
|
+
# If a block is not given, return whether the dataset is not empty.
|
35
|
+
def any?
|
36
|
+
if block_given?
|
37
|
+
super
|
38
|
+
else
|
39
|
+
!empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Dataset.register_extension(:any_not_empty, AnyNotEmpty)
|
45
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The exclude_or_null extension adds Dataset#exclude_or_null and
|
4
|
+
# Dataset#exclude_or_null_having. These methods are similar to
|
5
|
+
# Dataset#exclude and Dataset#exclude_having, except that they
|
6
|
+
# will also exclude rows where the condition IS NULL.
|
7
|
+
#
|
8
|
+
# DB[:table].exclude_or_null(foo: 1)
|
9
|
+
# # SELECT * FROM table WHERE NOT coalesce((foo = 1), false)
|
10
|
+
#
|
11
|
+
# DB[:table].exclude_or_null{foo(bar) =~ 1}
|
12
|
+
# # SELECT * FROM table HAVING NOT coalesce((foo(bar) = 1), false))
|
13
|
+
#
|
14
|
+
# You can load this extension into specific datasets:
|
15
|
+
#
|
16
|
+
# ds = DB[:table]
|
17
|
+
# ds = ds.extension(:exclude_or_null)
|
18
|
+
#
|
19
|
+
# Or you can load it into all of a database's datasets, which
|
20
|
+
# is probably the desired behavior if you are using this extension:
|
21
|
+
#
|
22
|
+
# DB.extension(:exclude_or_null)
|
23
|
+
#
|
24
|
+
# Note, this extension works correctly on PostgreSQL, SQLite, MySQL,
|
25
|
+
# H2, and HSQLDB. However, it does not work correctly on Microsoft SQL Server,
|
26
|
+
# Oracle, DB2, SQLAnywhere, or Derby.
|
27
|
+
#
|
28
|
+
# Related module: Sequel::ExcludeOrNull
|
29
|
+
|
30
|
+
#
|
31
|
+
module Sequel
|
32
|
+
module ExcludeOrNull
|
33
|
+
# Performs the inverse of Dataset#where, but also excludes rows where the given
|
34
|
+
# condition IS NULL.
|
35
|
+
#
|
36
|
+
# DB[:items].exclude_or_null(category: 'software')
|
37
|
+
# # SELECT * FROM items WHERE NOT coalesce((category = 'software'), false)
|
38
|
+
#
|
39
|
+
# DB[:items].exclude_or_null(category: 'software', id: 3)
|
40
|
+
# # SELECT * FROM items WHERE NOT coalesce(((category = 'software') AND (id = 3)), false)
|
41
|
+
def exclude_or_null(*cond, &block)
|
42
|
+
add_filter(:where, cond, :or_null, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# The same as exclude_or_null, but affecting the HAVING clause instead of the
|
46
|
+
# WHERE clause.
|
47
|
+
#
|
48
|
+
# DB[:items].select_group(:name).exclude_or_null_having{count(name) < 2}
|
49
|
+
# # SELECT name FROM items GROUP BY name HAVING NOT coalesce((count(name) < 2), true)
|
50
|
+
def exclude_or_null_having(*cond, &block)
|
51
|
+
add_filter(:having, cond, :or_null, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Recognize :or_null value for invert, returning an expression for
|
57
|
+
# the invert of the condition or the condition being null.
|
58
|
+
def _invert_filter(cond, invert)
|
59
|
+
if invert == :or_null
|
60
|
+
~SQL::Function.new(:coalesce, cond, SQL::Constants::SQLFALSE)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Dataset.register_extension(:exclude_or_null, ExcludeOrNull)
|
68
|
+
end
|
@@ -51,8 +51,8 @@
|
|
51
51
|
# ia.length(2) # array_length(int_array_column, 2)
|
52
52
|
# ia.lower # array_lower(int_array_column, 1)
|
53
53
|
# ia.lower(2) # array_lower(int_array_column, 2)
|
54
|
-
# ia.join # array_to_string(int_array_column, ''
|
55
|
-
# ia.join(':') # array_to_string(int_array_column, ':'
|
54
|
+
# ia.join # array_to_string(int_array_column, '')
|
55
|
+
# ia.join(':') # array_to_string(int_array_column, ':')
|
56
56
|
# ia.join(':', ' ') # array_to_string(int_array_column, ':', ' ')
|
57
57
|
# ia.unnest # unnest(int_array_column)
|
58
58
|
# ia.unnest(:b) # unnest(int_array_column, b)
|
@@ -217,12 +217,16 @@ module Sequel
|
|
217
217
|
|
218
218
|
# Call the array_to_string method:
|
219
219
|
#
|
220
|
-
# array_op.join # array_to_string(array, ''
|
221
|
-
# array_op.to_string # array_to_string(array, ''
|
222
|
-
# array_op.join(":") # array_to_string(array, ':'
|
220
|
+
# array_op.join # array_to_string(array, '')
|
221
|
+
# array_op.to_string # array_to_string(array, '')
|
222
|
+
# array_op.join(":") # array_to_string(array, ':')
|
223
223
|
# array_op.join(":", "*") # array_to_string(array, ':', '*')
|
224
224
|
def to_string(joiner="", null=nil)
|
225
|
-
|
225
|
+
if null.nil?
|
226
|
+
function(:array_to_string, joiner)
|
227
|
+
else
|
228
|
+
function(:array_to_string, joiner, null)
|
229
|
+
end
|
226
230
|
end
|
227
231
|
alias join to_string
|
228
232
|
|
@@ -133,8 +133,11 @@ module Sequel
|
|
133
133
|
# the pg_type table to get names and array oids for
|
134
134
|
# enums.
|
135
135
|
def parse_enum_labels
|
136
|
+
order = [:enumtypid]
|
137
|
+
order << :enumsortorder if server_version >= 90100
|
138
|
+
|
136
139
|
enum_labels = metadata_dataset.from(:pg_enum).
|
137
|
-
order(
|
140
|
+
order(*order).
|
138
141
|
select_hash_groups(Sequel.cast(:enumtypid, Integer).as(:v), :enumlabel).freeze
|
139
142
|
enum_labels.each_value(&:freeze)
|
140
143
|
|
@@ -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
|
#
|
@@ -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.
|
@@ -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
|