rom-sql 1.3.1 → 1.3.2

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 (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)