sequel 5.102.0 → 5.103.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: 92eed5c337b361dfde5b948323fb6a0a22ad89ecb2473c284d9e5b70a5eed8e2
4
- data.tar.gz: dd07fefb9f3d2821479be092a12b87c6e7b92f5d8d8c314ff656741d31e75531
3
+ metadata.gz: 955bca9c729dcd1f54079d4d48d7ac162b6d7e2b234857cfd80d228117541e5a
4
+ data.tar.gz: fe96ad60b73bd55cbaec0f62ba0b977cb79a7ba23fa33a392116b54ca5e7ef34
5
5
  SHA512:
6
- metadata.gz: ea53c48308a031896491a59e57ca3902a9a3478b8d09d12de6548a927991419054fa8698463c18c18cef9801a9c4629dc299b27df03245272bb2296f79ccdc1c
7
- data.tar.gz: adfd92660e225f0d67948dfdc4aa57c2c61c1118265908f8b6a26fe23179573aff4195aec759575abea984e46fa5d1b62701b75caa8ce7e78838f6434e208527
6
+ metadata.gz: 783fa47d72067acb98193be788f133891839984441d0ca763c6be92421b99d294c1710b52e7538a1725fe476bd17b16a1796f68d96017f3e07ef12cc34a2181e
7
+ data.tar.gz: 38f08e76546e3cfee1fa4c520b9d524b1558608572532a940ab06c57ddb50b2ec7766978ab020d9754af415fbd679383d44a506018cca141c0c46ba9f62271cc
data/MIT-LICENSE CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2007-2008 Sharon Rosner
2
- Copyright (c) 2008-2023 Jeremy Evans
2
+ Copyright (c) 2008-2026 Jeremy Evans and contributors
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ # SEQUEL6: Remove
4
+
3
5
  Sequel::JDBC.load_driver('Java::OrgApacheDerbyJdbc::EmbeddedDriver', :Derby)
4
6
  require_relative 'transactions'
5
7
  require_relative '../utils/columns_limit_1'
@@ -18,7 +18,7 @@ module Sequel
18
18
  include AutoCastDateAndTime
19
19
 
20
20
  def commit_prepared_transaction(transaction_id, opts=OPTS)
21
- run("COMMIT TRANSACTION #{transaction_id}", opts)
21
+ run("COMMIT TRANSACTION #{transaction_id}".freeze, opts)
22
22
  end
23
23
 
24
24
  def database_type
@@ -36,7 +36,7 @@ module Sequel
36
36
  end
37
37
 
38
38
  def rollback_prepared_transaction(transaction_id, opts=OPTS)
39
- run("ROLLBACK TRANSACTION #{transaction_id}", opts)
39
+ run("ROLLBACK TRANSACTION #{transaction_id}".freeze, opts)
40
40
  end
41
41
 
42
42
  # H2 uses an IDENTITY type for primary keys
@@ -727,7 +727,7 @@ module Sequel
727
727
  prepared_args << y
728
728
  i = prepared_args.length
729
729
  end
730
- LiteralString.new("#{prepared_arg_placeholder}#{i}")
730
+ LiteralString.new("#{prepared_arg_placeholder}#{i}".freeze)
731
731
  end
732
732
  end
733
733
 
@@ -106,7 +106,7 @@ module Sequel
106
106
  values << value
107
107
  end
108
108
 
109
- sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
109
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}".freeze
110
110
 
111
111
  ds = dataset.with_sql(sql)
112
112
  ds = ds.server(opts[:server]) if opts[:server]
@@ -400,7 +400,7 @@ module Sequel
400
400
  # Error if a string is given.
401
401
  def create_table_as(name, ds, options)
402
402
  raise(Error, "must provide dataset instance as value of create_table :as option on MSSQL") unless ds.is_a?(Sequel::Dataset)
403
- run(ds.into(name).sql)
403
+ run(ds.into(name).sql.freeze)
404
404
  end
405
405
 
