sequel 5.83.0 → 5.106.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 (259) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/lib/sequel/adapters/ado/access.rb +2 -2
  4. data/lib/sequel/adapters/ado.rb +2 -2
  5. data/lib/sequel/adapters/amalgalite.rb +16 -1
  6. data/lib/sequel/adapters/ibmdb.rb +2 -1
  7. data/lib/sequel/adapters/jdbc/db2.rb +2 -2
  8. data/lib/sequel/adapters/jdbc/derby.rb +4 -2
  9. data/lib/sequel/adapters/jdbc/h2.rb +4 -4
  10. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
  11. data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
  12. data/lib/sequel/adapters/jdbc/mysql.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/oracle.rb +6 -6
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
  15. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
  16. data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +3 -3
  18. data/lib/sequel/adapters/jdbc.rb +29 -15
  19. data/lib/sequel/adapters/mysql.rb +1 -1
  20. data/lib/sequel/adapters/mysql2.rb +10 -3
  21. data/lib/sequel/adapters/odbc.rb +1 -1
  22. data/lib/sequel/adapters/oracle.rb +16 -0
  23. data/lib/sequel/adapters/postgres.rb +2 -1
  24. data/lib/sequel/adapters/shared/access.rb +1 -0
  25. data/lib/sequel/adapters/shared/db2.rb +8 -3
  26. data/lib/sequel/adapters/shared/mssql.rb +9 -8
  27. data/lib/sequel/adapters/shared/mysql.rb +16 -11
  28. data/lib/sequel/adapters/shared/oracle.rb +1 -0
  29. data/lib/sequel/adapters/shared/postgres.rb +438 -47
  30. data/lib/sequel/adapters/shared/sqlite.rb +9 -7
  31. data/lib/sequel/adapters/sqlite.rb +20 -2
  32. data/lib/sequel/adapters/tinytds.rb +2 -2
  33. data/lib/sequel/adapters/trilogy.rb +2 -3
  34. data/lib/sequel/ast_transformer.rb +2 -0
  35. data/lib/sequel/connection_pool/sharded_timed_queue.rb +42 -11
  36. data/lib/sequel/connection_pool/timed_queue.rb +33 -10
  37. data/lib/sequel/connection_pool.rb +8 -2
  38. data/lib/sequel/core.rb +31 -2
  39. data/lib/sequel/database/connecting.rb +1 -1
  40. data/lib/sequel/database/dataset_defaults.rb +3 -3
  41. data/lib/sequel/database/misc.rb +29 -11
  42. data/lib/sequel/database/query.rb +12 -12
  43. data/lib/sequel/database/schema_generator.rb +117 -16
  44. data/lib/sequel/database/schema_methods.rb +85 -30
  45. data/lib/sequel/dataset/actions.rb +83 -11
  46. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
  47. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -0
  48. data/lib/sequel/dataset/prepared_statements.rb +76 -28
  49. data/lib/sequel/dataset/query.rb +26 -11
  50. data/lib/sequel/dataset/sql.rb +40 -17
  51. data/lib/sequel/extensions/async_thread_pool.rb +1 -1
  52. data/lib/sequel/extensions/caller_logging.rb +1 -3
  53. data/lib/sequel/extensions/connection_checkout_event_callback.rb +151 -0
  54. data/lib/sequel/extensions/connection_expiration.rb +44 -5
  55. data/lib/sequel/extensions/connection_validator.rb +15 -10
  56. data/lib/sequel/extensions/constraint_validations.rb +3 -3
  57. data/lib/sequel/extensions/dataset_run.rb +41 -0
  58. data/lib/sequel/extensions/date_arithmetic.rb +6 -6
  59. data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -0
  60. data/lib/sequel/extensions/eval_inspect.rb +3 -1
  61. data/lib/sequel/extensions/inflector.rb +2 -2
  62. data/lib/sequel/extensions/lit_require_frozen.rb +131 -0
  63. data/lib/sequel/extensions/looser_typecasting.rb +5 -12
  64. data/lib/sequel/extensions/migration.rb +38 -21
  65. data/lib/sequel/extensions/null_dataset.rb +2 -2
  66. data/lib/sequel/extensions/pg_array_ops.rb +41 -0
  67. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -3
  68. data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
  69. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +94 -10
  70. data/lib/sequel/extensions/pg_enum.rb +4 -4
  71. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  72. data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
  73. data/lib/sequel/extensions/pg_json_ops.rb +674 -18
  74. data/lib/sequel/extensions/pg_multirange.rb +1 -1
  75. data/lib/sequel/extensions/pg_range.rb +14 -2
  76. data/lib/sequel/extensions/pg_row.rb +5 -3
  77. data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
  78. data/lib/sequel/extensions/provenance.rb +1 -3
  79. data/lib/sequel/extensions/query_blocker.rb +172 -0
  80. data/lib/sequel/extensions/schema_caching.rb +24 -9
  81. data/lib/sequel/extensions/schema_dumper.rb +17 -5
  82. data/lib/sequel/extensions/set_literalizer.rb +20 -39
  83. data/lib/sequel/extensions/split_array_nil.rb +12 -2
  84. data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
  85. data/lib/sequel/extensions/string_agg.rb +2 -2
  86. data/lib/sequel/extensions/to_dot.rb +5 -0
  87. data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
  88. data/lib/sequel/model/associations.rb +215 -15
  89. data/lib/sequel/model/base.rb +198 -35
  90. data/lib/sequel/model/plugins.rb +12 -1
  91. data/lib/sequel/model.rb +1 -0
  92. data/lib/sequel/plugins/accessed_columns.rb +4 -0
  93. data/lib/sequel/plugins/active_model.rb +5 -6
  94. data/lib/sequel/plugins/association_pks.rb +2 -0
  95. data/lib/sequel/plugins/auto_validations.rb +2 -0
  96. data/lib/sequel/plugins/class_table_inheritance_constraint_validations.rb +82 -0
  97. data/lib/sequel/plugins/columns_updated.rb +4 -0
  98. data/lib/sequel/plugins/composition.rb +3 -1
  99. data/lib/sequel/plugins/constraint_validations.rb +15 -10
  100. data/lib/sequel/plugins/dataset_associations.rb +20 -1
  101. data/lib/sequel/plugins/deprecated_associations.rb +151 -0
  102. data/lib/sequel/plugins/detect_unnecessary_association_options.rb +164 -0
  103. data/lib/sequel/plugins/dirty.rb +9 -2
  104. data/lib/sequel/plugins/enum.rb +1 -1
  105. data/lib/sequel/plugins/forbid_lazy_load.rb +15 -1
  106. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  107. data/lib/sequel/plugins/insert_returning_select.rb +10 -1
  108. data/lib/sequel/plugins/inspect_pk.rb +44 -0
  109. data/lib/sequel/plugins/instance_filters.rb +8 -1
  110. data/lib/sequel/plugins/instance_hooks.rb +4 -0
  111. data/lib/sequel/plugins/inverted_subsets.rb +1 -0
  112. data/lib/sequel/plugins/json_serializer.rb +6 -10
  113. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  114. data/lib/sequel/plugins/many_through_many.rb +21 -0
  115. data/lib/sequel/plugins/modification_detection.rb +2 -0
  116. data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
  117. data/lib/sequel/plugins/nested_attributes.rb +10 -5
  118. data/lib/sequel/plugins/paged_operations.rb +5 -2
  119. data/lib/sequel/plugins/pg_array_associations.rb +2 -2
  120. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
  121. data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
  122. data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
  123. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1 -1
  124. data/lib/sequel/plugins/rcte_tree.rb +6 -6
  125. data/lib/sequel/plugins/serialization.rb +23 -6
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +4 -0
  127. data/lib/sequel/plugins/single_statement_dataset_destroy.rb +49 -0
  128. data/lib/sequel/plugins/split_values.rb +12 -0
  129. data/lib/sequel/plugins/sql_comments.rb +7 -2
  130. data/lib/sequel/plugins/static_cache.rb +13 -0
  131. data/lib/sequel/plugins/static_cache_cache.rb +50 -13
  132. data/lib/sequel/plugins/subset_conditions.rb +85 -5
  133. data/lib/sequel/plugins/subset_static_cache.rb +278 -0
  134. data/lib/sequel/plugins/table_select.rb +7 -0
  135. data/lib/sequel/plugins/tactical_eager_loading.rb +4 -0
  136. data/lib/sequel/plugins/unused_associations.rb +11 -3
  137. data/lib/sequel/plugins/update_primary_key.rb +4 -0
  138. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  139. data/lib/sequel/plugins/validation_contexts.rb +4 -0
  140. data/lib/sequel/sql.rb +27 -12
  141. data/lib/sequel/version.rb +1 -1
  142. metadata +17 -240
  143. data/CHANGELOG +0 -1393
  144. data/README.rdoc +0 -936
  145. data/doc/advanced_associations.rdoc +0 -884
  146. data/doc/association_basics.rdoc +0 -1859
  147. data/doc/bin_sequel.rdoc +0 -146
  148. data/doc/cheat_sheet.rdoc +0 -255
  149. data/doc/code_order.rdoc +0 -104
  150. data/doc/core_extensions.rdoc +0 -405
  151. data/doc/dataset_basics.rdoc +0 -96
  152. data/doc/dataset_filtering.rdoc +0 -222
  153. data/doc/extensions.rdoc +0 -77
  154. data/doc/fork_safety.rdoc +0 -84
  155. data/doc/mass_assignment.rdoc +0 -98
  156. data/doc/migration.rdoc +0 -660
  157. data/doc/model_dataset_method_design.rdoc +0 -129
  158. data/doc/model_hooks.rdoc +0 -254
  159. data/doc/model_plugins.rdoc +0 -270
  160. data/doc/mssql_stored_procedures.rdoc +0 -43
  161. data/doc/object_model.rdoc +0 -563
  162. data/doc/opening_databases.rdoc +0 -439
  163. data/doc/postgresql.rdoc +0 -611
  164. data/doc/prepared_statements.rdoc +0 -144
  165. data/doc/querying.rdoc +0 -1070
  166. data/doc/reflection.rdoc +0 -120
  167. data/doc/release_notes/5.0.0.txt +0 -159
  168. data/doc/release_notes/5.1.0.txt +0 -31
  169. data/doc/release_notes/5.10.0.txt +0 -84
  170. data/doc/release_notes/5.11.0.txt +0 -83
  171. data/doc/release_notes/5.12.0.txt +0 -141
  172. data/doc/release_notes/5.13.0.txt +0 -27
  173. data/doc/release_notes/5.14.0.txt +0 -63
  174. data/doc/release_notes/5.15.0.txt +0 -39
  175. data/doc/release_notes/5.16.0.txt +0 -110
  176. data/doc/release_notes/5.17.0.txt +0 -31
  177. data/doc/release_notes/5.18.0.txt +0 -69
  178. data/doc/release_notes/5.19.0.txt +0 -28
  179. data/doc/release_notes/5.2.0.txt +0 -33
  180. data/doc/release_notes/5.20.0.txt +0 -89
  181. data/doc/release_notes/5.21.0.txt +0 -87
  182. data/doc/release_notes/5.22.0.txt +0 -48
  183. data/doc/release_notes/5.23.0.txt +0 -56
  184. data/doc/release_notes/5.24.0.txt +0 -56
  185. data/doc/release_notes/5.25.0.txt +0 -32
  186. data/doc/release_notes/5.26.0.txt +0 -35
  187. data/doc/release_notes/5.27.0.txt +0 -21
  188. data/doc/release_notes/5.28.0.txt +0 -16
  189. data/doc/release_notes/5.29.0.txt +0 -22
  190. data/doc/release_notes/5.3.0.txt +0 -121
  191. data/doc/release_notes/5.30.0.txt +0 -20
  192. data/doc/release_notes/5.31.0.txt +0 -148
  193. data/doc/release_notes/5.32.0.txt +0 -46
  194. data/doc/release_notes/5.33.0.txt +0 -24
  195. data/doc/release_notes/5.34.0.txt +0 -40
  196. data/doc/release_notes/5.35.0.txt +0 -56
  197. data/doc/release_notes/5.36.0.txt +0 -60
  198. data/doc/release_notes/5.37.0.txt +0 -30
  199. data/doc/release_notes/5.38.0.txt +0 -28
  200. data/doc/release_notes/5.39.0.txt +0 -19
  201. data/doc/release_notes/5.4.0.txt +0 -80
  202. data/doc/release_notes/5.40.0.txt +0 -40
  203. data/doc/release_notes/5.41.0.txt +0 -25
  204. data/doc/release_notes/5.42.0.txt +0 -136
  205. data/doc/release_notes/5.43.0.txt +0 -98
  206. data/doc/release_notes/5.44.0.txt +0 -32
  207. data/doc/release_notes/5.45.0.txt +0 -34
  208. data/doc/release_notes/5.46.0.txt +0 -87
  209. data/doc/release_notes/5.47.0.txt +0 -59
  210. data/doc/release_notes/5.48.0.txt +0 -14
  211. data/doc/release_notes/5.49.0.txt +0 -59
  212. data/doc/release_notes/5.5.0.txt +0 -61
  213. data/doc/release_notes/5.50.0.txt +0 -78
  214. data/doc/release_notes/5.51.0.txt +0 -47
  215. data/doc/release_notes/5.52.0.txt +0 -87
  216. data/doc/release_notes/5.53.0.txt +0 -23
  217. data/doc/release_notes/5.54.0.txt +0 -27
  218. data/doc/release_notes/5.55.0.txt +0 -21
  219. data/doc/release_notes/5.56.0.txt +0 -51
  220. data/doc/release_notes/5.57.0.txt +0 -23
  221. data/doc/release_notes/5.58.0.txt +0 -31
  222. data/doc/release_notes/5.59.0.txt +0 -73
  223. data/doc/release_notes/5.6.0.txt +0 -31
  224. data/doc/release_notes/5.60.0.txt +0 -22
  225. data/doc/release_notes/5.61.0.txt +0 -43
  226. data/doc/release_notes/5.62.0.txt +0 -132
  227. data/doc/release_notes/5.63.0.txt +0 -33
  228. data/doc/release_notes/5.64.0.txt +0 -50
  229. data/doc/release_notes/5.65.0.txt +0 -21
  230. data/doc/release_notes/5.66.0.txt +0 -24
  231. data/doc/release_notes/5.67.0.txt +0 -32
  232. data/doc/release_notes/5.68.0.txt +0 -61
  233. data/doc/release_notes/5.69.0.txt +0 -26
  234. data/doc/release_notes/5.7.0.txt +0 -108
  235. data/doc/release_notes/5.70.0.txt +0 -35
  236. data/doc/release_notes/5.71.0.txt +0 -21
  237. data/doc/release_notes/5.72.0.txt +0 -33
  238. data/doc/release_notes/5.73.0.txt +0 -66
  239. data/doc/release_notes/5.74.0.txt +0 -45
  240. data/doc/release_notes/5.75.0.txt +0 -35
  241. data/doc/release_notes/5.76.0.txt +0 -86
  242. data/doc/release_notes/5.77.0.txt +0 -63
  243. data/doc/release_notes/5.78.0.txt +0 -67
  244. data/doc/release_notes/5.79.0.txt +0 -28
  245. data/doc/release_notes/5.8.0.txt +0 -170
  246. data/doc/release_notes/5.80.0.txt +0 -40
  247. data/doc/release_notes/5.81.0.txt +0 -31
  248. data/doc/release_notes/5.82.0.txt +0 -61
  249. data/doc/release_notes/5.83.0.txt +0 -56
  250. data/doc/release_notes/5.9.0.txt +0 -99
  251. data/doc/schema_modification.rdoc +0 -679
  252. data/doc/security.rdoc +0 -443
  253. data/doc/sharding.rdoc +0 -286
  254. data/doc/sql.rdoc +0 -648
  255. data/doc/testing.rdoc +0 -204
  256. data/doc/thread_safety.rdoc +0 -15
  257. data/doc/transactions.rdoc +0 -250
  258. data/doc/validations.rdoc +0 -558
  259. data/doc/virtual_rows.rdoc +0 -265
