sequel 4.10.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
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