406
406
  DATABASE_ERROR_REGEXPS = {
@@ -878,7 +878,7 @@ module Sequel
878
878
  elsif @opts[:output]
879
879
  # no transaction: our multi_insert_sql_strategy should guarantee
880
880
  # that there's only ever a single statement.
881
- sql = multi_insert_sql(columns, values)[0]
881
+ sql = multi_insert_sql(columns, values)[0].freeze
882
882
  naked.with_sql(sql).map{|v| v.length == 1 ? v.values.first : v}
883
883
  else
884
884
  super
@@ -40,7 +40,7 @@ module Sequel
40
40
  end
41
41
 
42
42
  def commit_prepared_transaction(transaction_id, opts=OPTS)
43
- run("XA COMMIT #{literal(transaction_id)}", opts)
43
+ run("XA COMMIT #{literal(transaction_id)}".freeze, opts)
44
44
  end
45
45
 
46
46
  def database_type
@@ -103,7 +103,7 @@ module Sequel
103
103
  sql += " FROM #{literal(schema)}"
104
104
  end
105
105
 
106
- metadata_dataset.with_sql(sql).each do |r|
106
+ metadata_dataset.with_sql(sql.freeze).each do |r|
107
107
  name = r[:Key_name]
108
108
  next if name == 'PRIMARY'
109
109
  name = m.call(name)
@@ -115,7 +115,7 @@ module Sequel
115
115
  end
116
116
 
117
117
  def rollback_prepared_transaction(transaction_id, opts=OPTS)
118
- run("XA ROLLBACK #{literal(transaction_id)}", opts)
118
+ run("XA ROLLBACK #{literal(transaction_id)}".freeze, opts)
119
119
  end
120
120
 
121
121
  # Whether the database is MariaDB and not MySQL
@@ -780,7 +780,8 @@ module Sequel
780
780
  # Load the PrettyTable class, needed for explain output
781
781
  Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
782
782
 
783
- ds = db.send(:metadata_dataset).with_sql(((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql).naked
783
+ sql = ((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql
784
+ ds = db.send(:metadata_dataset).with_sql(sql.freeze).naked
784
785
  rows = ds.all
785
786
  Sequel::PrettyTable.string(rows, ds.columns)
786
787
  end
@@ -339,7 +339,7 @@ module Sequel
339
339
  end
340
340
 
341
341
  def commit_prepared_transaction(transaction_id, opts=OPTS)
342
- run("COMMIT PREPARED #{literal(transaction_id)}", opts)
342
+ run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
343
343
  end
344
344
 
345
345
  # A hash of metadata for CHECK constraints on the table.
@@ -416,7 +416,7 @@ module Sequel
416
416
  return if already_identity
417
417
 
418
418
  transaction(server_hash) do
419
- run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT", server_hash)
419
+ run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT".freeze, server_hash)
420
420
 
421
421
  ds.from(:pg_depend).
422
422
  where(:classid=>pg_class, :objid=>seq_oid, :objsubid=>0, :deptype=>'a').
@@ -453,7 +453,7 @@ module Sequel
453
453
  # often used here if :security_definer is used.
454
454
  # :strict :: Makes the function return NULL when any argument is NULL.
455
455
  def create_function(name, definition, opts=OPTS)
456
- self << create_function_sql(name, definition, opts)
456
+ self << create_function_sql(name, definition, opts).freeze
457
457
  end
458
458
 
459
459
  # Create the procedural language in the database. Arguments:
@@ -464,7 +464,7 @@ module Sequel
464
464
  # :trusted :: Marks the language being created as trusted, allowing unprivileged users to create functions using this language.
465
465
  # :validator :: The name of previously registered function used as a validator of functions defined in this language.
466
466
  def create_language(name, opts=OPTS)
467
- self << create_language_sql(name, opts)
467
+ self << create_language_sql(name, opts).freeze
468
468
  end
469
469
 
470
470
  # Create a schema in the database. Arguments:
@@ -473,7 +473,7 @@ module Sequel
473
473
  # :if_not_exists :: Don't raise an error if the schema already exists (PostgreSQL 9.3+)
474
474
  # :owner :: The owner to set for the schema (defaults to current user if not specified)
475
475
  def create_schema(name, opts=OPTS)
476
- self << create_schema_sql(name, opts)
476
+ self << create_schema_sql(name, opts).freeze
477
477
  end
478
478
 
479
479
  # Support partitions of tables using the :partition_of option.
@@ -509,7 +509,7 @@ module Sequel
509
509
  # :replace :: Replace the trigger with the same name if it already exists (PostgreSQL 14+).
510
510
  # :when :: A filter to use for the trigger
511
511
  def create_trigger(table, name, function, opts=OPTS)
512
- self << create_trigger_sql(table, name, function, opts)
512
+ self << create_trigger_sql(table, name, function, opts).freeze
513
513
  end
514
514
 
515
515
  def database_type
@@ -542,7 +542,7 @@ module Sequel
542
542
  # default is plpgsql. Can be specified as a string or a symbol.
543
543
  def do(code, opts=OPTS)
544
544
  language = opts[:language]
545
- run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
545
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}".freeze
546
546
  end
547
547
 
548
548
  # Drops the function from the database. Arguments:
@@ -552,7 +552,7 @@ module Sequel
552
552
  # :cascade :: Drop other objects depending on this function.
553
553
  # :if_exists :: Don't raise an error if the function doesn't exist.
554
554
  def drop_function(name, opts=OPTS)
555
- self << drop_function_sql(name, opts)
555
+ self << drop_function_sql(name, opts).freeze
556
556
  end
557
557
 
558
558
  # Drops a procedural language from the database. Arguments:
@@ -561,7 +561,7 @@ module Sequel
561
561
  # :cascade :: Drop other objects depending on this function.
562
562
  # :if_exists :: Don't raise an error if the function doesn't exist.
563
563
  def drop_language(name, opts=OPTS)
564
- self << drop_language_sql(name, opts)
564
+ self << drop_language_sql(name, opts).freeze
565
565
  end
566
566
 
567
567
  # Drops a schema from the database. Arguments:
@@ -570,7 +570,7 @@ module Sequel
570
570
  # :cascade :: Drop all objects in this schema.
571
571
  # :if_exists :: Don't raise an error if the schema doesn't exist.
572
572
  def drop_schema(name, opts=OPTS)
573
- self << drop_schema_sql(name, opts)
573
+ self << drop_schema_sql(name, opts).freeze
574
574
  remove_all_cached_schemas
575
575
  end
576
576
 
@@ -581,7 +581,7 @@ module Sequel
581
581
  # :cascade :: Drop other objects depending on this function.
582
582
  # :if_exists :: Don't raise an error if the function doesn't exist.
583
583
  def drop_trigger(table, name, opts=OPTS)
584
- self << drop_trigger_sql(table, name, opts)
584
+ self << drop_trigger_sql(table, name, opts).freeze
585
585
  end
586
586
 
587
587
  # Return full foreign key information using the pg system tables, including
@@ -745,7 +745,7 @@ module Sequel
745
745
  # name :: Current name of the schema
746
746
  # opts :: New name for the schema
747
747
  def rename_schema(name, new_name)
748
- self << rename_schema_sql(name, new_name)
748
+ self << rename_schema_sql(name, new_name).freeze
749
749
  remove_all_cached_schemas
750
750
  end
751
751
 
@@ -756,7 +756,7 @@ module Sequel
756
756
  # DB.refresh_view(:items_view, concurrently: true)
757
757
  # # REFRESH MATERIALIZED VIEW CONCURRENTLY items_view
758
758
  def refresh_view(name, opts=OPTS)
759
- run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
759
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}".freeze
760
760
  end
761
761
 
762
762
  # Reset the primary key sequence for the given table, basing it on the
@@ -769,7 +769,7 @@ module Sequel
769
769
  table = Sequel.qualify(s, t) if s
770
770
 
771
771
  if server_version >= 100000
772
- seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
772
+ seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
773
773
  increment_by = :seqincrement
774
774
  min_value = :seqmin
775
775
  # :nocov:
@@ -784,7 +784,7 @@ module Sequel
784
784
  end
785
785
 
786
786
  def rollback_prepared_transaction(transaction_id, opts=OPTS)
787
- run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
787
+ run("ROLLBACK PREPARED #{literal(transaction_id)}".freeze, opts)
788
788
  end
789
789
 
790
790
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
@@ -1469,7 +1469,7 @@ module Sequel
1469
1469
 
1470
1470
  def column_references_add_period(cols)
1471
1471
  cols= cols.dup
1472
- cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}")
1472
+ cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}".freeze)
1473
1473
  cols
1474
1474
  end
1475
1475
 
@@ -195,7 +195,7 @@ module Sequel
195
195
 
196
196
  # Dataset used for parsing schema
197
197
  def _parse_pragma_ds(table_name, opts)
198
- metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
198
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)".freeze, input_identifier_meth(opts[:dataset]).call(table_name))
199
199
  end
