sequel 4.10.0 → 4.11.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/doc/association_basics.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +0 -1
- data/doc/core_extensions.rdoc +2 -2
- data/doc/dataset_filtering.rdoc +5 -5
- data/doc/model_hooks.rdoc +9 -0
- data/doc/object_model.rdoc +7 -13
- data/doc/opening_databases.rdoc +3 -1
- data/doc/querying.rdoc +8 -8
- data/doc/release_notes/4.11.0.txt +147 -0
- data/doc/sql.rdoc +11 -7
- data/doc/virtual_rows.rdoc +4 -5
- data/lib/sequel/adapters/ibmdb.rb +24 -16
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/mock.rb +14 -2
- data/lib/sequel/adapters/shared/access.rb +6 -9
- data/lib/sequel/adapters/shared/cubrid.rb +5 -0
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/firebird.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +23 -16
- data/lib/sequel/adapters/shared/mysql.rb +12 -2
- data/lib/sequel/adapters/shared/oracle.rb +31 -15
- data/lib/sequel/adapters/shared/postgres.rb +28 -4
- data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
- data/lib/sequel/adapters/shared/sqlite.rb +12 -1
- data/lib/sequel/ast_transformer.rb +9 -7
- data/lib/sequel/connection_pool.rb +10 -4
- data/lib/sequel/database/features.rb +15 -0
- data/lib/sequel/database/schema_generator.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +21 -3
- data/lib/sequel/database/transactions.rb +8 -4
- data/lib/sequel/dataset/actions.rb +13 -7
- data/lib/sequel/dataset/features.rb +7 -0
- data/lib/sequel/dataset/query.rb +28 -11
- data/lib/sequel/dataset/sql.rb +90 -14
- data/lib/sequel/extensions/constraint_validations.rb +2 -2
- data/lib/sequel/extensions/date_arithmetic.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +12 -6
- data/lib/sequel/extensions/pg_array_ops.rb +11 -2
- data/lib/sequel/extensions/pg_json.rb +130 -23
- data/lib/sequel/extensions/pg_json_ops.rb +196 -28
- data/lib/sequel/extensions/to_dot.rb +5 -7
- data/lib/sequel/model/associations.rb +0 -50
- data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
- data/lib/sequel/plugins/many_through_many.rb +10 -11
- data/lib/sequel/plugins/serialization.rb +4 -1
- data/lib/sequel/plugins/sharding.rb +0 -9
- data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/sql.rb +166 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +199 -133
- data/spec/core/connection_pool_spec.rb +6 -0
- data/spec/core/database_spec.rb +12 -0
- data/spec/core/dataset_spec.rb +58 -3
- data/spec/core/expression_filters_spec.rb +67 -5
- data/spec/core/mock_adapter_spec.rb +8 -4
- data/spec/core/schema_spec.rb +7 -0
- data/spec/core_extensions_spec.rb +14 -0
- data/spec/extensions/class_table_inheritance_spec.rb +23 -3
- data/spec/extensions/core_refinements_spec.rb +14 -0
- data/spec/extensions/eval_inspect_spec.rb +8 -4
- data/spec/extensions/pg_array_ops_spec.rb +6 -0
- data/spec/extensions/pg_json_ops_spec.rb +99 -0
- data/spec/extensions/pg_json_spec.rb +104 -4
- data/spec/extensions/serialization_spec.rb +19 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -3
- data/spec/extensions/timestamps_spec.rb +10 -0
- data/spec/extensions/to_dot_spec.rb +8 -4
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +9 -0
- data/spec/integration/schema_test.rb +27 -0
- metadata +4 -2
@@ -70,8 +70,8 @@
|
|
70
70
|
# length_range 3...5 :: CHECK char_length(column) >= 3 AND char_length(column) < 5
|
71
71
|
# format /foo\\d+/ :: CHECK column ~ 'foo\\d+'
|
72
72
|
# format /foo\\d+/i :: CHECK column ~* 'foo\\d+'
|
73
|
-
# like 'foo%' :: CHECK column LIKE 'foo%'
|
74
|
-
# ilike 'foo%' :: CHECK column ILIKE 'foo%'
|
73
|
+
# like 'foo%' :: CHECK column LIKE 'foo%' ESCAPE '\'
|
74
|
+
# ilike 'foo%' :: CHECK column ILIKE 'foo%' ESCAPE '\'
|
75
75
|
# includes ['a', 'b'] :: CHECK column IN ('a', 'b')
|
76
76
|
# includes [1, 2] :: CHECK column IN (1, 2)
|
77
77
|
# includes 3..5 :: CHECK column >= 3 AND column <= 5
|
@@ -82,7 +82,7 @@ module Sequel
|
|
82
82
|
each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
|
83
83
|
args << "#{value} #{sql_unit}"
|
84
84
|
end
|
85
|
-
return
|
85
|
+
return function_sql_append(sql, Sequel.function(:datetime, *args))
|
86
86
|
when :mysql, :hsqldb, :cubrid
|
87
87
|
if db_type == :hsqldb
|
88
88
|
# HSQLDB requires 2.2.9+ for the DATE_ADD function
|
@@ -74,7 +74,7 @@ module Sequel
|
|
74
74
|
Sequel.eval_inspect(send(arg))
|
75
75
|
end
|
76
76
|
end
|
77
|
-
"#{klass}
|
77
|
+
"#{klass}.#{inspect_new_method}(#{args.join(', ')})"
|
78
78
|
end
|
79
79
|
|
80
80
|
private
|
@@ -83,6 +83,11 @@ module Sequel
|
|
83
83
|
def inspect_args
|
84
84
|
self.class.comparison_attrs
|
85
85
|
end
|
86
|
+
|
87
|
+
# Use the new method by default for creating new objects.
|
88
|
+
def inspect_new_method
|
89
|
+
:new
|
90
|
+
end
|
86
91
|
end
|
87
92
|
|
88
93
|
class ComplexExpression
|
@@ -127,9 +132,10 @@ module Sequel
|
|
127
132
|
class Function
|
128
133
|
private
|
129
134
|
|
130
|
-
# Function
|
131
|
-
|
132
|
-
|
135
|
+
# Function uses a new! method for creating functions with options,
|
136
|
+
# since Function.new does not allow for an options hash.
|
137
|
+
def inspect_new_method
|
138
|
+
:new!
|
133
139
|
end
|
134
140
|
end
|
135
141
|
|
@@ -139,7 +145,7 @@ module Sequel
|
|
139
145
|
# JoinOnClause's initializer takes the on argument as the first argument
|
140
146
|
# instead of the last.
|
141
147
|
def inspect_args
|
142
|
-
[:on, :join_type, :
|
148
|
+
[:on, :join_type, :table_expr]
|
143
149
|
end
|
144
150
|
end
|
145
151
|
|
@@ -149,7 +155,7 @@ module Sequel
|
|
149
155
|
# JoinOnClause's initializer takes the using argument as the first argument
|
150
156
|
# instead of the last.
|
151
157
|
def inspect_args
|
152
|
-
[:using, :join_type, :
|
158
|
+
[:using, :join_type, :table_expr]
|
153
159
|
end
|
154
160
|
end
|
155
161
|
|
@@ -41,6 +41,7 @@
|
|
41
41
|
#
|
42
42
|
# ia.any # ANY(int_array_column)
|
43
43
|
# ia.all # ALL(int_array_column)
|
44
|
+
# ia.cardinality # cardinality(int_array_column)
|
44
45
|
# ia.dims # array_dims(int_array_column)
|
45
46
|
# ia.hstore # hstore(int_array_column)
|
46
47
|
# ia.hstore(:a) # hstore(int_array_column, a)
|
@@ -52,6 +53,7 @@
|
|
52
53
|
# ia.join(':') # array_to_string(int_array_column, ':', NULL)
|
53
54
|
# ia.join(':', ' ') # array_to_string(int_array_column, ':', ' ')
|
54
55
|
# ia.unnest # unnest(int_array_column)
|
56
|
+
# ia.unnest(:b) # unnest(int_array_column, b)
|
55
57
|
#
|
56
58
|
# See the PostgreSQL array function and operator documentation for more
|
57
59
|
# details on what these functions and operators do.
|
@@ -111,6 +113,13 @@ module Sequel
|
|
111
113
|
function(:ANY)
|
112
114
|
end
|
113
115
|
|
116
|
+
# Call the cardinality method:
|
117
|
+
#
|
118
|
+
# array_op.cardinality # cardinality(array)
|
119
|
+
def cardinality
|
120
|
+
function(:cardinality)
|
121
|
+
end
|
122
|
+
|
114
123
|
# Use the contains (@>) operator:
|
115
124
|
#
|
116
125
|
# array_op.contains(:a) # (array @> a)
|
@@ -215,8 +224,8 @@ module Sequel
|
|
215
224
|
# Call the unnest method:
|
216
225
|
#
|
217
226
|
# array_op.unnest # unnest(array)
|
218
|
-
def unnest
|
219
|
-
function(:unnest)
|
227
|
+
def unnest(*args)
|
228
|
+
function(:unnest, *args.map{|a| wrap_array(a)})
|
220
229
|
end
|
221
230
|
|
222
231
|
# Use the concatentation (||) operator, reversing the order:
|
@@ -64,42 +64,74 @@ Sequel.require 'adapters/utils/pg_types'
|
|
64
64
|
module Sequel
|
65
65
|
module Postgres
|
66
66
|
CAST_JSON = '::json'.freeze
|
67
|
+
CAST_JSONB = '::jsonb'.freeze
|
67
68
|
|
68
|
-
# Class
|
69
|
-
class
|
69
|
+
# Class representing PostgreSQL JSON/JSONB column array values.
|
70
|
+
class JSONArrayBase < DelegateClass(Array)
|
70
71
|
include Sequel::SQL::AliasMethods
|
72
|
+
include Sequel::SQL::CastMethods
|
71
73
|
|
72
|
-
# Convert the array to a json string
|
73
|
-
# literalized version of the string to the sql
|
74
|
-
# cast the string to json.
|
74
|
+
# Convert the array to a json string and append a
|
75
|
+
# literalized version of the string to the sql.
|
75
76
|
def sql_literal_append(ds, sql)
|
76
77
|
ds.literal_append(sql, Sequel.object_to_json(self))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class JSONArray < JSONArrayBase
|
82
|
+
# Cast as json
|
83
|
+
def sql_literal_append(ds, sql)
|
84
|
+
super
|
77
85
|
sql << CAST_JSON
|
78
86
|
end
|
79
87
|
end
|
80
88
|
|
81
|
-
|
82
|
-
|
89
|
+
class JSONBArray < JSONArrayBase
|
90
|
+
# Cast as jsonb
|
91
|
+
def sql_literal_append(ds, sql)
|
92
|
+
super
|
93
|
+
sql << CAST_JSONB
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Class representing PostgreSQL JSON/JSONB column hash/object values.
|
98
|
+
class JSONHashBase < DelegateClass(Hash)
|
83
99
|
include Sequel::SQL::AliasMethods
|
100
|
+
include Sequel::SQL::CastMethods
|
84
101
|
|
85
|
-
# Convert the hash to a json string
|
86
|
-
# literalized version of the string to the sql
|
87
|
-
# cast the string to json.
|
102
|
+
# Convert the hash to a json string and append a
|
103
|
+
# literalized version of the string to the sql.
|
88
104
|
def sql_literal_append(ds, sql)
|
89
105
|
ds.literal_append(sql, Sequel.object_to_json(self))
|
90
|
-
sql << CAST_JSON
|
91
106
|
end
|
92
107
|
|
93
108
|
# Return the object being delegated to.
|
94
109
|
alias to_hash __getobj__
|
95
110
|
end
|
96
111
|
|
112
|
+
class JSONHash < JSONHashBase
|
113
|
+
# Cast as json
|
114
|
+
def sql_literal_append(ds, sql)
|
115
|
+
super
|
116
|
+
sql << CAST_JSON
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class JSONBHash < JSONHashBase
|
121
|
+
# Cast as jsonb
|
122
|
+
def sql_literal_append(ds, sql)
|
123
|
+
super
|
124
|
+
sql << CAST_JSONB
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
97
128
|
# Methods enabling Database object integration with the json type.
|
98
129
|
module JSONDatabaseMethods
|
99
130
|
def self.extended(db)
|
100
131
|
db.instance_eval do
|
101
|
-
copy_conversion_procs([114, 199])
|
132
|
+
copy_conversion_procs([114, 199, 3802, 3807])
|
102
133
|
@schema_type_classes[:json] = [JSONHash, JSONArray]
|
134
|
+
@schema_type_classes[:jsonb] = [JSONBHash, JSONBArray]
|
103
135
|
end
|
104
136
|
end
|
105
137
|
|
@@ -113,10 +145,18 @@ module Sequel
|
|
113
145
|
parse_json("[#{s}]").first
|
114
146
|
end
|
115
147
|
|
148
|
+
# Same as db_parse_json, but consider the input as jsonb.
|
149
|
+
def self.db_parse_jsonb(s)
|
150
|
+
parse_json(s, true)
|
151
|
+
rescue Sequel::InvalidValue
|
152
|
+
raise unless s.is_a?(String)
|
153
|
+
parse_json("[#{s}]").first
|
154
|
+
end
|
155
|
+
|
116
156
|
# Parse the given string as json, returning either a JSONArray
|
117
157
|
# or JSONHash instance, and raising an error if the JSON
|
118
158
|
# parsing does not yield an array or hash.
|
119
|
-
def self.parse_json(s)
|
159
|
+
def self.parse_json(s, jsonb=false)
|
120
160
|
begin
|
121
161
|
value = Sequel.parse_json(s)
|
122
162
|
rescue Sequel.json_parser_error_class => e
|
@@ -125,9 +165,9 @@ module Sequel
|
|
125
165
|
|
126
166
|
case value
|
127
167
|
when Array
|
128
|
-
JSONArray.new(value)
|
168
|
+
(jsonb ? JSONBArray : JSONArray).new(value)
|
129
169
|
when Hash
|
130
|
-
JSONHash.new(value)
|
170
|
+
(jsonb ? JSONBHash : JSONHash).new(value)
|
131
171
|
else
|
132
172
|
raise Sequel::InvalidValue, "unhandled json value: #{value.inspect} (from #{s.inspect})"
|
133
173
|
end
|
@@ -136,7 +176,7 @@ module Sequel
|
|
136
176
|
# Handle JSONArray and JSONHash in bound variables
|
137
177
|
def bound_variable_arg(arg, conn)
|
138
178
|
case arg
|
139
|
-
when
|
179
|
+
when JSONArrayBase, JSONHashBase
|
140
180
|
Sequel.object_to_json(arg)
|
141
181
|
else
|
142
182
|
super
|
@@ -148,7 +188,7 @@ module Sequel
|
|
148
188
|
# Handle json[] types in bound variables.
|
149
189
|
def bound_variable_array(a)
|
150
190
|
case a
|
151
|
-
when
|
191
|
+
when JSONHashBase, JSONArrayBase
|
152
192
|
"\"#{Sequel.object_to_json(a).gsub('"', '\\"')}\""
|
153
193
|
else
|
154
194
|
super
|
@@ -160,17 +200,14 @@ module Sequel
|
|
160
200
|
case db_type
|
161
201
|
when 'json'
|
162
202
|
:json
|
203
|
+
when 'jsonb'
|
204
|
+
:jsonb
|
163
205
|
else
|
164
206
|
super
|
165
207
|
end
|
166
208
|
end
|
167
209
|
|
168
|
-
#
|
169
|
-
# * If given a JSONArray or JSONHash, just return the value
|
170
|
-
# * If given an Array, return a JSONArray
|
171
|
-
# * If given a Hash, return a JSONHash
|
172
|
-
# * If given a String, parse it as would be done during
|
173
|
-
# database retrieval.
|
210
|
+
# Convert the value given to a JSONArray or JSONHash
|
174
211
|
def typecast_value_json(value)
|
175
212
|
case value
|
176
213
|
when JSONArray, JSONHash
|
@@ -179,17 +216,43 @@ module Sequel
|
|
179
216
|
JSONArray.new(value)
|
180
217
|
when Hash
|
181
218
|
JSONHash.new(value)
|
219
|
+
when JSONBArray
|
220
|
+
JSONArray.new(value.to_a)
|
221
|
+
when JSONBHash
|
222
|
+
JSONHash.new(value.to_hash)
|
182
223
|
when String
|
183
224
|
JSONDatabaseMethods.parse_json(value)
|
184
225
|
else
|
185
226
|
raise Sequel::InvalidValue, "invalid value for json: #{value.inspect}"
|
186
227
|
end
|
187
228
|
end
|
229
|
+
|
230
|
+
# Convert the value given to a JSONBArray or JSONBHash
|
231
|
+
def typecast_value_jsonb(value)
|
232
|
+
case value
|
233
|
+
when JSONBArray, JSONBHash
|
234
|
+
value
|
235
|
+
when Array
|
236
|
+
JSONBArray.new(value)
|
237
|
+
when Hash
|
238
|
+
JSONBHash.new(value)
|
239
|
+
when JSONArray
|
240
|
+
JSONBArray.new(value.to_a)
|
241
|
+
when JSONHash
|
242
|
+
JSONBHash.new(value.to_hash)
|
243
|
+
when String
|
244
|
+
JSONDatabaseMethods.parse_json(value, true)
|
245
|
+
else
|
246
|
+
raise Sequel::InvalidValue, "invalid value for jsonb: #{value.inspect}"
|
247
|
+
end
|
248
|
+
end
|
188
249
|
end
|
189
250
|
|
190
251
|
PG_TYPES[114] = JSONDatabaseMethods.method(:db_parse_json)
|
252
|
+
PG_TYPES[3802] = JSONDatabaseMethods.method(:db_parse_jsonb)
|
191
253
|
if defined?(PGArray) && PGArray.respond_to?(:register)
|
192
254
|
PGArray.register('json', :oid=>199, :scalar_oid=>114)
|
255
|
+
PGArray.register('jsonb', :oid=>3807, :scalar_oid=>3802)
|
193
256
|
end
|
194
257
|
end
|
195
258
|
|
@@ -203,6 +266,28 @@ module Sequel
|
|
203
266
|
Postgres::JSONArray.new(v)
|
204
267
|
when Hash
|
205
268
|
Postgres::JSONHash.new(v)
|
269
|
+
when Postgres::JSONBArray
|
270
|
+
Postgres::JSONArray.new(v.to_a)
|
271
|
+
when Postgres::JSONBHash
|
272
|
+
Postgres::JSONHash.new(v.to_hash)
|
273
|
+
else
|
274
|
+
Sequel.pg_json_op(v)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Wrap the array or hash in a Postgres::JSONArray or Postgres::JSONHash.
|
279
|
+
def pg_jsonb(v)
|
280
|
+
case v
|
281
|
+
when Postgres::JSONBArray, Postgres::JSONBHash
|
282
|
+
v
|
283
|
+
when Array
|
284
|
+
Postgres::JSONBArray.new(v)
|
285
|
+
when Hash
|
286
|
+
Postgres::JSONBHash.new(v)
|
287
|
+
when Postgres::JSONArray
|
288
|
+
Postgres::JSONBArray.new(v.to_a)
|
289
|
+
when Postgres::JSONHash
|
290
|
+
Postgres::JSONBHash.new(v.to_hash)
|
206
291
|
else
|
207
292
|
Sequel.pg_json_op(v)
|
208
293
|
end
|
@@ -221,6 +306,13 @@ if Sequel.core_extensions?
|
|
221
306
|
def pg_json
|
222
307
|
Sequel::Postgres::JSONArray.new(self)
|
223
308
|
end
|
309
|
+
|
310
|
+
# Return a Sequel::Postgres::JSONArray proxy to the receiver.
|
311
|
+
# This is mostly useful as a short cut for creating JSONArray
|
312
|
+
# objects that didn't come from the database.
|
313
|
+
def pg_jsonb
|
314
|
+
Sequel::Postgres::JSONBArray.new(self)
|
315
|
+
end
|
224
316
|
end
|
225
317
|
|
226
318
|
class Hash
|
@@ -230,6 +322,13 @@ if Sequel.core_extensions?
|
|
230
322
|
def pg_json
|
231
323
|
Sequel::Postgres::JSONHash.new(self)
|
232
324
|
end
|
325
|
+
|
326
|
+
# Return a Sequel::Postgres::JSONHash proxy to the receiver.
|
327
|
+
# This is mostly useful as a short cut for creating JSONHash
|
328
|
+
# objects that didn't come from the database.
|
329
|
+
def pg_jsonb
|
330
|
+
Sequel::Postgres::JSONBHash.new(self)
|
331
|
+
end
|
233
332
|
end
|
234
333
|
end
|
235
334
|
|
@@ -239,12 +338,20 @@ if defined?(Sequel::CoreRefinements)
|
|
239
338
|
def pg_json
|
240
339
|
Sequel::Postgres::JSONArray.new(self)
|
241
340
|
end
|
341
|
+
|
342
|
+
def pg_jsonb
|
343
|
+
Sequel::Postgres::JSONBArray.new(self)
|
344
|
+
end
|
242
345
|
end
|
243
346
|
|
244
347
|
refine Hash do
|
245
348
|
def pg_json
|
246
349
|
Sequel::Postgres::JSONHash.new(self)
|
247
350
|
end
|
351
|
+
|
352
|
+
def pg_jsonb
|
353
|
+
Sequel::Postgres::JSONBHash.new(self)
|
354
|
+
end
|
248
355
|
end
|
249
356
|
end
|
250
357
|
end
|
@@ -1,32 +1,39 @@
|
|
1
1
|
# The pg_json_ops extension adds support to Sequel's DSL to make
|
2
2
|
# it easier to call PostgreSQL JSON functions and operators (added
|
3
|
-
# first in PostgreSQL 9.3).
|
3
|
+
# first in PostgreSQL 9.3). It also supports the JSONB functions
|
4
|
+
# and operators added in PostgreSQL 9.4).
|
4
5
|
#
|
5
6
|
# To load the extension:
|
6
7
|
#
|
7
8
|
# Sequel.extension :pg_json_ops
|
8
9
|
#
|
9
|
-
# The most common usage is passing an expression to Sequel.pg_json_op
|
10
|
+
# The most common usage is passing an expression to Sequel.pg_json_op
|
11
|
+
# or Sequel.pg_jsonb_op:
|
10
12
|
#
|
11
13
|
# j = Sequel.pg_json_op(:json_column)
|
14
|
+
# jb = Sequel.pg_jsonb_op(:jsonb_column)
|
12
15
|
#
|
13
16
|
# If you have also loaded the pg_json extension, you can use
|
14
|
-
# Sequel.pg_json as well:
|
17
|
+
# Sequel.pg_json or Sequel.pg_jsonb as well:
|
15
18
|
#
|
16
19
|
# j = Sequel.pg_json(:json_column)
|
20
|
+
# jb = Sequel.pg_jsonb(:jsonb_column)
|
17
21
|
#
|
18
22
|
# Also, on most Sequel expression objects, you can call the pg_json
|
19
|
-
# method:
|
23
|
+
# or pg_jsonb # method:
|
20
24
|
#
|
21
25
|
# j = Sequel.expr(:json_column).pg_json
|
26
|
+
# jb = Sequel.expr(:jsonb_column).pg_jsonb
|
22
27
|
#
|
23
28
|
# If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
|
24
29
|
# or you have loaded the core_refinements extension
|
25
|
-
# and have activated refinements for the file, you can also use Symbol#pg_json
|
30
|
+
# and have activated refinements for the file, you can also use Symbol#pg_json or
|
31
|
+
# Symbol#pg_jsonb:
|
26
32
|
#
|
27
33
|
# j = :json_column.pg_json
|
34
|
+
# jb = :jsonb_column.pg_jsonb
|
28
35
|
#
|
29
|
-
# This creates a Sequel::Postgres::JSONOp object that can be used
|
36
|
+
# This creates a Sequel::Postgres::JSONOp or Sequel::Postgres::JSONBOp object that can be used
|
30
37
|
# for easier querying:
|
31
38
|
#
|
32
39
|
# j[1] # (json_column -> 1)
|
@@ -38,17 +45,21 @@
|
|
38
45
|
#
|
39
46
|
# j.array_length # json_array_length(json_column)
|
40
47
|
# j.array_elements # json_array_elements(json_column)
|
48
|
+
# j.array_elements_text # json_array_elements_text(json_column)
|
41
49
|
# j.each # json_each(json_column)
|
42
50
|
# j.each_text # json_each_text(json_column)
|
43
51
|
# j.keys # json_object_keys(json_column)
|
52
|
+
# j.typeof # json_typeof(json_column)
|
44
53
|
#
|
45
54
|
# j.populate(:a) # json_populate_record(:a, json_column)
|
46
55
|
# j.populate_set(:a) # json_populate_recordset(:a, json_column)
|
56
|
+
# j.to_record # json_to_record(json_column)
|
57
|
+
# j.to_recordset # json_to_recordset(json_column)
|
47
58
|
#
|
48
59
|
# If you are also using the pg_json extension, you should load it before
|
49
|
-
# loading this extension. Doing so will allow you to use
|
50
|
-
#
|
51
|
-
# on json literals.
|
60
|
+
# loading this extension. Doing so will allow you to use the #op method on
|
61
|
+
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
62
|
+
# on json/jsonb literals.
|
52
63
|
#
|
53
64
|
# In order to get the automatic conversion from a ruby array to a PostgreSQL array
|
54
65
|
# (as shown in the #[] and #get_text examples above), you need to load the pg_array
|
@@ -56,14 +67,14 @@
|
|
56
67
|
|
57
68
|
module Sequel
|
58
69
|
module Postgres
|
59
|
-
# The
|
70
|
+
# The JSONBaseOp class is a simple container for a single object that
|
60
71
|
# defines methods that yield Sequel expression objects representing
|
61
72
|
# PostgreSQL json operators and functions.
|
62
73
|
#
|
63
74
|
# In the method documentation examples, assume that:
|
64
75
|
#
|
65
76
|
# json_op = Sequel.pg_json(:json)
|
66
|
-
class
|
77
|
+
class JSONBaseOp < Sequel::SQL::Wrapper
|
67
78
|
GET = ["(".freeze, " -> ".freeze, ")".freeze].freeze
|
68
79
|
GET_TEXT = ["(".freeze, " ->> ".freeze, ")".freeze].freeze
|
69
80
|
GET_PATH = ["(".freeze, " #> ".freeze, ")".freeze].freeze
|
@@ -86,16 +97,23 @@ module Sequel
|
|
86
97
|
|
87
98
|
# Returns a set of json values for the elements in the json array.
|
88
99
|
#
|
89
|
-
# json_op.array_elements #
|
100
|
+
# json_op.array_elements # json_array_elements(json)
|
90
101
|
def array_elements
|
91
|
-
function(:
|
102
|
+
function(:array_elements)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns a set of text values for the elements in the json array.
|
106
|
+
#
|
107
|
+
# json_op.array_elements_text # json_array_elements_text(json)
|
108
|
+
def array_elements_text
|
109
|
+
function(:array_elements_text)
|
92
110
|
end
|
93
111
|
|
94
112
|
# Get the length of the outermost json array.
|
95
113
|
#
|
96
114
|
# json_op.array_length # json_array_length(json)
|
97
115
|
def array_length
|
98
|
-
Sequel::SQL::NumericExpression.new(:NOOP, function(:
|
116
|
+
Sequel::SQL::NumericExpression.new(:NOOP, function(:array_length))
|
99
117
|
end
|
100
118
|
|
101
119
|
# Returns a set of key and value pairs, where the keys
|
@@ -103,7 +121,7 @@ module Sequel
|
|
103
121
|
#
|
104
122
|
# json_op.each # json_each(json)
|
105
123
|
def each
|
106
|
-
function(:
|
124
|
+
function(:each)
|
107
125
|
end
|
108
126
|
|
109
127
|
# Returns a set of key and value pairs, where the keys
|
@@ -111,7 +129,7 @@ module Sequel
|
|
111
129
|
#
|
112
130
|
# json_op.each_text # json_each_text(json)
|
113
131
|
def each_text
|
114
|
-
function(:
|
132
|
+
function(:each_text)
|
115
133
|
end
|
116
134
|
|
117
135
|
# Returns a json value for the object at the given path.
|
@@ -119,7 +137,7 @@ module Sequel
|
|
119
137
|
# json_op.extract('a') # json_extract_path(json, 'a')
|
120
138
|
# json_op.extract('a', 'b') # json_extract_path(json, 'a', 'b')
|
121
139
|
def extract(*a)
|
122
|
-
|
140
|
+
self.class.new(function(:extract_path, *a))
|
123
141
|
end
|
124
142
|
|
125
143
|
# Returns a text value for the object at the given path.
|
@@ -127,7 +145,7 @@ module Sequel
|
|
127
145
|
# json_op.extract_text('a') # json_extract_path_text(json, 'a')
|
128
146
|
# json_op.extract_text('a', 'b') # json_extract_path_text(json, 'a', 'b')
|
129
147
|
def extract_text(*a)
|
130
|
-
Sequel::SQL::StringExpression.new(:NOOP, function(:
|
148
|
+
Sequel::SQL::StringExpression.new(:NOOP, function(:extract_path_text, *a))
|
131
149
|
end
|
132
150
|
|
133
151
|
# Get JSON array element or object field as text. If an array is given,
|
@@ -148,26 +166,44 @@ module Sequel
|
|
148
166
|
#
|
149
167
|
# json_op.keys # json_object_keys(json)
|
150
168
|
def keys
|
151
|
-
function(:
|
152
|
-
end
|
153
|
-
|
154
|
-
# Return the receiver, since it is already a JSONOp.
|
155
|
-
def pg_json
|
156
|
-
self
|
169
|
+
function(:object_keys)
|
157
170
|
end
|
158
171
|
|
159
172
|
# Expands the given argument using the columns in the json.
|
160
173
|
#
|
161
174
|
# json_op.populate(arg) # json_populate_record(arg, json)
|
162
175
|
def populate(arg)
|
163
|
-
SQL::Function.new(:
|
176
|
+
SQL::Function.new(function_name(:populate_record), arg, self)
|
164
177
|
end
|
165
178
|
|
166
179
|
# Expands the given argument using the columns in the json.
|
167
180
|
#
|
168
181
|
# json_op.populate_set(arg) # json_populate_recordset(arg, json)
|
169
182
|
def populate_set(arg)
|
170
|
-
SQL::Function.new(:
|
183
|
+
SQL::Function.new(function_name(:populate_recordset), arg, self)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Builds arbitrary record from json object. You need to define the
|
187
|
+
# structure of the record using #as on the resulting object:
|
188
|
+
#
|
189
|
+
# json_op.to_record.as(:x, [Sequel.lit('a integer'), Sequel.lit('b text')]) # json_to_record(json) AS x(a integer, b text)
|
190
|
+
def to_record(nested_as_text=false)
|
191
|
+
function(:to_record, nested_as_text)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Builds arbitrary set of records from json array of objects. You need to define the
|
195
|
+
# structure of the records using #as on the resulting object:
|
196
|
+
#
|
197
|
+
# json_op.to_recordset.as(:x, [Sequel.lit('a integer'), Sequel.lit('b text')]) # json_to_recordset(json) AS x(a integer, b text)
|
198
|
+
def to_recordset(nested_as_text=false)
|
199
|
+
function(:to_recordset, nested_as_text)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the type of the outermost json value as text.
|
203
|
+
#
|
204
|
+
# json_op.typeof # json_typeof(json)
|
205
|
+
def typeof
|
206
|
+
function(:typeof)
|
171
207
|
end
|
172
208
|
|
173
209
|
private
|
@@ -181,7 +217,7 @@ module Sequel
|
|
181
217
|
# Return a function with the given name, and the receiver as the first
|
182
218
|
# argument, with any additional arguments given.
|
183
219
|
def function(name, *args)
|
184
|
-
SQL::Function.new(name, self, *args)
|
220
|
+
SQL::Function.new(function_name(name), self, *args)
|
185
221
|
end
|
186
222
|
|
187
223
|
# Whether the given object represents an array in PostgreSQL.
|
@@ -200,17 +236,123 @@ module Sequel
|
|
200
236
|
end
|
201
237
|
end
|
202
238
|
|
239
|
+
# JSONBaseOp subclass for the json type
|
240
|
+
class JSONOp < JSONBaseOp
|
241
|
+
# Return the receiver, since it is already a JSONOp.
|
242
|
+
def pg_json
|
243
|
+
self
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# The json type functions are prefixed with json_
|
249
|
+
def function_name(name)
|
250
|
+
"json_#{name}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# JSONBaseOp subclass for the jsonb type.
|
255
|
+
#
|
256
|
+
# In the method documentation examples, assume that:
|
257
|
+
#
|
258
|
+
# jsonb_op = Sequel.pg_jsonb(:jsonb)
|
259
|
+
class JSONBOp < JSONBaseOp
|
260
|
+
CONTAIN_ALL = ["(".freeze, " ?& ".freeze, ")".freeze].freeze
|
261
|
+
CONTAIN_ANY = ["(".freeze, " ?| ".freeze, ")".freeze].freeze
|
262
|
+
CONTAINS = ["(".freeze, " @> ".freeze, ")".freeze].freeze
|
263
|
+
CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
|
264
|
+
HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
|
265
|
+
|
266
|
+
# Check if the receiver contains all of the keys in the given array:
|
267
|
+
#
|
268
|
+
# jsonb_op.contain_all(:a) # (jsonb ?& a)
|
269
|
+
def contain_all(other)
|
270
|
+
bool_op(CONTAIN_ALL, wrap_input_array(other))
|
271
|
+
end
|
272
|
+
|
273
|
+
# Check if the receiver contains any of the keys in the given array:
|
274
|
+
#
|
275
|
+
# jsonb_op.contain_any(:a) # (jsonb ?| a)
|
276
|
+
def contain_any(other)
|
277
|
+
bool_op(CONTAIN_ANY, wrap_input_array(other))
|
278
|
+
end
|
279
|
+
|
280
|
+
# Check if the receiver contains all entries in the other jsonb:
|
281
|
+
#
|
282
|
+
# jsonb_op.contains(:h) # (jsonb @> h)
|
283
|
+
def contains(other)
|
284
|
+
bool_op(CONTAINS, wrap_input_jsonb(other))
|
285
|
+
end
|
286
|
+
|
287
|
+
# Check if the other jsonb contains all entries in the receiver:
|
288
|
+
#
|
289
|
+
# jsonb_op.contained_by(:h) # (jsonb <@ h)
|
290
|
+
def contained_by(other)
|
291
|
+
bool_op(CONTAINED_BY, wrap_input_jsonb(other))
|
292
|
+
end
|
293
|
+
|
294
|
+
# Check if the receiver contains the given key:
|
295
|
+
#
|
296
|
+
# jsonb_op.has_key?('a') # (jsonb ? 'a')
|
297
|
+
def has_key?(key)
|
298
|
+
bool_op(HAS_KEY, key)
|
299
|
+
end
|
300
|
+
alias include? has_key?
|
301
|
+
|
302
|
+
# Return the receiver, since it is already a JSONBOp.
|
303
|
+
def pg_jsonb
|
304
|
+
self
|
305
|
+
end
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
# Return a placeholder literal with the given str and args, wrapped
|
310
|
+
# in a boolean expression, used by operators that return booleans.
|
311
|
+
def bool_op(str, other)
|
312
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
|
313
|
+
end
|
314
|
+
|
315
|
+
# Wrap argument in a PGArray if it is an array
|
316
|
+
def wrap_input_array(obj)
|
317
|
+
if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
|
318
|
+
Sequel.pg_array(obj)
|
319
|
+
else
|
320
|
+
obj
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Wrap argument in a JSONBArray or JSONBHash if it is an array or hash.
|
325
|
+
def wrap_input_jsonb(obj)
|
326
|
+
if Sequel.respond_to?(:pg_jsonb) && (obj.is_a?(Array) || obj.is_a?(Hash))
|
327
|
+
Sequel.pg_jsonb(obj)
|
328
|
+
else
|
329
|
+
obj
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# The jsonb type functions are prefixed with jsonb_
|
334
|
+
def function_name(name)
|
335
|
+
"jsonb_#{name}"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
203
339
|
module JSONOpMethods
|
204
340
|
# Wrap the receiver in an JSONOp so you can easily use the PostgreSQL
|
205
341
|
# json functions and operators with it.
|
206
342
|
def pg_json
|
207
343
|
JSONOp.new(self)
|
208
344
|
end
|
345
|
+
#
|
346
|
+
# Wrap the receiver in an JSONBOp so you can easily use the PostgreSQL
|
347
|
+
# jsonb functions and operators with it.
|
348
|
+
def pg_jsonb
|
349
|
+
JSONBOp.new(self)
|
350
|
+
end
|
209
351
|
end
|
210
352
|
|
211
353
|
if defined?(JSONArray)
|
212
354
|
class JSONArray
|
213
|
-
# Wrap the
|
355
|
+
# Wrap the JSONArray instance in an JSONOp, allowing you to easily use
|
214
356
|
# the PostgreSQL json functions and operators with literal jsons.
|
215
357
|
def op
|
216
358
|
JSONOp.new(self)
|
@@ -224,6 +366,22 @@ module Sequel
|
|
224
366
|
JSONOp.new(self)
|
225
367
|
end
|
226
368
|
end
|
369
|
+
|
370
|
+
class JSONBArray
|
371
|
+
# Wrap the JSONBArray instance in an JSONBOp, allowing you to easily use
|
372
|
+
# the PostgreSQL jsonb functions and operators with literal jsonbs.
|
373
|
+
def op
|
374
|
+
JSONBOp.new(self)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
class JSONBHash
|
379
|
+
# Wrap the JSONBHash instance in an JSONBOp, allowing you to easily use
|
380
|
+
# the PostgreSQL jsonb functions and operators with literal jsonbs.
|
381
|
+
def op
|
382
|
+
JSONBOp.new(self)
|
383
|
+
end
|
384
|
+
end
|
227
385
|
end
|
228
386
|
end
|
229
387
|
|
@@ -237,6 +395,16 @@ module Sequel
|
|
237
395
|
Postgres::JSONOp.new(v)
|
238
396
|
end
|
239
397
|
end
|
398
|
+
|
399
|
+
# Return the object wrapped in an Postgres::JSONBOp.
|
400
|
+
def pg_jsonb_op(v)
|
401
|
+
case v
|
402
|
+
when Postgres::JSONBOp
|
403
|
+
v
|
404
|
+
else
|
405
|
+
Postgres::JSONBOp.new(v)
|
406
|
+
end
|
407
|
+
end
|
240
408
|
end
|
241
409
|
|
242
410
|
class SQL::GenericExpression
|