@@ -88,6 +88,12 @@
88
88
  # j.path_query_array('$.foo') # jsonb_path_query_array(jsonb_column, '$.foo')
89
89
  # j.path_query_first('$.foo') # jsonb_path_query_first(jsonb_column, '$.foo')
90
90
  #
91
+ # For the PostgreSQL 12+ SQL/JSON path functions, one argument is required (+path+) and
92
+ # two more arguments are optional (+vars+ and +silent+). +path+ specifies the JSON path.
93
+ # +vars+ specifies a hash or a string in JSON format of named variables to be
94
+ # substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
95
+ # errors are not suppressed.
96
+ #
91
97
  # On PostgreSQL 13+ timezone-aware SQL/JSON path functions and operators are supported:
92
98
  #
93
99
  # j.path_exists_tz!('$.foo') # jsonb_path_exists_tz(jsonb_column, '$.foo')
@@ -96,12 +102,6 @@
96
102
  # j.path_query_array_tz('$.foo') # jsonb_path_query_array_tz(jsonb_column, '$.foo')
97
103
  # j.path_query_first_tz('$.foo') # jsonb_path_query_first_tz(jsonb_column, '$.foo')
98
104
  #
99
- # For the PostgreSQL 12+ SQL/JSON path functions, one argument is required (+path+) and
100
- # two more arguments are optional (+vars+ and +silent+). +path+ specifies the JSON path.
101
- # +vars+ specifies a hash or a string in JSON format of named variables to be
102
- # substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
103
- # errors are not suppressed.
104
- #
105
105
  # On PostgreSQL 14+, The JSONB <tt>[]</tt> method will use subscripts instead of being
