rom-sql 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/lib/rom/sql/association.rb +1 -1
  4. data/lib/rom/sql/association/many_to_one.rb +1 -1
  5. data/lib/rom/sql/association/name.rb +1 -1
  6. data/lib/rom/sql/association/one_to_many.rb +1 -1
  7. data/lib/rom/sql/attribute.rb +23 -9
  8. data/lib/rom/sql/dsl.rb +13 -0
  9. data/lib/rom/sql/extensions/postgres/commands.rb +4 -4
  10. data/lib/rom/sql/extensions/postgres/types.rb +262 -110
  11. data/lib/rom/sql/function.rb +2 -2
  12. data/lib/rom/sql/projection_dsl.rb +1 -12
  13. data/lib/rom/sql/relation.rb +1 -1
  14. data/lib/rom/sql/relation/reading.rb +8 -4
  15. data/lib/rom/sql/restriction_dsl.rb +7 -1
  16. data/lib/rom/sql/types.rb +2 -0
  17. data/lib/rom/sql/version.rb +1 -1
  18. data/spec/extensions/postgres/attribute_spec.rb +78 -0
  19. data/spec/integration/association/many_to_many/custom_fks_spec.rb +8 -3
  20. data/spec/integration/association/many_to_many/from_view_spec.rb +9 -3
  21. data/spec/integration/association/many_to_many_spec.rb +8 -2
  22. data/spec/integration/association/many_to_one/custom_fks_spec.rb +8 -4
  23. data/spec/integration/association/many_to_one/from_view_spec.rb +10 -4
  24. data/spec/integration/association/many_to_one/self_ref_spec.rb +4 -2
  25. data/spec/integration/association/many_to_one_spec.rb +8 -4
  26. data/spec/integration/association/one_to_many/custom_fks_spec.rb +5 -2
  27. data/spec/integration/association/one_to_many/from_view_spec.rb +5 -2
  28. data/spec/integration/association/one_to_many/self_ref_spec.rb +4 -2
  29. data/spec/integration/association/one_to_many_spec.rb +1 -1
  30. data/spec/integration/commands/upsert_spec.rb +2 -2
  31. data/spec/integration/plugins/auto_wrap_spec.rb +1 -1
  32. data/spec/integration/sequel_api_spec.rb +3 -2
  33. data/spec/unit/function_spec.rb +1 -1
  34. data/spec/unit/order_dsl_spec.rb +4 -4
  35. data/spec/unit/projection_dsl_spec.rb +8 -0
  36. data/spec/unit/relation/dataset_spec.rb +3 -3
  37. data/spec/unit/relation/project_spec.rb +1 -1
  38. data/spec/unit/relation/qualified_columns_spec.rb +3 -2
  39. data/spec/unit/relation/where_spec.rb +20 -0
  40. data/spec/unit/restriction_dsl_spec.rb +2 -2
  41. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7bcf2dc42045eeec0c29f7f4eb833b9e1381e63
4
- data.tar.gz: 43a0ea01c51a400e1af5c1bb84aaf54d05851597
3
+ metadata.gz: dfb29b79f9b58b2fc9b30752951636130b525440
4
+ data.tar.gz: 2f6b70d93abf536152d991cb689bd77c699a6e83
5
5
  SHA512:
