sequel 4.10.0 → 4.11.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/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
|