200
200
 
201
201
  # Run all alter_table commands in a transaction. This is technically only
@@ -407,7 +407,7 @@ module Sequel
407
407
  def defined_columns_for(table)
408
408
  cols = parse_pragma(table, OPTS)
409
409
  cols.each do |c|
410
- c[:default] = LiteralString.new(c[:default]) if c[:default]
410
+ c[:default] = LiteralString.new(c[:default]).freeze if c[:default]
411
411
  c[:type] = c[:db_type]
412
412
  end
413
413
  cols
@@ -684,7 +684,7 @@ module Sequel
684
684
  # Load the PrettyTable class, needed for explain output
685
685
  Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
686
686
 
687
- ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
687
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}".freeze)
688
688
  rows = ds.all
689
689
  Sequel::PrettyTable.string(rows, ds.columns)
690
690
  end
@@ -388,7 +388,7 @@ module Sequel
388
388
  # SQLite uses a : before the name of the argument for named
389
389
  # arguments.
390
390
  def prepared_arg(k)
391
- LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
391
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}".freeze)
392
392
  end
393
393
  end
394
394
 
@@ -205,7 +205,7 @@ module Sequel
205
205
  private
206
206
 
207
207
  def prepared_arg(k)
208
- LiteralString.new("@#{k.to_s.gsub('.', '__')}")
208
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}".freeze)
209
209
  end
210
210
  end
211
211
 
@@ -91,14 +91,16 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
91
91
  # creates new connections to the database.
92
92
  #
93
93
  # If the :server option is provided, it should be a symbol or array of symbols,
94
- # and then the method will only disconnect connectsion from those specified shards.
94
+ # and then the method will only disconnect connections from those specified shards.
95
95
  def disconnect(opts=OPTS)
96
96
  (opts[:server] ? Array(opts[:server]) : sync{@servers.keys}).each do |server|
97
97
  raise Sequel::Error, "invalid server" unless queue = sync{@queues[server]}
98
+ nconns = 0
98
99
  while conn = available(queue, server)
100
+ nconns += 1
99
101
  disconnect_pool_connection(conn, server)
100
102
  end
101
- fill_queue(server)
103
+ fill_queue(server, nconns)
102
104
  end
103
105
  nil
104
106
  end
@@ -132,7 +134,7 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
132
134
  conn = nil
133
135
  disconnect_pool_connection(oconn, server) if oconn
134
136
  sync{@allocated[server].delete(t)}