106
106
  # the same as +get+, if the value being wrapped is an identifer:
107
107
  #
@@ -129,8 +129,47 @@
129
129
  # j.is_json(type: :object) # j IS JSON OBJECT
130
130
  # j.is_json(type: :object, unique: true) # j IS JSON OBJECT WITH UNIQUE
131
131
  # j.is_not_json # j IS NOT JSON
132
- # j.is_not_json(type: :array) # j IS NOT JSON ARRAY
133
- # j.is_not_json(unique: true) # j IS NOT JSON WITH UNIQUE
132
+ # j.is_not_json(type: :array) # j IS NOT JSON ARRAY
133
+ # j.is_not_json(unique: true) # j IS NOT JSON WITH UNIQUE
134
+ #
135
+ # On PostgreSQL 17+, the additional JSON functions are supported (see method documentation
136
+ # for additional options):
137
+ #
138
+ # j.exists('$.foo') # json_exists(jsonb_column, '$.foo')
139
+ # j.value('$.foo') # json_value(jsonb_column, '$.foo')
140
+ # j.query('$.foo') # json_query(jsonb_column, '$.foo')
141
+ #
142
+ # j.exists('$.foo', passing: {a: 1}) # json_exists(jsonb_column, '$.foo' PASSING 1 AS a)
143
+ # j.value('$.foo', returning: Time) # json_value(jsonb_column, '$.foo' RETURNING timestamp)
144
+ # j.query('$.foo', wrapper: true) # json_query(jsonb_column, '$.foo' WITH WRAPPER)
145
+ #
146
+ # j.table('$.foo') do
147
+ # String :bar
148
+ # Integer :baz
149
+ # end
150
+ # # json_table("jsonb_column", '$.foo' COLUMNS("bar" text, "baz" integer))
151
+ #
152
+ # j.table('$.foo', passing: {a: 1}) do
153
+ # ordinality :id
154
+ # String :bar, format: :json, on_error: :empty_object
155
+ # nested '$.baz' do
156
+ # Integer :q, path: '$.quux', on_empty: :error
157
+ # end
158
+ # exists :x, Date, on_error: false
159
+ # end
160
+ # # json_table(jsonb_column, '$.foo' PASSING 1 AS a COLUMNS(
161
+ # # "id" FOR ORDINALITY,
162
+ # # "bar" text FORMAT JSON EMPTY OBJECT ON ERROR,
163
+ # # NESTED '$.baz' COLUMNS(
164
+ # # "q" integer PATH '$.quux' ERROR ON EMPTY
165
+ # # ),
166
+ # # "d" date EXISTS FALSE ON ERROR
167
+ # # ))
168
+ #
169
+ # On PostgreSQL 18+, strip_nulls can take an argument for whether to strip in arrays
170
+ #
171
+ # j.strip_nulls(in_arrays: true) # json_strip_nulls(json_column, true)
172
+ # j.strip_nulls(in_arrays: false) # json_strip_nulls(json_column, false)
134
173
  #