6
- metadata.gz: 86b7528f10243b97aabc0c0c17f2d1c55bf7e8f4827d6e85b882635c27a83bb100c24edab9c2d4c5d6ff79b5307dc83f3bd14c10414c8f452cbae995f4fb1f59
7
- data.tar.gz: a341dca4d4034d960e2f991f37177a4681a23d8aff252cc9e239ac96625f9277ca968b253acff75fa9aaf1ce2ce54672cc29357a2eb1a8a96e7824d41fa655e7
6
+ metadata.gz: ac6401a56919535a010c3ad049d63df969873c73169f629c5875edd343b404cf02ffc81f3458e9bbb6d3c20edc066fa5e26b2db30840a321f5ea10d5a86c5de4
7
+ data.tar.gz: d9e27f5a201c515fcd2d2d0a2f5cacae6cfdd2f44d1dfd5c0260c2121714c0491f8c49f87ed6120690d8d7f1603dcbc7308598c7e1000c4da4b775cf8d5494fa
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## v1.3.2 to-be-released
2
+
3
+ ## Added
4
+
5
+ * Support for filtering with a SQL function in the `WHERE` clause. Be sure you're using it wisely and don't call it on large datasets ;) (flash-gordon)
6
+ * `Void` type for calling functions without returning value (flash-gordon)
7
+ * Support for `PG::Array` transformations and queries (flash-gordon)
8
+
9
+ ## Fixed
10
+
11
+ * A bunch of warnings from Sequel 4.46
12
+
13
+ ## v1.3.1 2017-05-05
14
+
15
+ ## Changed
16
+
17
+ * [internal] Compatibility with `dry-core` v0.3.0 (flash-gordon)
18
+
19
+ [Compare v1.3.0...v1.3.1](https://github.com/rom-rb/rom-sql/compare/v1.3.0...v1.3.1)
20
+
1
21
  ## v1.3.0 2017-05-02
2
22
 
3
23
  ### Added
@@ -81,7 +81,7 @@ module ROM
81
81
  # @api protected
82
82
  def apply_view(schema, relation)
83
83
  view_rel = relation.public_send(view)
84
- schema.merge(view_rel.schema.qualified).uniq(&:to_sym).(view_rel)
84
+ schema.merge(view_rel.schema.qualified).uniq(&:to_sql_name).(view_rel)
85
85
  end
86
86
 
87
87
  # @api private
@@ -52,7 +52,7 @@ module ROM
52
52
 
53
53
  # @api private
54
54
  def source_table
55
- self_ref? ? :"#{source.dataset}___#{source_alias}" : source
55
+ self_ref? ? Sequel.as(source.dataset, source_alias) : source
56
56
  end
57
57
 
58
58
  # @api private
@@ -70,7 +70,7 @@ module ROM
70
70
  end
71
71
 
72
72
  def sql_literal(ds)
73
- Sequel.expr(aliased? ? :"#{dataset}___#{key}" : dataset).sql_literal(ds)
73
+ ds.literal(aliased? ? Sequel[dataset] : Sequel.as(dataset, key))
74
74
  end
75
75
  end
76
76
  end
@@ -39,7 +39,7 @@ module ROM
39
39
 
40
40
  # @api private
41
41
  def source_table
42
- self_ref? ? :"#{source.dataset}___#{source_alias}" : source
42
+ self_ref? ? Sequel.as(source.dataset, source_alias) : source
43
43
  end
44
44
 
45
45
  # @api private
@@ -37,8 +37,8 @@ module ROM
37
37
  #
38
38
  # @example
39
39
  # ROM::SQL::Attribute::TypeExtensions.register(ROM::SQL::Types::PG::JSONB) do
40
- # def contains(type, keys)
41
- # Sequel::Postgres::JSONBOp.new(type.meta[:name]).contains(keys)
40
+ # def contain(type, expr, keys)
41
+ # Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.contains(value))
42
42
  # end
43
43
  # end
44
44
  #
@@ -104,7 +104,7 @@ module ROM
104
104
  case sql_expr
105
105
  when Sequel::SQL::AliasedExpression, Sequel::SQL::Identifier
106
106
  type = meta(qualified: true)
107
- type.meta(qualified: true, sql_expr: Sequel[type.to_sym])
107
+ type.meta(sql_expr: type.to_sql_name)
108
108
  else
109
109
  raise QualifyError, "can't qualify #{name.inspect} (#{sql_expr.inspect})"
110
110
  end
@@ -302,11 +302,25 @@ module ROM
302
302
  #
303
303
  # @api private
304
304
  def sql_literal(ds)
305
- if sql_expr
306
- sql_expr.sql_literal(ds)
307
- else
308
- Sequel[to_sym].sql_literal(ds)
309
- end
305
+ ds.literal(sql_expr)
306
+ end
307
+
308
+ # Sequel column representation
309
+ #
310
+ # @return [Sequel::SQL::AliasedExpression,Sequel::SQL::Identifier]
311
+ #
312
+ # @api private
313
+ def to_sql_name
314
+ @_to_sql_name ||=
315
+ if qualified? && aliased?
316
+ Sequel.qualify(source.dataset, name).as(meta[:alias])
317
+ elsif qualified?
318
+ Sequel.qualify(source.dataset, name)
319
+ elsif aliased?
320
+ Sequel.as(name, meta[:alias])
321
+ else
322
+ Sequel[name]
323
+ end
310
324
  end
311
325
 
312
326
  private
@@ -315,7 +329,7 @@ module ROM
315
329
  #
316
330
  # @api private
317
331
  def sql_expr
318
- @sql_expr ||= (meta[:sql_expr] || Sequel[to_sym])
332
+ @sql_expr ||= (meta[:sql_expr] || to_sql_name)
319
333
  end
320
334
 
321
335
  # Delegate to sql expression if it responds to a given method
data/lib/rom/sql/dsl.rb CHANGED
@@ -25,6 +25,19 @@ module ROM
25
25
  def respond_to_missing?(name, include_private = false)
26
26
  super || schema.key?(name)
27
27
  end
28
+
29
+ private
30
+
31
+ # @api private
32
+ def type(identifier)
33
+ type_name = ::Dry::Core::Inflector.classify(identifier)
34
+ types.const_get(type_name) if types.const_defined?(type_name)
35
+ end
36
+
37
+ # @api private
38
+ def types
39
+ ::ROM::SQL::Types
40
+ end
28
41
  end
29
42
  end
30
43
  end
@@ -11,7 +11,7 @@ module ROM
11
11
  # @api private
12
12
  def insert(tuples)
13
13
  dataset = tuples.map do |tuple|
14
- relation.dataset.returning(*relation.columns).insert(tuple)
14
+ relation.dataset.returning.insert(tuple)
15
15
  end.flatten(1)
16
16
 
17
17
  wrap_dataset(dataset)
@@ -21,7 +21,7 @@ module ROM
21
21
  #
22
22
  # @api private
23
23
  def multi_insert(tuples)
24
- relation.dataset.returning(*relation.columns).multi_insert(tuples)
24
+ relation.dataset.returning.multi_insert(tuples)
25
25
  end
26
26
 
27
27
  # Executes upsert statement (INSERT with ON CONFLICT clause)
@@ -29,7 +29,7 @@ module ROM
29
29
  #
30
30
  # @api private
31
31
  def upsert(tuple, opts = EMPTY_HASH)
32
- relation.dataset.returning(*relation.columns).insert_conflict(opts).insert(tuple)
32
+ relation.dataset.returning.insert_conflict(opts).insert(tuple)
33
33
  end
34
34
  end
35
35
 
@@ -38,7 +38,7 @@ module ROM
38
38
  #
39
39
  # @api private
40
40
  def update(tuple)
41
- dataset = relation.dataset.returning(*relation.columns).update(tuple)
41
+ dataset = relation.dataset.returning.update(tuple)
42
42
  wrap_dataset(dataset)
43
43
  end
44
44
  end
@@ -20,6 +20,133 @@ module ROM
20
20
  Array.constructor(-> (v) { Sequel.pg_array(v, db_type) }).meta(type: db_type)
21
21
  end
22
22
 
23
+ # @!parse
24
+ # class ROM::SQL::Attribute
25
+ # # @!method contain(other)
26
+ # # Check whether the array includes another array
27
+ # # Translates to the @> operator
28
+ # #
29
+ # # @param [Array] other
30
+ # #
31
+ # # @return [SQL::Attribute<Types::Bool>]
32
+ # #
33
+ # # @api public
34
+ #
35
+ # # @!method get(idx)
36
+ # # Get element by index (PG uses 1-based indexing)
37
+ # #
38
+ # # @param [Integer] idx
39
+ # #
40
+ # # @return [SQL::Attribute]
41
+ # #
42
+ # # @api public
43
+ #
44
+ # # @!method any(value)
45
+ # # Check whether the array includes a value
46
+ # # Translates to the ANY operator
47
+ # #
48
+ # # @param [Object] value
49
+ # #
50
+ # # @return [SQL::Attribute<Types::Bool>]
51
+ # #
52
+ # # @api public
53
+ #
54
+ # # @!method contained_by(other)
55
+ # # Check whether the array is contained by another array
56
+ # # Translates to the <@ operator
57
+ # #
58
+ # # @param [Array] other
59
+ # #
60
+ # # @return [SQL::Attribute<Types::Bool>]
61
+ # #
62
+ # # @api public
63
+ #
64
+ # # @!method length
65
+ # # Return array size
66
+ # #
67
+ # # @return [SQL::Attribute<Types::Int>]
68
+ # #
69
+ # # @api public
70
+ #
71
+ # # @!method overlaps(other)
72
+ # # Check whether the arrays have common values
73
+ # # Translates to &&
74
+ # #
75
+ # # @param [Array] other
76
+ # #
77
+ # # @return [SQL::Attribute<Types::Bool>]
78
+ # #
79
+ # # @api public
80
+ #
81
+ # # @!method remove_value(value)
82
+ # # Remove elements by value
83
+ # #
84
+ # # @param [Object] value
85
+ # #
86
+ # # @return [SQL::Attribute<Types::PG::Array>]
87
+ # #
88
+ # # @api public
89
+ #
90
+ # # @!method join(delimiter, null_repr)
91
+ # # Convert the array to a string by joining
92
+ # # values with a delimiter (empty stirng by default)
93
+ # # and optional filler for NULL values
94
+ # # Translates to an `array_to_string` call
95
+ # #
96
+ # # @param [Object] delimiter
97
+ # # @param [Object] null
98
+ # #
99
+ # # @return [SQL::Attribute<Types::String>]
100
+ # #
101
+ # # @api public
102
+ #
103
+ # # @!method +(other)
104
+ # # Concatenate two arrays
105
+ # #
106
+ # # @param [Array] other
107
+ # #
108
+ # # @return [SQL::Attribute<Types::PG::Array>]
109
+ # #
110
+ # # @api public
111
+ # end
112
+ Attribute::TypeExtensions.register(Array.constructor -> { }) do
113
+ def contain(type, expr, other)
114
+ Attribute[Types::Bool].meta(sql_expr: expr.pg_array.contains(type[other]))
115
+ end
116
+
117
+ def get(type, expr, idx)
118
+ Attribute[type].meta(sql_expr: expr.pg_array[idx])
119
+ end
120
+
121
+ def any(type, expr, value)
122
+ Attribute[Types::Bool].meta(sql_expr: { value => expr.pg_array.any })
123
+ end
124
+
125
+ def contained_by(type, expr, other)
126
+ Attribute[Types::Bool].meta(sql_expr: expr.pg_array.contained_by(type[other]))
127
+ end
128
+
129
+ def length(type, expr)
130
+ Attribute[Types::Int].meta(sql_expr: expr.pg_array.length)
131
+ end
132
+
133
+ def overlaps(type, expr, other_array)
134
+ Attribute[Types::Bool].meta(sql_expr: expr.pg_array.overlaps(type[other_array]))
135
+ end
136
+
137
+ def remove_value(type, expr, value)
138
+ Attribute[type].meta(sql_expr: expr.pg_array.remove(value))
139
+ end
140
+
141
+ def join(type, expr, delimiter = '', null = nil)
142
+ Attribute[Types::String].meta(sql_expr: expr.pg_array.join(delimiter, null))
143
+ end
144
+
145
+ def +(type, expr, other)
146
+ Attribute[type].meta(sql_expr: expr.pg_array.concat(other))
147
+ end
148
+ end
149
+
23
150
  # JSON
24
151
 
25
152
  JSONArray = Types.Constructor(Sequel::Postgres::JSONArray, &Sequel.method(:pg_json))
@@ -40,150 +167,175 @@ module ROM
40
167
 
41
168
  JSONB = JSONBArray | JSONBHash | JSONBOp
42
169
 
170
+ # @!parse
171
+ # class ROM::SQL::Attribute
172
+ # # @!method contain(value)
173
+ # # Check whether the JSON value includes a json value
174
+ # # Translates to the @> operator
175
+ # #
176
+ # # @example
177
+ # # people.where { fields.contain(gender: 'Female') }
178
+ # # people.where(people[:fields].contain([name: 'age']))
179
+ # # people.select { fields.contain(gender: 'Female').as(:is_female) }
180
+ # #
181
+ # # @param [Hash,Array,Object] value
182
+ # #
183
+ # # @return [SQL::Attribute<Types::Bool>]
184
+ # #
185
+ # # @api public
186
+ #
187
+ # # @!method contained_by(value)
188
+ # # Check whether the JSON value is contained by other value
189
+ # # Translates to the <@ operator
190
+ # #
191
+ # # @example
192
+ # # people.where { custom_values.contained_by(age: 25, foo: 'bar') }
193
+ # #
194
+ # # @param [Hash,Array] value
195
+ # #
196
+ # # @return [SQL::Attribute<Types::Bool>]
197
+ # #
198
+ # # @api public
199
+ #
200
+ # # @!method get(*path)
201
+ # # Extract the JSON value using at the specified path
202
+ # # Translates to -> or #> depending on the number of arguments
203
+ # #
204
+ # # @example
205
+ # # people.select { data.get('age').as(:person_age) }
206
+ # # people.select { fields.get(0).as(:first_field) }
207
+ # # people.select { fields.get('0', 'value').as(:first_field_value) }
208
+ # #
209
+ # # @param [Array<Integer>,Array<String>] path Path to extract
210
+ # #
211
+ # # @return [SQL::Attribute<Types::PG::JSONB>]
212
+ # #
213
+ # # @api public
214
+ #
215
+ # # @!method get_text(*path)
216
+ # # Extract the JSON value as text using at the specified path
217
+ # # Translates to ->> or #>> depending on the number of arguments
218
+ # #
219
+ # # @example
220
+ # # people.select { data.get('age').as(:person_age) }
221
+ # # people.select { fields.get(0).as(:first_field) }
222
+ # # people.select { fields.get('0', 'value').as(:first_field_value) }
223
+ # #
224
+ # # @param [Array<Integer>,Array<String>] path Path to extract
225
+ # #
226
+ # # @return [SQL::Attribute<Types::String>]
227
+ # #
228
+ # # @api public
229
+ #
230
+ # # @!method has_key(key)
231
+ # # Does the JSON value has the specified top-level key
232
+ # # Translates to ?
233
+ # #
234
+ # # @example
235
+ # # people.where { data.has_key('age') }
236
+ # #
237
+ # # @param [String] key
238
+ # #
239
+ # # @return [SQL::Attribute<Types::Bool>]
240
+ # #
241
+ # # @api public
242
+ #
243
+ # # @!method has_any_key(*keys)
244
+ # # Does the JSON value has any of the specified top-level keys
245
+ # # Translates to ?|
246
+ # #
247
+ # # @example
248
+ # # people.where { data.has_any_key('age', 'height') }
249
+ # #
250
+ # # @param [Array<String>] keys
251
+ # #
252
+ # # @return [SQL::Attribute<Types::Bool>]
253
+ # #
254
+ # # @api public
255
+ #
256
+ # # @!method has_all_keys(*keys)
257
+ # # Does the JSON value has all the specified top-level keys
258
+ # # Translates to ?&
259
+ # #
260
+ # # @example
261
+ # # people.where { data.has_all_keys('age', 'height') }
262
+ # #
263
+ # # @param [Array<String>] keys
264
+ # #
265
+ # # @return [SQL::Attribute<Types::Bool>]
266
+ # #
267
+ # # @api public
268
+ #
269
+ # # @!method merge(value)
270
+ # # Concatenate two JSON values
271
+ # # Translates to ||
272
+ # #
273
+ # # @example
274
+ # # people.select { data.merge(fetched_at: Time.now).as(:data) }
275
+ # # people.select { (fields + [name: 'height', value: 165]).as(:fields) }
276
+ # #
277
+ # # @param [Hash,Array] value
278
+ # #
279
+ # # @return [SQL::Attribute<Types::PG::JSONB>]
280
+ # #
281
+ # # @api public
282
+ #
283
+ # # @!method +(value)
284
+ # # An alias for ROM::SQL::Attribute<JSONB>#merge
285
+ # #
286
+ # # @api public
287
+ #
288
+ # # @!method delete(*path)
289
+ # # Deletes the specified value by key, index, or path
290
+ # # Translates to - or #- depending on the number of arguments
291
+ # #
292
+ # # @example
293
+ # # people.select { data.delete('age').as(:data_without_age) }
294
+ # # people.select { fields.delete(0).as(:fields_without_first) }
295
+ # # people.select { fields.delete(-1).as(:fields_without_last) }
296
+ # # people.select { data.delete('deeply', 'nested', 'value').as(:data) }
297
+ # # people.select { fields.delete('0', 'name').as(:data) }
298
+ # #
299
+ # # @param [Array<String>] path
300
+ # #
301
+ # # @return [SQL::Attribute<Types::PG::JSONB>]
302
+ # #
303
+ # # @api public
304
+ # end
43
305
  Attribute::TypeExtensions.register(JSONB) do
44
- # Checks whether the JSON value includes a json value
45
- # Translates to the @> operator
46
- #
47
- # @example
48
- # people.where { fields.contain(gender: 'Female') }
49
- # people.where(people[:fields].contain([name: 'age']))
50
- # people.select { fields.contain(gender: 'Female').as(:is_female) }
51
- #
52
- # @param [Hash,Array,Object] value
53
- #
54
- # @return [SQL::Attribute<Types::Bool>]
55
- #
56
- # @api public
57
306
  def contain(type, expr, value)
58
307
  Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.contains(value))
