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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/cheat_sheet.rdoc +0 -1
  5. data/doc/core_extensions.rdoc +2 -2
  6. data/doc/dataset_filtering.rdoc +5 -5
  7. data/doc/model_hooks.rdoc +9 -0
  8. data/doc/object_model.rdoc +7 -13
  9. data/doc/opening_databases.rdoc +3 -1
  10. data/doc/querying.rdoc +8 -8
  11. data/doc/release_notes/4.11.0.txt +147 -0
  12. data/doc/sql.rdoc +11 -7
  13. data/doc/virtual_rows.rdoc +4 -5
  14. data/lib/sequel/adapters/ibmdb.rb +24 -16
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  17. data/lib/sequel/adapters/mock.rb +14 -2
  18. data/lib/sequel/adapters/shared/access.rb +6 -9
  19. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  20. data/lib/sequel/adapters/shared/db2.rb +5 -0
  21. data/lib/sequel/adapters/shared/firebird.rb +5 -0
  22. data/lib/sequel/adapters/shared/mssql.rb +23 -16
  23. data/lib/sequel/adapters/shared/mysql.rb +12 -2
  24. data/lib/sequel/adapters/shared/oracle.rb +31 -15
  25. data/lib/sequel/adapters/shared/postgres.rb +28 -4
  26. data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
  27. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  28. data/lib/sequel/ast_transformer.rb +9 -7
  29. data/lib/sequel/connection_pool.rb +10 -4
  30. data/lib/sequel/database/features.rb +15 -0
  31. data/lib/sequel/database/schema_generator.rb +2 -2
  32. data/lib/sequel/database/schema_methods.rb +21 -3
  33. data/lib/sequel/database/transactions.rb +8 -4
  34. data/lib/sequel/dataset/actions.rb +13 -7
  35. data/lib/sequel/dataset/features.rb +7 -0
  36. data/lib/sequel/dataset/query.rb +28 -11
  37. data/lib/sequel/dataset/sql.rb +90 -14
  38. data/lib/sequel/extensions/constraint_validations.rb +2 -2
  39. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  40. data/lib/sequel/extensions/eval_inspect.rb +12 -6
  41. data/lib/sequel/extensions/pg_array_ops.rb +11 -2
  42. data/lib/sequel/extensions/pg_json.rb +130 -23
  43. data/lib/sequel/extensions/pg_json_ops.rb +196 -28
  44. data/lib/sequel/extensions/to_dot.rb +5 -7
  45. data/lib/sequel/model/associations.rb +0 -50
  46. data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
  47. data/lib/sequel/plugins/many_through_many.rb +10 -11
  48. data/lib/sequel/plugins/serialization.rb +4 -1
  49. data/lib/sequel/plugins/sharding.rb +0 -9
  50. data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
  51. data/lib/sequel/plugins/timestamps.rb +2 -2
  52. data/lib/sequel/sql.rb +166 -44
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +199 -133
  55. data/spec/core/connection_pool_spec.rb +6 -0
  56. data/spec/core/database_spec.rb +12 -0
  57. data/spec/core/dataset_spec.rb +58 -3
  58. data/spec/core/expression_filters_spec.rb +67 -5
  59. data/spec/core/mock_adapter_spec.rb +8 -4
  60. data/spec/core/schema_spec.rb +7 -0
  61. data/spec/core_extensions_spec.rb +14 -0
  62. data/spec/extensions/class_table_inheritance_spec.rb +23 -3
  63. data/spec/extensions/core_refinements_spec.rb +14 -0
  64. data/spec/extensions/eval_inspect_spec.rb +8 -4
  65. data/spec/extensions/pg_array_ops_spec.rb +6 -0
  66. data/spec/extensions/pg_json_ops_spec.rb +99 -0
  67. data/spec/extensions/pg_json_spec.rb +104 -4
  68. data/spec/extensions/serialization_spec.rb +19 -0
  69. data/spec/extensions/single_table_inheritance_spec.rb +11 -3
  70. data/spec/extensions/timestamps_spec.rb +10 -0
  71. data/spec/extensions/to_dot_spec.rb +8 -4
  72. data/spec/integration/database_test.rb +1 -1
  73. data/spec/integration/dataset_test.rb +9 -0
  74. data/spec/integration/schema_test.rb +27 -0
  75. 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 _function_sql_append(sql, :datetime, args)
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}.new(#{args.join(', ')})"
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's initializer uses a splat for the function arguments.
131
- def inspect_args
132
- [:f, "*args"]
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, :table, :table_alias]
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, :table, :table_alias]
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 representating PostgreSQL JSON column array values.
69
- class JSONArray < DelegateClass(Array)
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, append a
73
- # literalized version of the string to the sql, and explicitly
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
- # Class representating PostgreSQL JSON column hash/object values.
82
- class JSONHash < DelegateClass(Hash)
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, append a
86
- # literalized version of the string to the sql, and explicitly
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 JSONArray, JSONHash
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 JSONHash, JSONArray
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
- # Given a value to typecast to the json column
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 JSONHash#op and
50
- # JSONArray#op to get a JSONOp, allowing you to perform json operations
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 JSONOp class is a simple container for a single object that
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 JSONOp < Sequel::SQL::Wrapper
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 # json_oarray_elements(json)
100
+ # json_op.array_elements # json_array_elements(json)
90
101
  def array_elements
91
- function(:json_array_elements)
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(:json_array_length))
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(:json_each)
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(:json_each_text)
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
- JSONOp.new(function(:json_extract_path, *a))
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(:json_extract_path_text, *a))
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(:json_object_keys)
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(:json_populate_record, arg, self)
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(:json_populate_recordset, arg, self)
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 JSONHash instance in an JSONOp, allowing you to easily use
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