sequel 5.84.0 → 5.85.0

Sign up to get free protection for your applications and to get access to all the features.
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