135
174
  # If you are also using the pg_json extension, you should load it before
136
175
  # loading this extension. Doing so will allow you to use the #op method on
@@ -224,7 +263,25 @@ module Sequel
224
263
  function(:each_text)
225
264
  end
226
265
 
227
- # Returns a json value for the object at the given path.
266
+ # Return whether the given JSON path yields any items in the receiver.
267
+ # Options:
268
+ #
269
+ # :on_error :: How to handle errors when evaluating the JSON path expression.
270
+ # true :: Return true
271
+ # false :: Return false (default behavior)
272
+ # :null :: Return nil
273
+ # :error :: raise a DatabaseError
274
+ # :passing :: Variables to pass to the JSON path expression. Keys are variable
275
+ # names, values are the values of the variable.
276
+ #
277
+ # json_op.exists("$.a") # json_exists(json, '$.a')
278
+ # json_op.exists("$.a", passing: {a: 1}) # json_exists(json, '$.a' PASSING 1 AS a)
279
+ # json_op.exists("$.a", on_error: :error) # json_exists(json, '$.a' ERROR ON ERROR)
280
+ def exists(path, opts=OPTS)
281
+ Sequel::SQL::BooleanExpression.new(:NOOP, JSONExistsOp.new(self, path, opts))
282
+ end
283
+
284
+ # Returns a JSON value for the object at the given path.
228
285
  #
229
286
  # json_op.extract('a') # json_extract_path(json, 'a')
230
287
  # json_op.extract('a', 'b') # json_extract_path(json, 'a', 'b')
@@ -299,11 +356,117 @@ module Sequel
299
356
  SQL::Function.new(function_name(:populate_recordset), arg, self)
300
357
  end
301
358
 
302
- # Returns a json value stripped of all internal null values.
359
+ # Return the result of applying the JSON path expression to the receiver, by default
360
+ # returning results as jsonb. Options:
303
361
  #