59
308
  end
60
309
 
61
- # Checks whether the JSON value is contained by other value
62
- # Translates to the <@ operator
63
- #
64
- # @example
65
- # people.where { custom_values.contained_by(age: 25, foo: 'bar') }
66
- #
67
- # @param [Hash,Array] value
68
- #
69
- # @return [SQL::Attribute<Types::Bool>]
70
- #
71
- # @api public
72
310
  def contained_by(type, expr, value)
73
311
  Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.contained_by(value))
74
312
  end
75
313
 
76
- # Extracts the JSON value using at the specified path
77
- # Translates to -> or #> depending on the number of arguments
78
- #
79
- # @example
80
- # people.select { data.get('age').as(:person_age) }
81
- # people.select { fields.get(0).as(:first_field) }
82
- # people.select { fields.get('0', 'value').as(:first_field_value) }
83
- #
84
- # @param [Array<Integer>,Array<String>] path Path to extract
85
- #
86
- # @return [SQL::Attribute<Types::PG::JSONB>]
87
- #
88
- # @api public
89
314
  def get(type, expr, *path)
90
315
  Attribute[JSONB].meta(sql_expr: expr.pg_jsonb[path_args(path)])
91
316
  end
92
317
 
93
- # Extracts the JSON value as text using at the specified path
94
- # Translates to ->> or #>> depending on the number of arguments
95
- #
96
- # @example
97
- # people.select { data.get('age').as(:person_age) }
98
- # people.select { fields.get(0).as(:first_field) }
99
- # people.select { fields.get('0', 'value').as(:first_field_value) }
100
- #
101
- # @param [Array<Integer>,Array<String>] path Path to extract
102
- #
103
- # @return [SQL::Attribute<Types::String>]
104
- #
105
- # @api public
106
318
  def get_text(type, expr, *path)