135
- fill_queue(server)
137
+ fill_queue(server, 1)
136
138
  end
137
139
  raise
138
140
  ensure
@@ -250,11 +252,15 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
250
252
  # after disconnecting to potentially add new connections to the
251
253
  # pool, so the threads that are currently waiting for connections
252
254
  # do not timeout after the pool is no longer full.
253
- def fill_queue(server)
255
+ #
256
+ # nconns specifies the maximum number of connections to add, which should
257
+ # be the number of connections that were disconnected.
258
+ def fill_queue(server, nconns)
254
259
  queue = sync{@queues[server]}
255
- if queue.num_waiting > 0
260
+ if nconns > 0 && queue.num_waiting > 0
256
261
  Thread.new do
257
- while queue.num_waiting > 0 && (conn = try_make_new(server))
262
+ while nconns > 0 && queue.num_waiting > 0 && (conn = try_make_new(server))
263
+ nconns -= 1
258
264
  queue.push(conn)
259
265
  end
260
266
  end
@@ -303,7 +309,7 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
303
309
  ensure
304
310
  if to_disconnect
305
311
  to_disconnect.each{|conn| disconnect_pool_connection(conn, server)}
306
- fill_queue(server)
312
+ fill_queue(server, to_disconnect.size)
307
313
  end
308
314
  end
309
315
  end
@@ -59,10 +59,12 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
59
59
  # Once a connection is requested using #hold, the connection pool
60
60
  # creates new connections to the database.
61
61
  def disconnect(opts=OPTS)
62
+ nconns = 0
62
63
  while conn = available
64
+ nconns += 1
63
65
  disconnect_connection(conn)
64
66
  end
65
- fill_queue
67
+ fill_queue(nconns)
66
68
  nil
67
69
  end
68
70
 
@@ -94,7 +96,7 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
94
96
  conn = nil
95
97
  disconnect_connection(oconn) if oconn
96
98
  sync{@allocated.delete(t)}
97
- fill_queue
99
+ fill_queue(1)
98
100
  end
99
101
  raise
100
102
  ensure
@@ -156,10 +158,13 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
156
158
  # after disconnecting to potentially add new connections to the
157
159
  # pool, so the threads that are currently waiting for connections
158
160
  # do not timeout after the pool is no longer full.
159
- def fill_queue
160
- if @queue.num_waiting > 0
161
+ #
162
+ # nconns specifies the maximum number of connections to add, which should
163
+ # be the number of connections that were disconnected.
164
+ def fill_queue(nconns)
165
+ if nconns > 0 && @queue.num_waiting > 0
161
166
  Thread.new do
162
- while @queue.num_waiting > 0 && (conn = try_make_new)
167
+ while nconns > 0 && @queue.num_waiting > 0 && (conn = try_make_new)
163
168
  @queue.push(conn)
164
169
  end
165
170
  end
@@ -207,7 +212,7 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
207
212
  ensure
208
213
  if to_disconnect
209
214
  to_disconnect.each{|conn| disconnect_connection(conn)}
210
- fill_queue
215
+ fill_queue(to_disconnect.size)
211
216
  end
212
217
  end
213
218
  end
@@ -3,6 +3,33 @@
3
3
  module Sequel
4
4
  # The Schema module holds the schema generators.
5
5
  module Schema
6
+ module ColumnOptionMerger
7
+ private
8
+
9
+ # Merge given options into the column's default options. For backwards compatibility,
10
+ # the options take priority, but in cases where the option value overrides the argument
11
+ # value, and the values are different, we warn as this is likely to be an error in the
12
+ # code.
13
+ def _merge_column_options(defaults, opts)
14
+ defaults.merge!(opts) do |k, defv, v|
15
+ unless defv == v
16
+ # :nocov:
17
+ if RUBY_VERSION >= "3.2"
18
+ # :nocov:
19
+ caller_loc = Thread.each_caller_location do |loc|
20
+ break loc unless loc.path == __FILE__
21
+ end
22
+ caller_loc &&= "#{caller_loc.path}:#{caller_loc.lineno}: "
23
+ end
24
+ warn("#{caller_loc}#{k.inspect} option value (#{v.inspect}) overrides argument value (#{defv.inspect})")
25
+ end
26
+
27
+ v
28
+ end
29
+ end
30
+ end
31
+ private_constant :ColumnOptionMerger
32
+
6
33
  # Schema::CreateTableGenerator is an internal class that the user is not expected
7
34
  # to instantiate directly. Instances are created by Database#create_table.
8
35
  # It is used to specify table creation parameters. It takes a Database
@@ -17,6 +44,8 @@ module Sequel
17
44
  # For more information on Sequel's support for schema modification, see
18
45
  # the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
19
46
  class CreateTableGenerator
47
+ include ColumnOptionMerger
48
+
20
49
  # Classes specifying generic types that Sequel will convert to database-specific types.
21
50
  GENERIC_TYPES=%w'String Integer Float Numeric BigDecimal Date DateTime Time File TrueClass FalseClass'.freeze
22
51
 
@@ -173,13 +202,13 @@ module Sequel
173
202
  # :clustered :: When using :primary_key or :unique, marks the primary key or unique
