sequel 5.83.1 → 5.85.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/shared/sqlite.rb +3 -1
  3. data/lib/sequel/connection_pool.rb +2 -2
  4. data/lib/sequel/database/schema_methods.rb +2 -0
  5. data/lib/sequel/dataset/actions.rb +9 -1
  6. data/lib/sequel/extensions/dataset_run.rb +41 -0
  7. data/lib/sequel/extensions/pg_json_ops.rb +642 -9
  8. data/lib/sequel/sql.rb +8 -5
  9. data/lib/sequel/version.rb +2 -2
  10. metadata +4 -237
  11. data/CHANGELOG +0 -1397
  12. data/README.rdoc +0 -936
  13. data/doc/advanced_associations.rdoc +0 -884
  14. data/doc/association_basics.rdoc +0 -1859
  15. data/doc/bin_sequel.rdoc +0 -146
  16. data/doc/cheat_sheet.rdoc +0 -255
  17. data/doc/code_order.rdoc +0 -104
  18. data/doc/core_extensions.rdoc +0 -405
  19. data/doc/dataset_basics.rdoc +0 -96
  20. data/doc/dataset_filtering.rdoc +0 -222
  21. data/doc/extensions.rdoc +0 -77
  22. data/doc/fork_safety.rdoc +0 -84
  23. data/doc/mass_assignment.rdoc +0 -98
  24. data/doc/migration.rdoc +0 -660
  25. data/doc/model_dataset_method_design.rdoc +0 -129
  26. data/doc/model_hooks.rdoc +0 -254
  27. data/doc/model_plugins.rdoc +0 -270
  28. data/doc/mssql_stored_procedures.rdoc +0 -43
  29. data/doc/object_model.rdoc +0 -563
  30. data/doc/opening_databases.rdoc +0 -439
  31. data/doc/postgresql.rdoc +0 -611
  32. data/doc/prepared_statements.rdoc +0 -144
  33. data/doc/querying.rdoc +0 -1070
  34. data/doc/reflection.rdoc +0 -120
  35. data/doc/release_notes/5.0.0.txt +0 -159
  36. data/doc/release_notes/5.1.0.txt +0 -31
  37. data/doc/release_notes/5.10.0.txt +0 -84
  38. data/doc/release_notes/5.11.0.txt +0 -83
  39. data/doc/release_notes/5.12.0.txt +0 -141
  40. data/doc/release_notes/5.13.0.txt +0 -27
  41. data/doc/release_notes/5.14.0.txt +0 -63
  42. data/doc/release_notes/5.15.0.txt +0 -39
  43. data/doc/release_notes/5.16.0.txt +0 -110
  44. data/doc/release_notes/5.17.0.txt +0 -31
  45. data/doc/release_notes/5.18.0.txt +0 -69
  46. data/doc/release_notes/5.19.0.txt +0 -28
  47. data/doc/release_notes/5.2.0.txt +0 -33
  48. data/doc/release_notes/5.20.0.txt +0 -89
  49. data/doc/release_notes/5.21.0.txt +0 -87
  50. data/doc/release_notes/5.22.0.txt +0 -48
  51. data/doc/release_notes/5.23.0.txt +0 -56
  52. data/doc/release_notes/5.24.0.txt +0 -56
  53. data/doc/release_notes/5.25.0.txt +0 -32
  54. data/doc/release_notes/5.26.0.txt +0 -35
  55. data/doc/release_notes/5.27.0.txt +0 -21
  56. data/doc/release_notes/5.28.0.txt +0 -16
  57. data/doc/release_notes/5.29.0.txt +0 -22
  58. data/doc/release_notes/5.3.0.txt +0 -121
  59. data/doc/release_notes/5.30.0.txt +0 -20
  60. data/doc/release_notes/5.31.0.txt +0 -148
  61. data/doc/release_notes/5.32.0.txt +0 -46
  62. data/doc/release_notes/5.33.0.txt +0 -24
  63. data/doc/release_notes/5.34.0.txt +0 -40
  64. data/doc/release_notes/5.35.0.txt +0 -56
  65. data/doc/release_notes/5.36.0.txt +0 -60
  66. data/doc/release_notes/5.37.0.txt +0 -30
  67. data/doc/release_notes/5.38.0.txt +0 -28
  68. data/doc/release_notes/5.39.0.txt +0 -19
  69. data/doc/release_notes/5.4.0.txt +0 -80
  70. data/doc/release_notes/5.40.0.txt +0 -40
  71. data/doc/release_notes/5.41.0.txt +0 -25
  72. data/doc/release_notes/5.42.0.txt +0 -136
  73. data/doc/release_notes/5.43.0.txt +0 -98
  74. data/doc/release_notes/5.44.0.txt +0 -32
  75. data/doc/release_notes/5.45.0.txt +0 -34
  76. data/doc/release_notes/5.46.0.txt +0 -87
  77. data/doc/release_notes/5.47.0.txt +0 -59
  78. data/doc/release_notes/5.48.0.txt +0 -14
  79. data/doc/release_notes/5.49.0.txt +0 -59
  80. data/doc/release_notes/5.5.0.txt +0 -61
  81. data/doc/release_notes/5.50.0.txt +0 -78
  82. data/doc/release_notes/5.51.0.txt +0 -47
  83. data/doc/release_notes/5.52.0.txt +0 -87
  84. data/doc/release_notes/5.53.0.txt +0 -23
  85. data/doc/release_notes/5.54.0.txt +0 -27
  86. data/doc/release_notes/5.55.0.txt +0 -21
  87. data/doc/release_notes/5.56.0.txt +0 -51
  88. data/doc/release_notes/5.57.0.txt +0 -23
  89. data/doc/release_notes/5.58.0.txt +0 -31
  90. data/doc/release_notes/5.59.0.txt +0 -73
  91. data/doc/release_notes/5.6.0.txt +0 -31
  92. data/doc/release_notes/5.60.0.txt +0 -22
  93. data/doc/release_notes/5.61.0.txt +0 -43
  94. data/doc/release_notes/5.62.0.txt +0 -132
  95. data/doc/release_notes/5.63.0.txt +0 -33
  96. data/doc/release_notes/5.64.0.txt +0 -50
  97. data/doc/release_notes/5.65.0.txt +0 -21
  98. data/doc/release_notes/5.66.0.txt +0 -24
  99. data/doc/release_notes/5.67.0.txt +0 -32
  100. data/doc/release_notes/5.68.0.txt +0 -61
  101. data/doc/release_notes/5.69.0.txt +0 -26
  102. data/doc/release_notes/5.7.0.txt +0 -108
  103. data/doc/release_notes/5.70.0.txt +0 -35
  104. data/doc/release_notes/5.71.0.txt +0 -21
  105. data/doc/release_notes/5.72.0.txt +0 -33
  106. data/doc/release_notes/5.73.0.txt +0 -66
  107. data/doc/release_notes/5.74.0.txt +0 -45
  108. data/doc/release_notes/5.75.0.txt +0 -35
  109. data/doc/release_notes/5.76.0.txt +0 -86
  110. data/doc/release_notes/5.77.0.txt +0 -63
  111. data/doc/release_notes/5.78.0.txt +0 -67
  112. data/doc/release_notes/5.79.0.txt +0 -28
  113. data/doc/release_notes/5.8.0.txt +0 -170
  114. data/doc/release_notes/5.80.0.txt +0 -40
  115. data/doc/release_notes/5.81.0.txt +0 -31
  116. data/doc/release_notes/5.82.0.txt +0 -61
  117. data/doc/release_notes/5.83.0.txt +0 -56
  118. data/doc/release_notes/5.9.0.txt +0 -99
  119. data/doc/schema_modification.rdoc +0 -679
  120. data/doc/security.rdoc +0 -443
  121. data/doc/sharding.rdoc +0 -286
  122. data/doc/sql.rdoc +0 -648
  123. data/doc/testing.rdoc +0 -204
  124. data/doc/thread_safety.rdoc +0 -15
  125. data/doc/transactions.rdoc +0 -250
  126. data/doc/validations.rdoc +0 -558
  127. 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.