107
319
  Attribute[Types::String].meta(sql_expr: expr.pg_jsonb.get_text(path_args(path)))
108
320
  end
109
321
 
110
- # Does the JSON value has the specified top-level key
111
- # Translates to ?
112
- #
113
- # @example
114
- # people.where { data.has_key('age') }
115
- #
116
- # @param [String] key
117
- #
118
- # @return [SQL::Attribute<Types::Bool>]
119
- #
120
- # @api public
121
322
  def has_key(type, expr, key)
122
323
  Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.has_key?(key))
123
324
  end
124
325
 
125
- # Does the JSON value has any of the specified top-level keys
126
- # Translates to ?|
127
- #
128
- # @example
129
- # people.where { data.has_any_key('age', 'height') }
130
- #
131
- # @param [Array<String>] keys
132
- #
133
- # @return [SQL::Attribute<Types::Bool>]
134
- #
135
- # @api public
136
326
  def has_any_key(type, expr, *keys)
137
327
  Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.contain_any(keys))
138
328
  end
139
329
 
140
- # Does the JSON value has all the specified top-level keys
141
- # Translates to ?&
142
- #
143
- # @example
144
- # people.where { data.has_all_keys('age', 'height') }
145
- #
146
- # @param [Array<String>] keys
147
- #
148
- # @return [SQL::Attribute<Types::Bool>]
149
- #
150
- # @api public
151
330
  def has_all_keys(type, expr, *keys)