174
203
  # constraint as CLUSTERED (if true), or NONCLUSTERED (if false).
175
204
  def column(name, type, opts = OPTS)
176
- columns << {:name => name, :type => type}.merge!(opts)
205
+ columns << _merge_column_options({:name => name, :type => type}, opts)
177
206
  if index_opts = opts[:index]
178
207
  index(name, index_opts.is_a?(Hash) ? index_opts : OPTS)
179
208
  end
180
209
  nil
181
210
  end
182
-
211
+
183
212
  # Adds a named CHECK constraint (or unnamed if name is nil),
184
213
  # with the given block or args. To provide options for the constraint, pass
185
214
  # a hash as the first argument.
@@ -246,7 +275,7 @@ module Sequel
246
275
  opts.merge(:table=>table)
247
276
  end
248
277
  return composite_foreign_key(name, opts) if name.is_a?(Array)
249
- column(name, Integer, opts)
278
+ column(name, opts.fetch(:type, Integer), opts)
250
279
  end
251
280
 
252
281
  # Add a full text index on the given columns.
@@ -429,6 +458,8 @@ module Sequel
429
458
  # For more information on Sequel's support for schema modification, see
430
459
  # the {"Schema Modification" guide}[link:files/doc/schema_modification_rdoc.html].
431
460
  class AlterTableGenerator
461
+ include ColumnOptionMerger
462
+
432
463
  # An array of operations to perform
433
464
  attr_reader :operations
434
465
 
@@ -454,7 +485,7 @@ module Sequel
454
485
  # :after :: The name of an existing column that the new column should be positioned after
455
486
  # :first :: Create this new column before all other existing columns
456
487
  def add_column(name, type, opts = OPTS)
457
- op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
488
+ op = _merge_column_options({:op => :add_column, :name => name, :type => type}, opts)
458
489
  index_opts = op.delete(:index)
459
490
  @operations << op
460
491
  add_index(name, index_opts.is_a?(Hash) ? index_opts : OPTS) if index_opts
@@ -519,7 +550,7 @@ module Sequel
519
550
  # sense when using an array of columns.
520
551
  def add_foreign_key(name, table, opts = OPTS)
521
552
  return add_composite_foreign_key(name, table, opts) if name.is_a?(Array)
522
- add_column(name, Integer, {:table=>table}.merge!(opts))
553
+ add_column(name, opts.fetch(:type, Integer), {:table=>table}.merge!(opts))
523
554
  end
524
555
 
525
556
  # Add a full text index on the given columns.
@@ -819,7 +819,7 @@ module Sequel
819
819
  # SELECT sql statement.
820
820
  def create_table_as(name, sql, options)
821
821
  sql = sql.sql if sql.is_a?(Sequel::Dataset)
822
- run(create_table_as_sql(name, sql, options))
822
+ run(create_table_as_sql(name, sql, options).freeze)
823
823
  end
824
824
 
825
825
  # SQL statement for creating a table from the result of a SELECT statement.
@@ -117,6 +117,8 @@ module Sequel
117
117
  frags << final_sql
118
118
  prepared_sql << final_sql
119
119
 
120
+ frags.each(&:freeze)
121
+ frags.freeze
120
122
  [prepared_sql, frags]
121
123
  end
122
124
 
@@ -125,6 +127,7 @@ module Sequel
125
127
  @argn = -1
126
128
  @args = []
127
129
  ds = yield self, dataset
130
+ ds.opts[:sql].freeze
128
131
  sql = ds.clone(:placeholder_literalizer=>self).sql
129
132
 
130
133
  last_offset = 0
@@ -54,7 +54,7 @@ module Sequel
54
54
 
55
55
  # Set the bind arguments based on the hash and call super.
56
56
  def call(bind_vars=OPTS, &block)
57
- sql = prepared_sql
57
+ sql = prepared_sql.freeze
58
58
  prepared_args.freeze
59
59
  ps = bind(bind_vars)
60
60
  ps.clone(:bind_arguments=>ps.map_to_prepared_args(ps.opts[:bind_vars]), :sql=>sql, :prepared_sql=>sql).run(&block)
@@ -223,7 +223,7 @@ module Sequel
223
223
  # with the prepared SQL, to ensure the prepared_sql_type is respected.
224
224
  def force_prepared_sql
225
225
  if prepared_sql_type != prepared_type
226
- with_sql(prepared_sql)
226
+ with_sql(prepared_sql.freeze)
227
227
  else
228
228
  self
229
229
  end
@@ -287,7 +287,7 @@ module Sequel
287
287
 
288
288
  def run(&block)
289
289
  if @opts[:prepared_sql_frags]
290
- sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
290
+ sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false)).freeze
291
291
  clone(:prepared_sql_frags=>nil, :sql=>sql, :prepared_sql=>sql).run(&block)
292
292
  else
293
293
  super
@@ -320,6 +320,9 @@ module Sequel
320
320
  end
321
321
 
322
322
  prepared_args.freeze
323
+ frags.freeze
324
+ frags.each(&:freeze)
325
+ prepared_sql.freeze
323
326
  clone(:prepared_sql_frags=>frags, :prepared_sql=>prepared_sql, :sql=>prepared_sql)