304
- # json_op.strip_nulls # json_strip_nulls(json)
305
- def strip_nulls
306
- self.class.new(function(:strip_nulls))
362
+ # :on_empty :: How to handle case where path expression yields an empty set.
363
+ # Uses same values as :on_error option.
364
+ # :on_error :: How to handle errors when evaluating the JSON path expression:
365
+ # :null :: Return nil (default)
366
+ # :empty_array :: Return an empty array
367
+ # :empty_object :: Return an empty object
368
+ # :error :: raise a DatabaseError
369
+ # any other value :: used as default value
370
+ # :passing :: Variables to pass to the JSON path expression. Keys are variable
371
+ # names, values are the values of the variable.
372
+ # :returning :: The data type to return (jsonb by default)
373
+ # :wrapper :: How to wrap returned values:
374
+ # true, :unconditional :: Always wrap returning values in an array
375
+ # :conditional :: Only wrap multiple return values in an array
376
+ # :omit_quotes :: Do not wrap scalar strings in quotes
377
+ #
378
+ # json_op.query("$.a") # json_query(json, '$.a')
379
+ # json_op.query("$.a", passing: {a: 1}) # json_query(json, '$.a' PASSING 1 AS a)
380
+ # json_op.query("$.a", on_error: :empty_array) # json_query(json, '$.a' EMPTY ARRAY ON ERROR)
381
+ # json_op.query("$.a", returning: Time) # json_query(json, '$.a' RETURNING timestamp)
382
+ # json_op.query("$.a", on_empty: 2) # json_query(json, '$.a' DEFAULT 2 ON EMPTY)
383
+ # json_op.query("$.a", wrapper: true) # json_query(json, '$.a' WITH WRAPPER)
384
+ def query(path, opts=OPTS)
385
+ self.class.new(JSONQueryOp.new(self, path, opts))
386
+ end
387
+
388
+ # Returns a json value stripped of all internal null values. Options:
389
+ #
390
+ # :in_arrays :: Whether to strip null values in JSON arrays
391
+ #
392
+ # json_op.strip_nulls # json_strip_nulls(json)
393
+ # json_op.strip_nulls(in_arrays: true) # json_strip_nulls(json, true)
394
+ # json_op.strip_nulls(in_arrays: false) # json_strip_nulls(json, false)
395
+ def strip_nulls(opts=OPTS)
396
+ in_arrays = opts[:in_arrays]
397
+ f = if in_arrays.nil?
398
+ function(:strip_nulls)
399
+ else
400
+ function(:strip_nulls, in_arrays)
401
+ end
402
+
403
+ self.class.new(f)
404
+ end
405
+
406
+ # Returns json_table SQL function expression, querying JSON data and returning
407
+ # the results as a relational view, which can be accessed similarly to a regular
408
+ # SQL table. This accepts a block that is handled in a similar manner to
409
+ # Database#create_table, though it operates differently.
410
+ #
411
+ # Table level options:
412
+ #
413
+ # :on_error :: How to handle errors when evaluating the JSON path expression.
414
+ # :empty_array :: Return an empty array/result set
415
+ # :error :: raise a DatabaseError
416
+ # :passing :: Variables to pass to the JSON path expression. Keys are variable
417
+ # names, values are the values of the variable.
418
+ #
419
+ # Inside the block, the following methods can be used:
420
+ #
421
+ # ordinality(name) :: Include a FOR ORDINALITY column, which operates similar to an
422
+ # autoincrementing primary key.
423
+ # column(name, type, opts={}) :: Return a normal column that uses the given type.
424
+ # exists(name, type, opts={}) :: Return a boolean column for whether the JSON path yields any values.
425
+ # nested(path, &block) :: Extract nested data from the result set at the given path.
426
+ # This block is treated the same as a json_table block, and
427
+ # arbitrary levels of nesting are supported.
428
+ #
429
+ # The +column+ method supports the following options:
430
+ #
431
+ # :path :: JSON path to the object (the default is <tt>$.NAME</tt>, where +NAME+ is the
432
+ # name of the column).
433
+ # :format :: Set to +:json+ to use FORMAT JSON, when you expect the value to be a
434
+ # valid JSON object.
435
+ # :on_empty, :on_error :: How to handle case where JSON path evaluation is empty or
436
+ # results in an error. Values supported are:
437
+ # :empty_array :: Return empty array (requires <tt>format: :json</tt>)
438
+ # :empty_object :: Return empty object (requires <tt>format: :json</tt>)
439
+ # :error :: Raise a DatabaseError
440
+ # :null :: Return nil (NULL)
441
+ # :wrapper :: How to wrap returned values:
442
+ # true, :unconditional :: Always wrap returning values in an array
443
+ # :conditional :: Only wrap multiple return values in an array
444
+ # :keep_quotes :: Wrap scalar strings in quotes
445
+ # :omit_quotes :: Do not wrap scalar strings in quotes
446
+ #
447
+ # The +exists+ method supports the following options:
448
+ #
449
+ # :path :: JSON path to the object (same as +column+ option)
450
+ # :on_error :: How to handle case where JSON path evaluation results in an error.
451
+ # Values supported are:
452
+ # :error :: Raise a DatabaseError
453
+ # true :: Return true
454
+ # false :: Return false
455
+ # :null :: Return nil (NULL)
456
+ #
457
+ # Inside the block, methods for Ruby class names are also supported, allowing you
458
+ # to use syntax such as:
459
+ #
460
+ # json_op.table('$.a') do
461
+ # String :b
462
+ # Integer :c, path: '$.d'
463
+ # end
464
+ #
465
+ # One difference between this method and Database#create_table is that method_missing
466
+ # is not supported inside the block. Use the +column+ method for PostgreSQL types
467
+ # that are not mapped to Ruby classes.
468
+ def table(path, opts=OPTS, &block)
469
+ JSONTableOp.new(self, path, opts, &block)
307
470
  end
308
471
 
309
472
  # Builds arbitrary record from json object. You need to define the
@@ -329,6 +492,34 @@ module Sequel
329
492
  function(:typeof)
330
493
  end
331
494
 
495
+ # If called without arguments, operates as SQL::Wrapper#value. Otherwise,
496
+ # return the result of applying the JSON path expression to the receiver, by default
497
+ # returning results as text. Options:
498
+ #
499
+ # :on_empty :: How to handle case where path expression yields an empty set.
500
+ # Uses same values as :on_error option.
501
+ # :on_error :: How to handle errors when evaluating the JSON path expression.
502
+ # :null :: Return nil (default)
503
+ # :error :: raise a DatabaseError
504
+ # any other value :: used as default value
505
+ # :passing :: Variables to pass to the JSON path expression. Keys are variable
506
+ # names, values are the values of the variable.
507
+ # :returning :: The data type to return (text by default)
508
+ #
509
+ # json_op.value("$.a") # json_value(json, '$.a')
510
+ # json_op.value("$.a", passing: {a: 1}) # json_value(json, '$.a' PASSING 1 AS a)
511
+ # json_op.value("$.a", on_error: :error) # json_value(json, '$.a' ERROR ON ERROR)
512
+ # json_op.value("$.a", returning: Time) # json_value(json, '$.a' RETURNING timestamp)
513
+ # json_op.value("$.a", on_empty: 2) # json_value(json, '$.a' DEFAULT 2 ON EMPTY)
514
+ def value(path=(no_args_given = true), opts=OPTS)
515
+ if no_args_given
516
+ # Act as SQL::Wrapper#value
517
+ super()
518
+ else
519
+ Sequel::SQL::StringExpression.new(:NOOP, JSONValueOp.new(self, path, opts))
520
+ end
521
+ end
522
+
332
523
  private
333
524
 
