sequel 5.84.0 → 5.85.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5677b07054121f55f100f0629d8102cabdd9db454263c5199a8f401bea891992
4
- data.tar.gz: ca57e632e3e66c3a4003f988e5240da6a6eed0de9a143ac3985bd0603e0d9073
3
+ metadata.gz: ecb752c20a2b59df814e873f04ac143aadd875b503b2400cca8929d9eabff572
4
+ data.tar.gz: d528c6d03626b725f27599c8cf624311517e4f4fe67c12139a2e815278fbb707
5
5
  SHA512:
6
- metadata.gz: 2ea8e58679c4ae2455588b584b959b3be691c177823cf336b77fa7b5910e716d9ca88e54024bbef44ec58d3fa8d66070acbf94e8c32a4294126ab2e618f4285e
7
- data.tar.gz: acf4e565dd754b16ea0c07a11fe67cd21c9d5833ac1cf70c384a8da2c1b7c9ffd324358dbab9a8196d5a249365cc11870e6c807d07dbc631d359b413977f14ed
6
+ metadata.gz: 4fcb91f8acb96a4a57152c9a0f41e78af1b42d6c393f8c90b2099e00d632676096c8ac27b0e31699fd31be0e22912b55e4372353fef93e967e548741dbc6406e
7
+ data.tar.gz: e2545b4dcca9ca1a13f47cbfdf2f38802d2564f7f0766d526e5041b93a94142560517f5d3ef5c9a5d2692e67a482c8044b4812c064fd35c2edf0c19a3df6b25c
@@ -70,13 +70,13 @@ class Sequel::ConnectionPool
70
70
  else
71
71
  pc = if opts[:single_threaded]
72
72
  opts[:servers] ? :sharded_single : :single
73
- # :nocov:
74
- elsif RUBY_VERSION >= '3.4' # SEQUEL6 or maybe earlier switch to 3.2
73
+ elsif RUBY_VERSION >= '3.2'
75
74
  opts[:servers] ? :sharded_timed_queue : :timed_queue
76
75
  # :nocov:
77
76
  else
78
77
  opts[:servers] ? :sharded_threaded : :threaded
79
78
  end
79
+ # :nocov:
80
80
 
81
81
  connection_pool_class(:pool_class=>pc)
82
82
  end
@@ -217,7 +217,7 @@ module Sequel
217
217
  case args.length
218
218
  when 0
219
219
  unless block
220
- return single_record
220
+ return(@opts[:sql] ? single_record! : single_record)
221
221
  end
222
222
  when 1
223
223
  arg = args[0]
@@ -282,6 +282,12 @@ module Sequel
282
282
  #
283
283
  # DB[:table].get{[sum(id).as(sum), name]} # SELECT sum(id) AS sum, name FROM table LIMIT 1
284
284
  # # => [6, 'foo']
285
+ #
286
+ # If called on a dataset with raw SQL, returns the
287
+ # first value in the dataset without changing the selection or setting a limit:
288
+ #
289
+ # DB["SELECT id FROM table"].get # SELECT id FROM table
290
+ # # => 3
285
291
  def get(column=(no_arg=true; nil), &block)
286
292
  ds = naked
287
293
  if block
@@ -289,6 +295,8 @@ module Sequel
289
295
  ds = ds.select(&block)
290
296
  column = ds.opts[:select]
291
297
  column = nil if column.is_a?(Array) && column.length < 2
298
+ elsif no_arg && opts[:sql]
299
+ return ds.single_value!
292
300
  else
293
301
  case column
294
302
  when Array
@@ -0,0 +1,41 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The dataset_run extension is designed for cases where you want
4
+ # to use dataset methods to build a query, but want to run that
5
+ # query without returning a result. The most common need would
6
+ # be to easily use placeholders in an SQL string, which Database#run
7
+ # does not support directly.
8
+ #
9
+ # You can load this extension into specific datasets:
10
+ #
11
+ # ds = DB["GRANT SELECT ON ? TO ?", :table, :user]
12
+ # ds = ds.extension(:dataset_run)
13
+ # ds.run
14
+ #
15
+ # Or you can load it into all of a database's datasets, which
16
+ # is probably the desired behavior if you are using this extension:
17
+ #
18
+ # DB.extension(:dataset_run)
19
+ # DB["GRANT SELECT ON ? TO ?", :table, :user].run
20
+ #
21
+ # Related module: Sequel::DatasetRun
22
+
23
+ #
24
+ module Sequel
25
+ module DatasetRun
26
+ # Run the dataset's SQL on the database. Returns NULL. This is
27
+ # useful when you want to run SQL without returning a result.
28
+ #
29
+ # DB["GRANT SELECT ON ? TO ?", :table, :user].run
30
+ # # GRANT SELECT ON "table" TO "user"
31
+ def run
32
+ if server = @opts[:server]
33
+ db.run(sql, :server=>server)
34
+ else
35
+ db.run(sql)
36
+ end
37
+ end
38
+ end
39
+
40
+ Dataset.register_extension(:dataset_run, DatasetRun)
41
+ end
@@ -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,8 @@
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
134
  #
135
135
  # On PostgreSQL 17+, the additional JSON functions are supported (see method documentation
136
136
  # for additional options):
@@ -143,6 +143,29 @@
143
143
  # j.value('$.foo', returning: Time) # json_value(jsonb_column, '$.foo' RETURNING timestamp)
144
144
  # j.query('$.foo', wrapper: true) # json_query(jsonb_column, '$.foo' WITH WRAPPER)
145
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
+ #
146
169
  # If you are also using the pg_json extension, you should load it before
147
170
  # loading this extension. Doing so will allow you to use the #op method on
148
171
  # JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
@@ -364,6 +387,72 @@ module Sequel
364
387
  self.class.new(function(:strip_nulls))
365
388
  end
366
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
+
367
456
  # Builds arbitrary record from json object. You need to define the
368
457
  # structure of the record using #as on the resulting object:
369
458
  #
@@ -1032,6 +1121,223 @@ module Sequel
1032
1121
  end
1033
1122
  end
1034
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
+
1035
1341
  module JSONOpMethods
1036
1342
  # Wrap the receiver in an JSONOp so you can easily use the PostgreSQL
1037
1343
  # json functions and operators with it.
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 84
9
+ MINOR = 85
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.84.0
4
+ version: 5.85.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-01 00:00:00.000000000 Z
11
+ date: 2024-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -220,6 +220,7 @@ files:
220
220
  - lib/sequel/extensions/core_extensions.rb
221
221
  - lib/sequel/extensions/core_refinements.rb
222
222
  - lib/sequel/extensions/current_datetime_timestamp.rb
223
+ - lib/sequel/extensions/dataset_run.rb
223
224
  - lib/sequel/extensions/dataset_source_alias.rb
224
225
  - lib/sequel/extensions/date_arithmetic.rb
225
226
  - lib/sequel/extensions/date_parse_input_handler.rb
@@ -443,7 +444,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
443
444
  - !ruby/object:Gem::Version
444
445
  version: '0'
445
446
  requirements: []
446
- rubygems_version: 3.5.11
447
+ rubygems_version: 3.5.16
447
448
  signing_key:
448
449
  specification_version: 4
449
450
  summary: The Database Toolkit for Ruby