324
327
  end
325
328
 
@@ -409,7 +412,7 @@ module Sequel
409
412
  ps = ps.with_extend(EmulatePreparedStatementMethods)
410
413
  ps.send(:emulated_prepared_statement, type, name, values)
411
414
  else
412
- sql = ps.prepared_sql
415
+ sql = ps.prepared_sql.freeze
413
416
  ps.prepared_args.freeze
414
417
  ps.clone(:prepared_sql=>sql, :sql=>sql)
415
418
  end
@@ -1301,7 +1301,7 @@ module Sequel
1301
1301
  # * truncate (if a TRUNCATE statement, with no arguments)
1302
1302
  def with_sql(sql, *args)
1303
1303
  if sql.is_a?(Symbol)
1304
- sql = public_send(sql, *args)
1304
+ sql = public_send(sql, *args).freeze
1305
1305
  else
1306
1306
  sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
1307
1307
  end
@@ -1474,7 +1474,10 @@ module Sequel
1474
1474
  def default_join_table_qualification
1475
1475
  :symbol
1476
1476
  end
1477
-
1477
+
1478
+ PAREN_WRAPPER = ["(".freeze, ")".freeze].freeze
1479
+ private_constant :PAREN_WRAPPER
1480
+
1478
1481
  # SQL expression object based on the expr type. See +where+.
1479
1482
  def filter_expr(expr = nil, &block)
1480
1483
  expr = nil if expr == EMPTY_ARRAY
@@ -1495,7 +1498,7 @@ module Sequel
1495
1498
  raise Error, "Invalid filter expression: #{expr.inspect}"
1496
1499
  end
1497
1500
  when LiteralString
1498
- LiteralString.new("(#{expr})")
1501
+ SQL::PlaceholderLiteralString.new(PAREN_WRAPPER, [expr])
1499
1502
  when Numeric, SQL::NumericExpression, SQL::StringExpression, Proc, String, Set
1500
1503
  raise Error, "Invalid filter expression: #{expr.inspect}"
1501
1504
  when TrueClass, FalseClass
@@ -53,7 +53,7 @@ module Sequel
53
53
  when String
54
54
  case v
55
55
  when LiteralString
56
- sql << v
56
+ literal_literal_string_append(sql, v)
57
57
  when SQL::Blob
58
58
  literal_blob_append(sql, v)
59
59
  else
@@ -1424,6 +1424,11 @@ module Sequel
1424
1424
  v.to_s
1425
1425
  end
1426
1426
 
1427
+ # Append string to SQL string.
1428
+ def literal_literal_string_append(sql, v)
1429
+ sql << v
1430
+ end
1431
+
1427
1432
  # SQL fragment for nil
1428
1433
  def literal_nil
1429
1434
  "NULL"
@@ -30,9 +30,9 @@ module Sequel
30
30
  # # GRANT SELECT ON "table" TO "user"
31
31
  def run
32
32
  if server = @opts[:server]
33
- db.run(sql, :server=>server)
33
+ db.run(sql.freeze, :server=>server)
34
34
  else
35
- db.run(sql)
35
+ db.run(sql.freeze)
36
36
  end
37
37
  end
38
38
  end
@@ -82,12 +82,12 @@ module Sequel
82
82
  DURATION_UNITS = [:years, :months, :days, :hours, :minutes, :seconds].freeze
83
83
  DEF_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s.freeze}).freeze
84
84
  POSTGRES_DURATION_UNITS = DURATION_UNITS.zip([:years, :months, :days, :hours, :mins, :secs].map{|s| s.to_s.freeze}).freeze
85
- MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
86
- MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
85
+ MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1].freeze).freeze}).freeze
86
+ MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1].freeze).freeze}).freeze
87
87
  H2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s[0...-1].freeze}).freeze
88
- DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
88
+ DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}".freeze).freeze}).freeze
89
89
  ACCESS_DURATION_UNITS = DURATION_UNITS.zip(%w'yyyy m d h n s'.map(&:freeze)).freeze
90
- DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
90
+ DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.freeze).freeze}).freeze
91
91
 
92
92
  # Append the SQL fragment for the DateAdd expression to the SQL query.
93
93
  def date_add_sql_append(sql, da)
@@ -107,7 +107,7 @@ module Sequel
107
107
  placeholder = []
108
108
  vals = []
109
109
  each_valid_interval_unit(h, POSTGRES_DURATION_UNITS) do |value, sql_unit|
110
- placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
110
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := ".freeze
111
111
  vals << value
112
112
  end
113
113
  interval = Sequel.function(:make_interval, Sequel.lit(placeholder, *vals)) unless vals.empty?
@@ -156,7 +156,7 @@ module Sequel
156
156
  expr = Sequel.cast_string(expr) + ' 00:00:00'
157
157
  end
158
158
  each_valid_interval_unit(h, DERBY_DURATION_UNITS) do |value, sql_unit|
159
- expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ", ", timestamp(", "))}"], value, expr)
159
+ expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ".freeze, ", timestamp(", "))}"], value, expr)
160
160
  end