334
525
  # Internals of IS [NOT] JSON support
@@ -650,13 +841,20 @@ module Sequel
650
841
  Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
651
842
  end
652
843
 
653
- # Wrap argument in a PGArray if it is an array
844
+ # Wrap argument in a PGArray if it is an array or a set
654
845
  def wrap_input_array(obj)
655
- if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
656
- Sequel.pg_array(obj)
657
- else
658
- obj
846
+ # :nocov:
847
+ if Sequel.respond_to?(:pg_array)
848
+ # :nocov:
849
+ case obj
850
+ when Array
851
+ return Sequel.pg_array(obj)
852
+ when Set
853
+ return Sequel.pg_array(obj.to_a)
854
+ end
659
855
  end
856
+
857
+ obj
660
858
  end
661
859
 
662
860
  # Wrap argument in a JSONBArray or JSONBHash if it is an array or hash.
@@ -705,6 +903,464 @@ module Sequel
705
903
  end
706
904
  end
707
905
 
906
+ # Object representing json_exists calls
907
+ class JSONExistsOp < SQL::Expression
908
+ ON_ERROR_SQL = {
909
+ true => 'TRUE',
910
+ false => 'FALSE',
911
+ :null => 'UNKNOWN',
912
+ :error => 'ERROR',
913
+ }.freeze
914
+ private_constant :ON_ERROR_SQL
915
+
916
+ # Expression (context_item in PostgreSQL terms), usually JSONBaseOp instance
917
+ attr_reader :expr
918
+
919
+ # JSON path expression to apply against the expression
920
+ attr_reader :path
921
+
922
+ # Variables to set in the JSON path expression
923
+ attr_reader :passing
924
+
925
+ # How to handle errors when evaluating the JSON path expression
926
+ attr_reader :on_error
927
+
928
+ # See JSONBaseOp#exists for documentation on the options.
929
+ def initialize(expr, path, opts=OPTS)
930
+ @expr = expr
931
+ @path = path
932
+ @passing = opts[:passing]
933
+ @on_error = opts[:on_error]
934
+ freeze
935
+ end
936
+
937
+ # Append the SQL function call expression to the SQL
938
+ def to_s_append(ds, sql)
939
+ to_s_append_function_name(ds, sql)
940
+ to_s_append_args_passing(ds, sql)
941
+ to_s_append_on_error(ds, sql)
942
+ sql << ')'
943
+ end
944
+
945
+ # Support transforming of function call expression
946
+ def sequel_ast_transform(transformer)
947
+ opts = {}
948
+ transform_opts(transformer, opts)
949
+ self.class.new(transformer.call(@expr), @path, opts)
950
+ end
951
+
952
+ private
953
+
954
+ # Set the :passing and :on_error options when doing an
955
+ # AST transform.
956
+ def transform_opts(transformer, opts)
957
+ if @passing
958
+ passing = opts[:passing] = {}
959
+ @passing.each do |k, v|
960
+ passing[k] = transformer.call(v)
961
+ end
962
+ end
963
+
964
+ opts[:on_error] = @on_error
965
+ end
966
+
967
+ def to_s_append_function_name(ds, sql)
968
+ sql << 'json_exists('
969
+ end
970
+
971
+ # Append the expression, path, and optional PASSING fragments
972
+ def to_s_append_args_passing(ds, sql)
973
+ ds.literal_append(sql, @expr)
974
+ sql << ', '
975
+ ds.literal_append(sql, @path)
976
+
977
+ if (passing = @passing) && !passing.empty?
978
+ sql << ' PASSING '
979
+ comma = false
980
+ passing.each do |k, v|
981
+ if comma
982
+ sql << ', '
983
+ else
984
+ comma = true
985
+ end
986
+ ds.literal_append(sql, v)
987
+ sql << " AS " << k.to_s
988
+ end
989
+ end
990
+ end
991
+
992
+ # Append the optional ON ERROR fragments
993
+ def to_s_append_on_error(ds, sql)
994
+ unless @on_error.nil?
995
+ sql << " "
996
+ to_s_append_on_value(ds, sql, @on_error)
997
+ sql << " ON ERROR"
998
+ end
999
+ end
1000
+
1001
+ # Append the value to use for ON ERROR
1002
+ def to_s_append_on_value(ds, sql, value)
1003
+ sql << ON_ERROR_SQL.fetch(value)
1004
+ end
1005
+ end
1006
+
1007
+ # Object representing json_value calls
1008
+ class JSONValueOp < JSONExistsOp
1009
+ ON_SQL = {
1010
+ :null => 'NULL',
1011
+ :error => 'ERROR',
1012
+ }.freeze
1013
+ private_constant :ON_SQL
1014
+
1015
+ # The database type to cast returned values to
1016
+ attr_reader :returning
1017
+
1018
+ # How to handle cases where the JSON path expression evaluation yields
1019
+ # an empty set.
1020
+ attr_reader :on_empty
1021
+
1022
+ # See JSONBaseOp#value for documentation of the options.
1023
+ def initialize(expr, path, opts=OPTS)
1024
+ @returning = opts[:returning]
1025
+ @on_empty = opts[:on_empty]
1026
+ super
1027
+ end
1028
+
1029
+ private
1030
+
1031
+ # Also handle transforming the returning and on_empty options.
1032
+ def transform_opts(transformer, opts)
1033
+ super
1034
+ opts[:returning] = @returning
1035
+ on_error = @on_error
1036
+ on_error = transformer.call(on_error) unless on_sql_value(on_error)
1037
+ opts[:on_error] = on_error
1038
+ on_empty = @on_empty
1039
+ on_empty = transformer.call(on_empty) unless on_sql_value(on_empty)
1040
+ opts[:on_empty] = on_empty
1041
+ end
1042
+
1043
+ def to_s_append_function_name(ds, sql)
1044
+ sql << 'json_value('
1045
+ end
1046
+
1047
+ # Also append the optional RETURNING fragment
1048
+ def to_s_append_args_passing(ds, sql)
1049
+ super
1050
+
1051
+ if @returning
1052
+ sql << ' RETURNING ' << ds.db.cast_type_literal(@returning).to_s
1053
+ end
1054
+ end
1055
+
1056
+ # Also append the optional ON EMPTY fragment
1057
+ def to_s_append_on_error(ds, sql)
1058
+ unless @on_empty.nil?
1059
+ sql << " "
1060
+ to_s_append_on_value(ds, sql, @on_empty)
1061
+ sql << " ON EMPTY"
1062
+ end
1063
+
1064
+ super
1065
+ end
1066
+
1067
+ # Handle DEFAULT values in ON EMPTY/ON ERROR fragments
1068
+ def to_s_append_on_value(ds, sql, value)
1069
+ if v = on_sql_value(value)
1070
+ sql << v
1071
+ else
1072
+ sql << 'DEFAULT '
1073
+ default_literal_append(ds, sql, value)
1074
+ end
1075
+ end
1076
+
1077
+ # Do not auto paramterize default value, as PostgreSQL doesn't allow it.
1078
+ def default_literal_append(ds, sql, v)
1079
+ if sql.respond_to?(:skip_auto_param)
1080
+ sql.skip_auto_param do
1081
+ ds.literal_append(sql, v)
1082
+ end
1083
+ else
1084
+ ds.literal_append(sql, v)
1085
+ end
1086
+ end
1087
+
1088
+ def on_sql_value(value)
1089
+ ON_SQL[value]
1090
+ end
1091
+ end
1092
+
1093
+ # Object representing json_query calls
1094
+ class JSONQueryOp < JSONValueOp
1095
+ ON_SQL = {
1096
+ :null => 'NULL',
1097
+ :error => 'ERROR',
1098
+ :empty_array => 'EMPTY ARRAY',
1099
+ :empty_object => 'EMPTY OBJECT',
1100
+ }.freeze
1101
+ private_constant :ON_SQL
1102
+
1103
+ WRAPPER = {
1104
+ :conditional => ' WITH CONDITIONAL WRAPPER',
1105
+ :unconditional => ' WITH WRAPPER',
1106
+ :omit_quotes => ' OMIT QUOTES'
1107
+ }
1108
+ WRAPPER[true] = WRAPPER[:unconditional]
1109
+ WRAPPER.freeze
1110
+ private_constant :WRAPPER
1111
+
1112
+ # How to handle wrapping of results
1113
+ attr_reader :wrapper
1114
+
1115
+ # See JSONBaseOp#query for documentation of the options.
1116
+ def initialize(expr, path, opts=OPTS)
1117
+ @wrapper = opts[:wrapper]
1118
+ super
1119
+ end
1120
+
1121
+ private
1122
+
1123
+ # Also handle transforming the wrapper option
1124
+ def transform_opts(transformer, opts)
1125
+ super
1126
+ opts[:wrapper] = @wrapper
1127
+ end
1128
+
1129
+ def to_s_append_function_name(ds, sql)
1130
+ sql << 'json_query('
1131
+ end
1132
+
1133
+ # Also append the optional WRAPPER/OMIT QUOTES fragment
1134
+ def to_s_append_args_passing(ds, sql)
1135
+ super
1136
+
1137
+ if @wrapper
1138
+ sql << WRAPPER.fetch(@wrapper)
1139
+ end
1140
+ end
1141
+
1142
+ def on_sql_value(value)
1143
+ ON_SQL[value]
1144
+ end
1145
+ end
1146
+
1147
+ # Object representing json_table calls
1148
+ class JSONTableOp < SQL::Expression
1149
+ TABLE_ON_ERROR_SQL = {
1150
+ :error => ' ERROR ON ERROR',
1151
+ :empty_array => ' EMPTY ARRAY ON ERROR',
1152
+ }.freeze
1153
+ private_constant :TABLE_ON_ERROR_SQL
1154
+
1155
+ COLUMN_ON_SQL = {
1156
+ :null => ' NULL',
1157
+ :error => ' ERROR',
1158
+ :empty_array => ' EMPTY ARRAY',
1159
+ :empty_object => ' EMPTY OBJECT',
1160
+ }.freeze
1161
+ private_constant :COLUMN_ON_SQL
1162
+
1163
+ EXISTS_ON_ERROR_SQL = {
1164
+ :error => ' ERROR',
1165
+ true => ' TRUE',
1166
+ false => ' FALSE',
1167
+ :null => ' UNKNOWN',
1168
+ }.freeze
1169
+ private_constant :EXISTS_ON_ERROR_SQL
1170
+
1171
+ WRAPPER = {
1172
+ :conditional => ' WITH CONDITIONAL WRAPPER',
1173
+ :unconditional => ' WITH WRAPPER',
1174
+ :omit_quotes => ' OMIT QUOTES',
1175
+ :keep_quotes => ' KEEP QUOTES',
1176
+ }
1177
+ WRAPPER[true] = WRAPPER[:unconditional]
1178
+ WRAPPER.freeze
1179
+ private_constant :WRAPPER
1180
+
1181
+ # Class used to evaluate json_table blocks and nested blocks
1182
+ class ColumnDSL
1183
+ # Return array of column information recorded for the instance
1184
+ attr_reader :columns
1185
+
1186
+ def self.columns(&block)
1187
+ new(&block).columns.freeze
1188
+ end
1189
+
1190
+ def initialize(&block)
1191
+ @columns = []
1192
+ instance_exec(&block)
1193
+ end
1194
+
1195
+ # Include a FOR ORDINALITY column
1196
+ def ordinality(name)
1197
+ @columns << [:ordinality, name].freeze
1198
+ end
1199
+
1200
+ # Include a regular column with the given type
1201
+ def column(name, type, opts=OPTS)
1202
+ @columns << [:column, name, type, opts].freeze
1203
+ end
1204
+
1205
+ # Include an EXISTS column with the given type
1206
+ def exists(name, type, opts=OPTS)
1207
+ @columns << [:exists, name, type, opts].freeze
1208
+ end
1209
+
1210
+ # Include a nested set of columns at the given path.
1211
+ def nested(path, &block)
1212
+ @columns << [:nested, path, ColumnDSL.columns(&block)].freeze
1213
+ end
1214
+
1215
+ # Include a bigint column
1216
+ def Bignum(name, opts=OPTS)
1217
+ @columns << [:column, name, :Bignum, opts].freeze
1218
+ end
1219
+
1220
+ # Define methods for handling other generic types
1221
+ %w'String Integer Float Numeric BigDecimal Date DateTime Time File TrueClass FalseClass'.each do |meth|
1222
+ klass = Object.const_get(meth)
1223
+ define_method(meth) do |name, opts=OPTS|
1224
+ @columns << [:column, name, klass, opts].freeze
1225
+ end
1226
+ end
1227
+ end
1228
+ private_constant :ColumnDSL
1229
+
1230
+ # See JSONBaseOp#table for documentation on the options.
1231
+ def initialize(expr, path, opts=OPTS, &block)
1232
+ @expr = expr
1233
+ @path = path
1234
+ @passing = opts[:passing]
1235
+ @on_error = opts[:on_error]
1236
+ @columns = opts[:_columns] || ColumnDSL.columns(&block)
1237
+ freeze
1238
+ end
1239
+
1240
+ # Append the json_table function call expression to the SQL
1241
+ def to_s_append(ds, sql)
1242
+ sql << 'json_table('
1243
+ ds.literal_append(sql, @expr)
1244
+ sql << ', '
1245
+ default_literal_append(ds, sql, @path)
1246
+
1247
+ if (passing = @passing) && !passing.empty?
1248
+ sql << ' PASSING '
1249
+ comma = false
1250
+ passing.each do |k, v|
1251
+ if comma
1252
+ sql << ', '
1253
+ else
1254
+ comma = true
1255
+ end
1256
+ ds.literal_append(sql, v)
1257
+ sql << " AS " << k.to_s
1258
+ end
1259
+ end
1260
+
1261
+ to_s_append_columns(ds, sql, @columns)
1262
+ sql << TABLE_ON_ERROR_SQL.fetch(@on_error) if @on_error
1263
+ sql << ')'
1264
+ end
1265
+
1266
+ # Support transforming of json_table expression
1267
+ def sequel_ast_transform(transformer)
1268
+ opts = {:on_error=>@on_error, :_columns=>@columns}
1269
+
1270
+ if @passing
1271
+ passing = opts[:passing] = {}
1272
+ @passing.each do |k, v|
1273
+ passing[k] = transformer.call(v)
1274
+ end
1275
+ end
1276
+
1277
+ self.class.new(transformer.call(@expr), @path, opts)
1278
+ end
1279
+
1280
+ private
1281
+
1282
+ # Append the set of column information to the SQL. Separated to handle
1283
+ # nested sets of columns.
1284
+ def to_s_append_columns(ds, sql, columns)
1285
+ sql << ' COLUMNS('
1286
+ comma = nil
1287
+ columns.each do |column|
1288
+ if comma
1289
+ sql << comma
1290
+ else
1291
+ comma = ', '
1292
+ end
1293
+ to_s_append_column(ds, sql, column)
1294
+ end
1295
+ sql << ')'
1296
+ end
1297
+
1298
+ # Append the column information to the SQL. Handles the various
1299
+ # types of json_table columns.
1300
+ def to_s_append_column(ds, sql, column)
1301
+ case column[0]
1302
+ when :column
1303
+ _, name, type, opts = column
1304
+ ds.literal_append(sql, name)
1305
+ sql << ' ' << ds.db.send(:type_literal, opts.merge(:type=>type)).to_s
1306
+ sql << ' FORMAT JSON' if opts[:format] == :json
1307
+ to_s_append_path(ds, sql, opts[:path])
1308
+ sql << WRAPPER.fetch(opts[:wrapper]) if opts[:wrapper]
1309
+ to_s_append_on_value(ds, sql, opts[:on_empty], " ON EMPTY")
1310
+ to_s_append_on_value(ds, sql, opts[:on_error], " ON ERROR")
1311
+ when :ordinality
1312
+ ds.literal_append(sql, column[1])
1313
+ sql << ' FOR ORDINALITY'
1314
+ when :exists
1315
+ _, name, type, opts = column
1316
+ ds.literal_append(sql, name)
1317
+ sql << ' ' << ds.db.send(:type_literal, opts.merge(:type=>type)).to_s
1318
+ sql << ' EXISTS'
1319
+ to_s_append_path(ds, sql, opts[:path])
1320
+ unless (on_error = opts[:on_error]).nil?
1321
+ sql << EXISTS_ON_ERROR_SQL.fetch(on_error) << " ON ERROR"
1322
+ end
1323
+ else # when :nested
1324
+ _, path, columns = column
1325
+ sql << 'NESTED '
1326
+ default_literal_append(ds, sql, path)
1327
+ to_s_append_columns(ds, sql, columns)
1328
+ end
1329
+ end
1330
+
1331
+ # Handle DEFAULT values in ON EMPTY/ON ERROR fragments
1332
+ def to_s_append_on_value(ds, sql, value, cond)
1333
+ if value
1334
+ if v = COLUMN_ON_SQL[value]
1335
+ sql << v
1336
+ else
1337
+ sql << ' DEFAULT '
1338
+ default_literal_append(ds, sql, value)
1339
+ end
1340
+ sql << cond
1341
+ end
1342
+ end
1343
+
1344
+ # Append path caluse to the SQL
1345
+ def to_s_append_path(ds, sql, path)
1346
+ if path
1347
+ sql << ' PATH '
1348
+ default_literal_append(ds, sql, path)
1349
+ end
1350
+ end
1351
+
1352
+ # Do not auto paramterize default value or path value, as PostgreSQL doesn't allow it.
1353
+ def default_literal_append(ds, sql, v)
1354
+ if sql.respond_to?(:skip_auto_param)
1355
+ sql.skip_auto_param do
1356
+ ds.literal_append(sql, v)
1357
+ end
1358
+ else
1359
+ ds.literal_append(sql, v)
1360
+ end
1361
+ end
1362
+ end
1363
+
708
1364
  module JSONOpMethods
709
1365
  # Wrap the receiver in an JSONOp so you can easily use the PostgreSQL
710
1366
  # json functions and operators with it.