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