161
161
  when :oracle
162
162
  each_valid_interval_unit(h, MYSQL_DURATION_UNITS) do |value, sql_unit|
@@ -0,0 +1,131 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The lit_require_frozen extension disallows the use of unfrozen strings
4
+ # as literal strings in database and dataset methods. If you try to use an
5
+ # unfrozen string as a literal string for a dataset using this extension,
6
+ # an exception will be raised.
7
+ #
8
+ # While this works for all Ruby versions, it is designed for use on Ruby 3+
9
+ # where all files are using the frozen-string-literal magic comment. In this
10
+ # case, uninterpolated literal strings are frozen, but interpolated strings
11
+ # are not frozen. This allows you to catch potentially dangerous code:
12
+ #
13
+ # # Probably safe, no exception raised
14
+ # DB["SELECT * FROM t WHERE c > :v", v: user_provided_string)
15
+ #
16
+ # # Potentially unsafe, raises Sequel::LitRequireFrozen::Error
17
+ # DB["SELECT * FROM t WHERE c > '#{user_provided_string}'"]
18
+ #
19
+ # The assumption made is that a frozen string is unlikely to contain unsafe
20
+ # input, while an unfrozen string has potentially been interpolated and may
21
+ # contain unsafe input.
22
+ #
23
+ # This disallows the the following cases:
24
+ #
25
+ # * Sequel::LiteralString instances that are unfrozen and are not based on a
26
+ # frozen string
27
+ # * Sequel::SQL::PlaceholderLiteralString instances when the placeholder string
28
+ # is not frozen
29
+ # * Unfrozen strings passed to Database#<< or #[] or Dataset#with_sql
30
+ #
31
+ # To use this extension, load it into the database:
32
+ #
33
+ # DB.extension :lit_require_frozen
34
+ #
35
+ # It can also be loaded into individual datasets:
36
+ #
37
+ # ds = DB[:t].extension(:lit_require_frozen)
38
+ #
39
+ # Assuming you have good test coverage, it is recommended to only load
40
+ # this extension when testing.
41
+ #
42
+ # Related module: Sequel::LitRequireFrozen
43
+
44
+ #
45
+ module Sequel
46
+ class LiteralString
47
+ # The string used when creating the literal string (first argument to
48
+ # Sequel::LiteralString.new). This may be nil if no string was provided,
49
+ # or if the litral string was created before this extension was required.
50
+ attr_reader :source
51
+
52
+ def initialize(*a)
53
+ @source = a.first
54
+ super
55
+ end
56
+ # :nocov:
57
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
58
+ # :nocov:
59
+ end
60
+
61
+ module LitRequireFrozen
62
+ # Error class raised for using unfrozen literal string.
63
+ class Error < Sequel::Error
64
+ end
65
+
66
+ module DatabaseMethods
67
+ def self.extended(db)
68
+ db.extend_datasets(DatasetMethods)
69
+ end
70
+
71
+ # Check given SQL is frozen before running it.
72
+ def run(sql, opts=OPTS)
73
+ @default_dataset.with_sql(sql)
74
+ super
75
+ end
76
+ end
77
+
78
+ module DatasetMethods
79
+ # Check given SQL is not an unfrozen string.
80
+ def with_sql(sql, *args)
81
+ _check_unfrozen_literal_string(sql)
82
+ super
83
+ end
84
+
85
+ # Check that placeholder string is frozen (or all entries
86
+ # in placeholder array are frozen).
87
+ def placeholder_literal_string_sql_append(sql, pls)
88
+ str = pls.str
89
+
90
+ if str.is_a?(Array)
91
+ str.each do |s|
92
+ _check_unfrozen_literal_string(s)
93
+ end
94
+ else
95
+ _check_unfrozen_literal_string(str)
96
+ end
97
+
98
+ super
99
+ end
100
+
101
+ private
102
+
103
+ # Base method that other methods used to check for whether a string should be allowed
104
+ # as literal SQL. Allows non-strings as well as frozen strings.
105
+ def _check_unfrozen_literal_string(str)
106
+ return if !str.is_a?(String) || str.frozen?
107
+
108
+ if str.is_a?(LiteralString)
109
+ _check_unfrozen_literal_string(str.source)
110
+ else
111
+ raise Error, "cannot treat unfrozen string as literal SQL: #{str.inspect}"
112
+ end
113
+ end
114
+
115
+ # Check literal strings appended to SQL.
116
+ def literal_literal_string_append(sql, v)
117
+ _check_unfrozen_literal_string(v)
118
+ super
119
+ end
120
+
121
+ # Check static SQL is not frozen.
122
+ def static_sql(sql)
123
+ _check_unfrozen_literal_string(sql)
124
+ super
125
+ end
126
+ end
127
+ end
128
+
129
+ Dataset.register_extension(:lit_require_frozen, LitRequireFrozen::DatasetMethods)
130
+ Database.register_extension(:lit_require_frozen, LitRequireFrozen::DatabaseMethods)
131
+ end
@@ -186,22 +186,19 @@ module Sequel
186
186
  # and returns a new block that reverses the actions taken by
187
187
  # the given block.
