sequel 5.24.0 → 5.29.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.
- 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
|