152
331
  Attribute[Types::Bool].meta(sql_expr: expr.pg_jsonb.contain_all(keys))
153
332
  end
154
333
 
155
- # Concatenates two JSON values
156
- # Translates to ||
157
- #
158
- # @example
159
- # people.select { data.merge(fetched_at: Time.now).as(:data) }
160
- # people.select { (fields + [name: 'height', value: 165]).as(:fields) }
161
- #
162
- # @param [Hash,Array] value
163
- #
164
- # @return [SQL::Attribute<Types::PG::JSONB>]
165
- #
166
- # @api public
167
334
  def merge(type, expr, value)
168
335
  Attribute[JSONB].meta(sql_expr: expr.pg_jsonb.concat(value))
169
336
  end
170
337
  alias_method :+, :merge
171
338
 
172
- # Deletes the specified value by key, index, or path
173
- # Translates to - or #- depending on the number of arguments
174
- #
175
- # @example
176
- # people.select { data.delete('age').as(:data_without_age) }
177
- # people.select { fields.delete(0).as(:fields_without_first) }
178
- # people.select { fields.delete(-1).as(:fields_without_last) }
179
- # people.select { data.delete('deeply', 'nested', 'value').as(:data) }
180
- # people.select { fields.delete('0', 'name').as(:data) }
181
- #
182
- # @param [Array<String>] path
183
- #
184
- # @return [SQL::Attribute<Types::PG::JSONB>]
185
- #
186
- # @api public
187
339
  def delete(type, expr, *path)
188
340
  sql_expr = path.size == 1 ? expr.pg_jsonb - path : expr.pg_jsonb.delete_path(path)
189
341
  Attribute[JSONB].meta(sql_expr: sql_expr)