188
188
  def reverse(&block)
189
- begin
190
- instance_exec(&block)
191
- rescue
192
- just_raise = true
193
- end
194
- if just_raise
195
- Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
196
- else
197
- actions = @actions.reverse
198
- Proc.new do
199
- actions.each do |a|
200
- pr = a.last.is_a?(Proc) ? a.pop : nil
201
- # Allow calling private methods as the reversing methods are private
202
- send(*a, &pr)
203
- end
204
- end
189
+ instance_exec(&block)
190
+ rescue NoMethodError => e
191
+ Proc.new{raise Sequel::Error, "irreversible migration method \"#{e.name}\" used in #{block.source_location.first}, you may need to write your own down method"}
192
+ rescue => e
193
+ Proc.new{raise Sequel::Error, "unable to reverse migration due to #{e.class} in #{block.source_location.first}, you may need to write your own down method"}
194
+ else
195
+ actions = @actions.reverse
196
+ Proc.new do
197
+ actions.each do |a|
198
+ pr = a.last.is_a?(Proc) ? a.pop : nil
199
+ # Allow calling private methods as the reversing methods are private
200
+ send(*a, &pr)
201
+ end
205
202
  end
206
203
  end
207
204
 
@@ -270,7 +267,7 @@ module Sequel
270
267
  end
271
268
 
272
269
  def add_primary_key(*args)
273
- raise if args.first.is_a?(Array)
270
+ super if args.first.is_a?(Array)
274
271
  @actions << [:drop_column, args.first]
275
272
  end
276
273
 
@@ -127,7 +127,7 @@ module Sequel
127
127
 
128
128
  # Run the SQL on the database, reparsing the enum labels after it is run.
129
129
  def _process_enum_change_sql(sql)
130
- run(sql)
130
+ run(sql.freeze)
131
131
  parse_enum_labels
132
132
  nil
133
133
  end
@@ -320,7 +320,7 @@ module Sequel
320
320
  end
321
321
  loader.append_sql(sql, *k)
322
322
  end
323
- objects.concat(ds.with_sql(sql).to_a)
323
+ objects.concat(ds.with_sql(sql.freeze).to_a)
324
324
  end
325
325
  ds = ds.eager(cascade) if cascade
326
326
  ds.send(:post_load, objects)
@@ -234,8 +234,11 @@ module Sequel
234
234
  iv.delete(column)
235
235
  end
236
236
  else
237
- check_missing_initial_value(column)
238
- iv[column] = get_column_value(column)
237
+ if db_schema[column]
238
+ check_missing_initial_value(column)
239
+ iv[column] = get_column_value(column)
240
+ end
241
+
239
242
  super
240
243
  end
241
244
  end
@@ -55,7 +55,7 @@ module Sequel
55
55
  def _update_without_checking(columns)
56
56
  ds = _update_dataset
57
57
  lc = model.lock_column
58
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
58
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
59
59
  values[lc] = rows.first[lc] unless rows.empty?
60
60
  rows.length
61
61
  end
@@ -99,7 +99,7 @@ module Sequel
99
99
  # Add an RETURNING clause to fetch the updated xmin when updating the row.
100
100
  def _update_without_checking(columns)
101
101
  ds = _update_dataset
102
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
102
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
103
103
  values[:xmin] = rows.first[:xmin] unless rows.empty?
104
104
  rows.length
105
105
  end
@@ -30,6 +30,15 @@ module Sequel
30
30
  # Otherwise, it is possible that the default column accessors will take
31
31
  # precedence.
32
32
  #
33
+ # Note that use of an unsafe serialization method can result in an attack vector
34
+ # (potentially allowing remote code execution) if an attacker has the ability to
35
+ # store data directly in the underlying column. This would affect the marshal
36
+ # serialization format, and on older versions of Ruby, potentially the yaml and
37
+ # json serialization formats as well. It can also affect custom formats. You
38
+ # should ensure that attackers do not have access to store data directly in the
39
+ # underlying column when using this plugin (especially when using an unsafe
40
+ # serialization method).
41
+ #
33
42
  # == Example
34
43
  #
35
44
  # # Require json if you plan to use it, as the plugin doesn't require it for you.
@@ -97,7 +106,7 @@ module Sequel
97
106
  register_format(:marshal, lambda{|v| [Marshal.dump(v)].pack('m')},
98
107
  lambda do |v|
99
108
  # Handle unpacked marshalled data for backwards compat
100
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
109
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
101
110
  Marshal.load(v)
102
111
  end)
103
112
  register_format(:yaml, :to_yaml.to_proc, lambda{|s| YAML.load(s)})
@@ -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 = 102
9
+ MINOR = 103
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.102.0
4
+ version: 5.103.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -241,6 +241,7 @@ files:
241
241
  - lib/sequel/extensions/inflector.rb
242
242
  - lib/sequel/extensions/integer64.rb
243
243
  - lib/sequel/extensions/is_distinct_from.rb
244
+ - lib/sequel/extensions/lit_require_frozen.rb
244
245
  - lib/sequel/extensions/looser_typecasting.rb
245
246
  - lib/sequel/extensions/migration.rb
246
247
  - lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb