Bump sequel from 5.101.0 to 5.104.0#921
Closed
dependabot[bot] wants to merge 1 commit intomainfrom
Closed
Conversation
Bumps [sequel](https://github.com/jeremyevans/sequel) from 5.101.0 to 5.104.0. - [Changelog](https://github.com/jeremyevans/sequel/blob/master/CHANGELOG) - [Commits](jeremyevans/sequel@5.101.0...5.104.0) --- updated-dependencies: - dependency-name: sequel dependency-version: 5.104.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
1 similar comment
Contributor
Contributor
gem compare bigdecimal 4.0.1 4.1.2Compared versions: ["4.0.1", "4.1.2"]
DIFFERENT require_paths:
4.0.1: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.0.1", "lib"]
4.1.2: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.1.2", "lib"]
DIFFERENT required_ruby_version:
4.0.1: >= 2.5.0
4.1.2: >= 2.6.0
DIFFERENT version:
4.0.1: 4.0.1
4.1.2: 4.1.2
DIFFERENT files:
4.0.1->4.1.2:
* Added:
ext/bigdecimal/div.h +192/-0
ext/bigdecimal/ntt.h +191/-0
sig/big_decimal.rbs +1502/-0
sig/big_decimal_util.rbs +158/-0
sig/big_math.rbs +423/-0
* Changed:
bigdecimal.gemspec +6/-1
ext/bigdecimal/bigdecimal.c +263/-262
ext/bigdecimal/bigdecimal.h +43/-37
ext/bigdecimal/extconf.rb +5/-2
ext/bigdecimal/missing.h +4/-2
ext/bigdecimal/missing/dtoa.c +183/-136
lib/bigdecimal.rb +104/-60
lib/bigdecimal/math.rb +103/-124
lib/bigdecimal/util.rb +1/-1
sample/linear.rb +73/-37
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/nlsolve.rb +47/-30
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/pi.rb +2/-7
(!) Shebang probably lost: #!/usr/local/bin/ruby |
1 similar comment
Contributor
gem compare bigdecimal 4.0.1 4.1.2Compared versions: ["4.0.1", "4.1.2"]
DIFFERENT require_paths:
4.0.1: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.0.1", "lib"]
4.1.2: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.1.2", "lib"]
DIFFERENT required_ruby_version:
4.0.1: >= 2.5.0
4.1.2: >= 2.6.0
DIFFERENT version:
4.0.1: 4.0.1
4.1.2: 4.1.2
DIFFERENT files:
4.0.1->4.1.2:
* Added:
ext/bigdecimal/div.h +192/-0
ext/bigdecimal/ntt.h +191/-0
sig/big_decimal.rbs +1502/-0
sig/big_decimal_util.rbs +158/-0
sig/big_math.rbs +423/-0
* Changed:
bigdecimal.gemspec +6/-1
ext/bigdecimal/bigdecimal.c +263/-262
ext/bigdecimal/bigdecimal.h +43/-37
ext/bigdecimal/extconf.rb +5/-2
ext/bigdecimal/missing.h +4/-2
ext/bigdecimal/missing/dtoa.c +183/-136
lib/bigdecimal.rb +104/-60
lib/bigdecimal/math.rb +103/-124
lib/bigdecimal/util.rb +1/-1
sample/linear.rb +73/-37
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/nlsolve.rb +47/-30
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/pi.rb +2/-7
(!) Shebang probably lost: #!/usr/local/bin/ruby |
Contributor
|
Contributor
Contributor
|
Contributor
gem compare sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT version:
5.101.0: 5.101.0
5.104.0: 5.104.0
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb +151/-0
lib/sequel/extensions/lit_require_frozen.rb +131/-0
* Changed:
MIT-LICENSE +1/-1
lib/sequel/adapters/jdbc/derby.rb +2/-0
lib/sequel/adapters/jdbc/h2.rb +2/-2
lib/sequel/adapters/postgres.rb +1/-1
lib/sequel/adapters/shared/mssql.rb +3/-3
lib/sequel/adapters/shared/mysql.rb +5/-4
lib/sequel/adapters/shared/postgres.rb +16/-16
lib/sequel/adapters/shared/sqlite.rb +3/-3
lib/sequel/adapters/sqlite.rb +1/-1
lib/sequel/adapters/tinytds.rb +1/-1
lib/sequel/connection_pool.rb +6/-0
lib/sequel/connection_pool/sharded_timed_queue.rb +36/-11
lib/sequel/connection_pool/timed_queue.rb +27/-9
lib/sequel/database/schema_generator.rb +36/-5
lib/sequel/database/schema_methods.rb +1/-1
lib/sequel/dataset/placeholder_literalizer.rb +3/-0
lib/sequel/dataset/prepared_statements.rb +7/-4
lib/sequel/dataset/query.rb +6/-3
lib/sequel/dataset/sql.rb +6/-1
lib/sequel/extensions/connection_expiration.rb +1/-1
lib/sequel/extensions/connection_validator.rb +1/-1
lib/sequel/extensions/dataset_run.rb +2/-2
lib/sequel/extensions/date_arithmetic.rb +6/-6
lib/sequel/extensions/migration.rb +14/-17
lib/sequel/extensions/pg_enum.rb +1/-1
lib/sequel/model/associations.rb +180/-6
lib/sequel/plugins/dataset_associations.rb +20/-1
lib/sequel/plugins/dirty.rb +5/-2
lib/sequel/plugins/many_through_many.rb +21/-0
lib/sequel/plugins/mssql_optimistic_locking.rb +1/-1
lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1/-1
lib/sequel/plugins/serialization.rb +10/-1
lib/sequel/version.rb +1/-1
DIFFERENT extra_rdoc_files:
5.101.0->5.104.0:
* Changed:
MIT-LICENSE +1/-1 |
1 similar comment
Contributor
gem compare sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT version:
5.101.0: 5.101.0
5.104.0: 5.104.0
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb +151/-0
lib/sequel/extensions/lit_require_frozen.rb +131/-0
* Changed:
MIT-LICENSE +1/-1
lib/sequel/adapters/jdbc/derby.rb +2/-0
lib/sequel/adapters/jdbc/h2.rb +2/-2
lib/sequel/adapters/postgres.rb +1/-1
lib/sequel/adapters/shared/mssql.rb +3/-3
lib/sequel/adapters/shared/mysql.rb +5/-4
lib/sequel/adapters/shared/postgres.rb +16/-16
lib/sequel/adapters/shared/sqlite.rb +3/-3
lib/sequel/adapters/sqlite.rb +1/-1
lib/sequel/adapters/tinytds.rb +1/-1
lib/sequel/connection_pool.rb +6/-0
lib/sequel/connection_pool/sharded_timed_queue.rb +36/-11
lib/sequel/connection_pool/timed_queue.rb +27/-9
lib/sequel/database/schema_generator.rb +36/-5
lib/sequel/database/schema_methods.rb +1/-1
lib/sequel/dataset/placeholder_literalizer.rb +3/-0
lib/sequel/dataset/prepared_statements.rb +7/-4
lib/sequel/dataset/query.rb +6/-3
lib/sequel/dataset/sql.rb +6/-1
lib/sequel/extensions/connection_expiration.rb +1/-1
lib/sequel/extensions/connection_validator.rb +1/-1
lib/sequel/extensions/dataset_run.rb +2/-2
lib/sequel/extensions/date_arithmetic.rb +6/-6
lib/sequel/extensions/migration.rb +14/-17
lib/sequel/extensions/pg_enum.rb +1/-1
lib/sequel/model/associations.rb +180/-6
lib/sequel/plugins/dataset_associations.rb +20/-1
lib/sequel/plugins/dirty.rb +5/-2
lib/sequel/plugins/many_through_many.rb +21/-0
lib/sequel/plugins/mssql_optimistic_locking.rb +1/-1
lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1/-1
lib/sequel/plugins/serialization.rb +10/-1
lib/sequel/version.rb +1/-1
DIFFERENT extra_rdoc_files:
5.101.0->5.104.0:
* Changed:
MIT-LICENSE +1/-1 |
Contributor
1 similar comment
Contributor
Contributor
gem compare bigdecimal 4.0.1 4.1.2Compared versions: ["4.0.1", "4.1.2"]
DIFFERENT require_paths:
4.0.1: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.0.1", "lib"]
4.1.2: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.1.2", "lib"]
DIFFERENT required_ruby_version:
4.0.1: >= 2.5.0
4.1.2: >= 2.6.0
DIFFERENT version:
4.0.1: 4.0.1
4.1.2: 4.1.2
DIFFERENT files:
4.0.1->4.1.2:
* Added:
ext/bigdecimal/div.h +192/-0
ext/bigdecimal/ntt.h +191/-0
sig/big_decimal.rbs +1502/-0
sig/big_decimal_util.rbs +158/-0
sig/big_math.rbs +423/-0
* Changed:
bigdecimal.gemspec +6/-1
ext/bigdecimal/bigdecimal.c +263/-262
ext/bigdecimal/bigdecimal.h +43/-37
ext/bigdecimal/extconf.rb +5/-2
ext/bigdecimal/missing.h +4/-2
ext/bigdecimal/missing/dtoa.c +183/-136
lib/bigdecimal.rb +104/-60
lib/bigdecimal/math.rb +103/-124
lib/bigdecimal/util.rb +1/-1
sample/linear.rb +73/-37
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/nlsolve.rb +47/-30
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/pi.rb +2/-7
(!) Shebang probably lost: #!/usr/local/bin/ruby |
Contributor
gem compare --diff sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb
--- /tmp/20260504-1933-b1sazu 2026-05-04 02:36:11.455864284 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/connection_checkout_event_callback.rb 2026-05-04 02:36:11.432863773 +0000
@@ -0,0 +1,151 @@
+# frozen-string-literal: true
+#
+# The connection_checkout_event_callback extension modifies a database's
+# connection pool to allow for a checkout event callback. This callback is
+# called with the following arguments:
+#
+# :immediately_available :: Connection immediately available and returned
+# :not_immediately_available :: Connection not immediately available
+# :new_connection :: New connection created and returned
+# Float :: Number of seconds waiting to acquire a connection
+#
+# This is a low-level extension that allows for building telemetry
+# information. It doesn't implement any telemetry reporting itself. The
+# main reason for recording this information is to use it to determine the
+# appropriate size for the connection pool. Having too large a connection
+# pool can waste resources, while having too small a connection pool can
+# result in substantial time to check out a connection. In general, you
+# want to use as small a pool as possible while keeping the time to
+# checkout a connection low.
+#
+# To use the connection checkout event callback, you must first load the
+# extension:
+#
+# DB.extension(:connection_checkout_event_callback)
+#
+# By default, an empty proc is used as the callback so that loading the
+# support doesn't break anything. If you are using the extension, you
+# should set the callback at some point during application startup:
+#
+# DB.pool.on_checkout_event = proc do |event|
+# # ...
+# end
+#
+# When using the sharded connection pool, the callback is also
+# passed a second argument, the requested server shard (generally a
+# symbol), allowing for collection of per-shard telemetry:
+#
+# DB.pool.on_checkout_event = proc do |event, server|
+# # ...
+# end
+#
+# Note that the callback may be called currently by multiple threads.
+# You should use some form of concurrency control inside the callback,
+# such as a mutex or queue.
+#
+# Below is a brief example of usage to determine the percentage of
+# connection requests where a connection was immediately available:
+#
+# mutex = Mutex.new
+# total = immediates = 0
+#
+# DB.pool.on_checkout_event = proc do |event|
+# case event
+# when :immediately_available
+# mutex.synchronize do
+# total += 1
+# immediates += 1
+# end
+# when :not_immediately_available
+# mutex.synchronize do
+# total += 1
+# end
+# end
+# end
+#
+# immediate_percentage = lambda do
+# mutex.synchronize do
+# 100.0 * immediates / total
+# end
+# end
+#
+# Note that this extension only works with the timed_queue
+# and sharded_timed_queue connection pools (the default
+# connection pools when using Ruby 3.2+).
+#
+# Related modules: Sequel::ConnectionCheckoutEventCallbacks::TimedQueue,
+# Sequel::ConnectionCheckoutEventCallbacks::ShardedTimedQueue
+
+#
+module Sequel
+ module ConnectionCheckoutEventCallbacks
+ module TimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available)
+ conn
+ end
+
+ def try_make_new
+ conn = super
+ @on_checkout_event.call(:new_connection) if conn
+ conn
+ end
+
+ def wait_until_available
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer))
+ conn
+ end
+ end
+
+ module ShardedTimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available(queue, server)
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available, server)
+ conn
+ end
+
+ def try_make_new(server)
+ conn = super
+ @on_checkout_event.call(:new_connection, server) if conn
+ conn
+ end
+
+ def wait_until_available(queue, server)
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer), server)
+ conn
+ end
+ end
+ end
+
+ default_callback = proc{}
+
+ Database.register_extension(:connection_checkout_event_callback) do |db|
+ pool = db.pool
+
+ case pool.pool_type
+ when :timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::TimedQueue)
+ when :sharded_timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::ShardedTimedQueue)
+ else
+ raise Error, "the connection_checkout_event_callback extension is only supported when using a timed_queue connection pool"
+ end
+
+ pool.on_checkout_event ||= default_callback
+ end
+end
lib/sequel/extensions/lit_require_frozen.rb
--- /tmp/20260504-1933-fkhhey 2026-05-04 02:36:11.458864350 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/lit_require_frozen.rb 2026-05-04 02:36:11.435863839 +0000
@@ -0,0 +1,131 @@
+# frozen-string-literal: true
+#
+# The lit_require_frozen extension disallows the use of unfrozen strings
+# as literal strings in database and dataset methods. If you try to use an
+# unfrozen string as a literal string for a dataset using this extension,
+# an exception will be raised.
+#
+# While this works for all Ruby versions, it is designed for use on Ruby 3+
+# where all files are using the frozen-string-literal magic comment. In this
+# case, uninterpolated literal strings are frozen, but interpolated strings
+# are not frozen. This allows you to catch potentially dangerous code:
+#
+# # Probably safe, no exception raised
+# DB["SELECT * FROM t WHERE c > :v", v: user_provided_string)
+#
+# # Potentially unsafe, raises Sequel::LitRequireFrozen::Error
+# DB["SELECT * FROM t WHERE c > '#{user_provided_string}'"]
+#
+# The assumption made is that a frozen string is unlikely to contain unsafe
+# input, while an unfrozen string has potentially been interpolated and may
+# contain unsafe input.
+#
+# This disallows the the following cases:
+#
+# * Sequel::LiteralString instances that are unfrozen and are not based on a
+# frozen string
+# * Sequel::SQL::PlaceholderLiteralString instances when the placeholder string
+# is not frozen
+# * Unfrozen strings passed to Database#<< or #[] or Dataset#with_sql
+#
+# To use this extension, load it into the database:
+#
+# DB.extension :lit_require_frozen
+#
+# It can also be loaded into individual datasets:
+#
+# ds = DB[:t].extension(:lit_require_frozen)
+#
+# Assuming you have good test coverage, it is recommended to only load
+# this extension when testing.
+#
+# Related module: Sequel::LitRequireFrozen
+
+#
+module Sequel
+ class LiteralString
+ # The string used when creating the literal string (first argument to
+ # Sequel::LiteralString.new). This may be nil if no string was provided,
+ # or if the litral string was created before this extension was required.
+ attr_reader :source
+
+ def initialize(*a)
+ @source = a.first
+ super
+ end
+ # :nocov:
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
+ # :nocov:
+ end
+
+ module LitRequireFrozen
+ # Error class raised for using unfrozen literal string.
+ class Error < Sequel::Error
+ end
+
+ module DatabaseMethods
+ def self.extended(db)
+ db.extend_datasets(DatasetMethods)
+ end
+
+ # Check given SQL is frozen before running it.
+ def run(sql, opts=OPTS)
+ @default_dataset.with_sql(sql)
+ super
+ end
+ end
+
+ module DatasetMethods
+ # Check given SQL is not an unfrozen string.
+ def with_sql(sql, *args)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+
+ # Check that placeholder string is frozen (or all entries
+ # in placeholder array are frozen).
+ def placeholder_literal_string_sql_append(sql, pls)
+ str = pls.str
+
+ if str.is_a?(Array)
+ str.each do |s|
+ _check_unfrozen_literal_string(s)
+ end
+ else
+ _check_unfrozen_literal_string(str)
+ end
+
+ super
+ end
+
+ private
+
+ # Base method that other methods used to check for whether a string should be allowed
+ # as literal SQL. Allows non-strings as well as frozen strings.
+ def _check_unfrozen_literal_string(str)
+ return if !str.is_a?(String) || str.frozen?
+
+ if str.is_a?(LiteralString)
+ _check_unfrozen_literal_string(str.source)
+ else
+ raise Error, "cannot treat unfrozen string as literal SQL: #{str.inspect}"
+ end
+ end
+
+ # Check literal strings appended to SQL.
+ def literal_literal_string_append(sql, v)
+ _check_unfrozen_literal_string(v)
+ super
+ end
+
+ # Check static SQL is not frozen.
+ def static_sql(sql)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+ end
+ end
+
+ Dataset.register_extension(:lit_require_frozen, LitRequireFrozen::DatasetMethods)
+ Database.register_extension(:lit_require_frozen, LitRequireFrozen::DatabaseMethods)
+end
* Changed:
MIT-LICENSE
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/MIT-LICENSE 2026-05-04 02:36:11.385862728 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/MIT-LICENSE 2026-05-04 02:36:11.420863506 +0000
@@ -2 +2 @@
-Copyright (c) 2008-2023 Jeremy Evans
+Copyright (c) 2008-2026 Jeremy Evans and contributors
lib/sequel/adapters/jdbc/derby.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:36:11.387862772 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:36:11.421863528 +0000
@@ -2,0 +3,2 @@
+# SEQUEL6: Remove
+
lib/sequel/adapters/jdbc/h2.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:36:11.387862772 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:36:11.421863528 +0000
@@ -21 +21 @@
- run("COMMIT TRANSACTION #{transaction_id}", opts)
+ run("COMMIT TRANSACTION #{transaction_id}".freeze, opts)
@@ -39 +39 @@
- run("ROLLBACK TRANSACTION #{transaction_id}", opts)
+ run("ROLLBACK TRANSACTION #{transaction_id}".freeze, opts)
lib/sequel/adapters/postgres.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:36:11.390862839 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:36:11.423863572 +0000
@@ -730 +730 @@
- LiteralString.new("#{prepared_arg_placeholder}#{i}")
+ LiteralString.new("#{prepared_arg_placeholder}#{i}".freeze)
lib/sequel/adapters/shared/mssql.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:36:11.390862839 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:36:11.424863595 +0000
@@ -109 +109 @@
- sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}".freeze
@@ -403 +403 @@
- run(ds.into(name).sql)
+ run(ds.into(name).sql.freeze)
@@ -881 +881 @@
- sql = multi_insert_sql(columns, values)[0]
+ sql = multi_insert_sql(columns, values)[0].freeze
lib/sequel/adapters/shared/mysql.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:36:11.391862861 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:36:11.424863595 +0000
@@ -43 +43 @@
- run("XA COMMIT #{literal(transaction_id)}", opts)
+ run("XA COMMIT #{literal(transaction_id)}".freeze, opts)
@@ -106 +106 @@
- metadata_dataset.with_sql(sql).each do |r|
+ metadata_dataset.with_sql(sql.freeze).each do |r|
@@ -118 +118 @@
- run("XA ROLLBACK #{literal(transaction_id)}", opts)
+ run("XA ROLLBACK #{literal(transaction_id)}".freeze, opts)
@@ -783 +783,2 @@
- ds = db.send(:metadata_dataset).with_sql(((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql).naked
+ sql = ((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql
+ ds = db.send(:metadata_dataset).with_sql(sql.freeze).naked
lib/sequel/adapters/shared/postgres.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:36:11.392862883 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:36:11.425863617 +0000
@@ -342 +342 @@
- run("COMMIT PREPARED #{literal(transaction_id)}", opts)
+ run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -419 +419 @@
- run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT", server_hash)
+ run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT".freeze, server_hash)
@@ -456 +456 @@
- self << create_function_sql(name, definition, opts)
+ self << create_function_sql(name, definition, opts).freeze
@@ -467 +467 @@
- self << create_language_sql(name, opts)
+ self << create_language_sql(name, opts).freeze
@@ -476 +476 @@
- self << create_schema_sql(name, opts)
+ self << create_schema_sql(name, opts).freeze
@@ -512 +512 @@
- self << create_trigger_sql(table, name, function, opts)
+ self << create_trigger_sql(table, name, function, opts).freeze
@@ -545 +545 @@
- run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}".freeze
@@ -555 +555 @@
- self << drop_function_sql(name, opts)
+ self << drop_function_sql(name, opts).freeze
@@ -564 +564 @@
- self << drop_language_sql(name, opts)
+ self << drop_language_sql(name, opts).freeze
@@ -573 +573 @@
- self << drop_schema_sql(name, opts)
+ self << drop_schema_sql(name, opts).freeze
@@ -584 +584 @@
- self << drop_trigger_sql(table, name, opts)
+ self << drop_trigger_sql(table, name, opts).freeze
@@ -748 +748 @@
- self << rename_schema_sql(name, new_name)
+ self << rename_schema_sql(name, new_name).freeze
@@ -759 +759 @@
- run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}".freeze
@@ -772 +772 @@
- seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
+ seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
@@ -787 +787 @@
- run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
+ run("ROLLBACK PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -1472 +1472 @@
- cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}")
+ cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}".freeze)
lib/sequel/adapters/shared/sqlite.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:36:11.392862883 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:36:11.425863617 +0000
@@ -198 +198 @@
- metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)".freeze, input_identifier_meth(opts[:dataset]).call(table_name))
@@ -410 +410 @@
- c[:default] = LiteralString.new(c[:default]) if c[:default]
+ c[:default] = LiteralString.new(c[:default]).freeze if c[:default]
@@ -687 +687 @@
- ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}".freeze)
lib/sequel/adapters/sqlite.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:36:11.392862883 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:36:11.426863639 +0000
@@ -391 +391 @@
- LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/adapters/tinytds.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:36:11.392862883 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:36:11.426863639 +0000
@@ -208 +208 @@
- LiteralString.new("@#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/connection_pool.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/connection_pool.rb 2026-05-04 02:36:11.394862928 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/connection_pool.rb 2026-05-04 02:36:11.427863661 +0000
@@ -133,0 +134,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, *)
+ disconnect_connection(conn)
+ end
+
lib/sequel/connection_pool/sharded_timed_queue.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:36:11.394862928 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:36:11.427863661 +0000
@@ -74 +74 @@
- break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available(queue, server)) && !conns[conn]
@@ -94 +94 @@
- # and then the method will only disconnect connectsion from those specified shards.
+ # and then the method will only disconnect connections from those specified shards.
@@ -98 +98,3 @@
- while conn = queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available(queue, server)
+ nconns += 1
@@ -101 +103 @@
- fill_queue(server)
+ fill_queue(server, nconns)
@@ -135 +137 @@
- fill_queue(server)
+ fill_queue(server, 1)
@@ -170 +172 @@
- while conn = queue.pop(timeout: 0)
+ while conn = available(queue, server)
@@ -227,0 +230,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, _, server)
+ disconnect_pool_connection(conn, server)
+ end
+
@@ -247 +255,4 @@
- def fill_queue(server)
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(server, nconns)
@@ -249 +260 @@
- if queue.num_waiting > 0
+ if nconns > 0 && queue.num_waiting > 0
@@ -251 +262,2 @@
- while queue.num_waiting > 0 && (conn = try_make_new(server))
+ while nconns > 0 && queue.num_waiting > 0 && (conn = try_make_new(server))
+ nconns -= 1
@@ -300 +312 @@
- fill_queue(server)
+ fill_queue(server, to_disconnect.size)
@@ -314 +326 @@
- if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
+ if conn = available(queue, server) || try_make_new(server) || wait_until_available(queue, server)
@@ -319,0 +332,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available(queue, _server)
+ queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available(queue, _server)
+ queue.pop(timeout: @timeout)
lib/sequel/connection_pool/timed_queue.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:36:11.394862928 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:36:11.427863661 +0000
@@ -45 +45 @@
- break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available) && !conns[conn]
@@ -62 +62,3 @@
- while conn = @queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available
+ nconns += 1
@@ -65 +67 @@
- fill_queue
+ fill_queue(nconns)
@@ -97 +99 @@
- fill_queue
+ fill_queue(1)
@@ -159,2 +161,5 @@
- def fill_queue
- if @queue.num_waiting > 0
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(nconns)
+ if nconns > 0 && @queue.num_waiting > 0
@@ -162 +167 @@
- while @queue.num_waiting > 0 && (conn = try_make_new)
+ while nconns > 0 && @queue.num_waiting > 0 && (conn = try_make_new)
@@ -210 +215 @@
- fill_queue
+ fill_queue(to_disconnect.size)
@@ -223 +228 @@
- if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
+ if conn = available || try_make_new || wait_until_available
@@ -228,0 +234,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available
+ @queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available
+ @queue.pop(timeout: @timeout)
lib/sequel/database/schema_generator.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:36:11.396862972 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:36:11.429863706 +0000
@@ -5,0 +6,27 @@
+ module ColumnOptionMerger
+ private
+
+ # Merge given options into the column's default options. For backwards compatibility,
+ # the options take priority, but in cases where the option value overrides the argument
+ # value, and the values are different, we warn as this is likely to be an error in the
+ # code.
+ def _merge_column_options(defaults, opts)
+ defaults.merge!(opts) do |k, defv, v|
+ unless defv == v
+ # :nocov:
+ if RUBY_VERSION >= "3.2"
+ # :nocov:
+ caller_loc = Thread.each_caller_location do |loc|
+ break loc unless loc.path == __FILE__
+ end
+ caller_loc &&= "#{caller_loc.path}:#{caller_loc.lineno}: "
+ end
+ warn("#{caller_loc}#{k.inspect} option value (#{v.inspect}) overrides argument value (#{defv.inspect})")
+ end
+
+ v
+ end
+ end
+ end
+ private_constant :ColumnOptionMerger
+
@@ -19,0 +47,2 @@
+ include ColumnOptionMerger
+
@@ -176 +205 @@
- columns << {:name => name, :type => type}.merge!(opts)
+ columns << _merge_column_options({:name => name, :type => type}, opts)
@@ -182 +211 @@
-
+
@@ -249 +278 @@
- column(name, Integer, opts)
+ column(name, opts.fetch(:type, Integer), opts)
@@ -431,0 +461,2 @@
+ include ColumnOptionMerger
+
@@ -457 +488 @@
- op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
+ op = _merge_column_options({:op => :add_column, :name => name, :type => type}, opts)
@@ -522 +553 @@
- add_column(name, Integer, {:table=>table}.merge!(opts))
+ add_column(name, opts.fetch(:type, Integer), {:table=>table}.merge!(opts))
lib/sequel/database/schema_methods.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:36:11.396862972 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:36:11.429863706 +0000
@@ -822 +822 @@
- run(create_table_as_sql(name, sql, options))
+ run(create_table_as_sql(name, sql, options).freeze)
lib/sequel/dataset/placeholder_literalizer.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:36:11.397862995 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:36:11.430863728 +0000
@@ -119,0 +120,2 @@
+ frags.each(&:freeze)
+ frags.freeze
@@ -127,0 +130 @@
+ ds.opts[:sql].freeze
lib/sequel/dataset/prepared_statements.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:36:11.397862995 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:36:11.430863728 +0000
@@ -57 +57 @@
- sql = prepared_sql
+ sql = prepared_sql.freeze
@@ -226 +226 @@
- with_sql(prepared_sql)
+ with_sql(prepared_sql.freeze)
@@ -290 +290 @@
- sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
+ sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false)).freeze
@@ -322,0 +323,3 @@
+ frags.freeze
+ frags.each(&:freeze)
+ prepared_sql.freeze
@@ -412 +415 @@
- sql = ps.prepared_sql
+ sql = ps.prepared_sql.freeze
lib/sequel/dataset/query.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/dataset/query.rb 2026-05-04 02:36:11.398863017 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/dataset/query.rb 2026-05-04 02:36:11.431863750 +0000
@@ -1304 +1304 @@
- sql = public_send(sql, *args)
+ sql = public_send(sql, *args).freeze
@@ -1477 +1477,4 @@
-
+
+ PAREN_WRAPPER = ["(".freeze, ")".freeze].freeze
+ private_constant :PAREN_WRAPPER
+
@@ -1498 +1501 @@
- LiteralString.new("(#{expr})")
+ SQL::PlaceholderLiteralString.new(PAREN_WRAPPER, [expr])
lib/sequel/dataset/sql.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/dataset/sql.rb 2026-05-04 02:36:11.398863017 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/dataset/sql.rb 2026-05-04 02:36:11.431863750 +0000
@@ -56 +56 @@
- sql << v
+ literal_literal_string_append(sql, v)
@@ -1424,0 +1425,5 @@
+ end
+
+ # Append string to SQL string.
+ def literal_literal_string_append(sql, v)
+ sql << v
lib/sequel/extensions/connection_expiration.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:36:11.399863039 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:36:11.432863773 +0000
@@ -94 +94 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/connection_validator.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:36:11.399863039 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:36:11.432863773 +0000
@@ -121 +121 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/dataset_run.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:36:11.400863061 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:36:11.433863795 +0000
@@ -33 +33 @@
- db.run(sql, :server=>server)
+ db.run(sql.freeze, :server=>server)
@@ -35 +35 @@
- db.run(sql)
+ db.run(sql.freeze)
lib/sequel/extensions/date_arithmetic.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:36:11.400863061 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:36:11.433863795 +0000
@@ -85,2 +85,2 @@
- MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
- MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
+ MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1].freeze).freeze}).freeze
+ MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1].freeze).freeze}).freeze
@@ -88 +88 @@
- DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
+ DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}".freeze).freeze}).freeze
@@ -90 +90 @@
- DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
+ DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.freeze).freeze}).freeze
@@ -110 +110 @@
- placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := ".freeze
@@ -159 +159 @@
- expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ", ", timestamp(", "))}"], value, expr)
+ expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ".freeze, ", timestamp(", "))}"], value, expr)
lib/sequel/extensions/migration.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/extensions/migration.rb 2026-05-04 02:36:11.402863106 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/migration.rb 2026-05-04 02:36:11.435863839 +0000
@@ -189,16 +189,13 @@
- begin
- instance_exec(&block)
- rescue
- just_raise = true
- end
- if just_raise
- Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
- else
- actions = @actions.reverse
- Proc.new do
- actions.each do |a|
- pr = a.last.is_a?(Proc) ? a.pop : nil
- # Allow calling private methods as the reversing methods are private
- send(*a, &pr)
- end
- end
+ instance_exec(&block)
+ rescue NoMethodError => e
+ 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"}
+ rescue => e
+ 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"}
+ else
+ actions = @actions.reverse
+ Proc.new do
+ actions.each do |a|
+ pr = a.last.is_a?(Proc) ? a.pop : nil
+ # Allow calling private methods as the reversing methods are private
+ send(*a, &pr)
+ end
@@ -273 +270 @@
- raise if args.first.is_a?(Array)
+ super if args.first.is_a?(Array)
lib/sequel/extensions/pg_enum.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:36:11.403863128 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:36:11.436863861 +0000
@@ -130 +130 @@
- run(sql)
+ run(sql.freeze)
lib/sequel/model/associations.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/model/associations.rb 2026-05-04 02:36:11.409863261 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/model/associations.rb 2026-05-04 02:36:11.443864017 +0000
@@ -114,0 +115,2 @@
+ when :lateral_subquery
+ apply_lateral_subquery_eager_graph_limit_strategy(ds)
@@ -301 +303,6 @@
- objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
+
+ objects = if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
+ else
+ apply_eager_limit_strategy(ds, strategy, eager_limit).all
+ end
@@ -323 +330 @@
- objects.concat(ds.with_sql(sql).to_a)
+ objects.concat(ds.with_sql(sql.freeze).to_a)
@@ -369 +376,6 @@
- ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ ds = if filter_by_associations_limit_strategy == :lateral_subquery
+ apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
+ else
+ filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ end
+
@@ -756,0 +769,55 @@
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds.
+ where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys))).
+ limit(*limit_and_offset).
+ lateral
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ associated_table_name = associated_class.table_name
+
+ associated_class.
+ from(table_name).
+ select_all(associated_table_name).
+ join(lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset).as(associated_table_name), true).
+ order(*self[:order])
+ end
+
+ # Return an expression to filter the filter by associations dataset to only
+ # rows related to given objects.
+ def _lateral_subquery_filter_limit_strategy_conditions(obj, key, value_method, value_column)
+ value = case obj
+ when Array
+ if key.is_a?(Array)
+ key_methods = Array(value_method)
+ obj.map{|o| key_methods.map{|meth| o.send(meth)}}
+ else
+ obj.map{|o| o.send(value_method)}
+ end
+ when Sequel::Dataset
+ obj.select(*Array(qualify(associated_class.table_name, value_column)))
+ else
+ if key.is_a?(Array)
+ return Array(key).zip(Array(value_method).map{|meth| obj.send(meth)})
+ else
+ obj.send(value_method)
+ end
+ end
+
+ {key => value}
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds.
+ select(*qualify(associated_class.table_name, associated_class.primary_key)).
+ limit(*limit_and_offset).
+ lateral)
+ end
+
+ def apply_lateral_subquery_filter_limit_strategy(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_dataset(self[:model].
+ select(*lateral_subquery_filter_limit_strategy_lateral_dataset_select).
+ join(lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj).as(associated_class.table_name), filter_by_associations_conditions_subquery_conditions(obj)), obj)
+ end
+
@@ -779 +846,6 @@
- apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
+ strategy = eager_limit_strategy
+ if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, arg, limit_and_offset)
+ else
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), strategy)
+ end
@@ -1021 +1093 @@
- # Support a correlated subquery limit strategy when using eager_graph.
+ # Support a lateral_subquery and correlated_subquery limit strategy when using eager_graph.
@@ -1120,0 +1193,48 @@
+ # Use a LATERAL subquery to limit the dataset. Note that this will not
+ # work correctly if the associated dataset uses qualified identifers in the WHERE clause,
+ # as they would reference the containing query instead of the subquery.
+ #
+ # This does not contain the conditions that are necessary to join to the
+ # query, since the necessary qualifier is not passed as an argument.
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ table_name = ds.first_source_alias
+ qualifier = ds.opts[:eager_options][:implicit_qualifier]
+ graph_conditions = self[:_graph_conditions]
+
+ unless Sequel.condition_specifier?(graph_conditions)
+ raise Error, "lateral_subquery eager graph limit strategy only supported when graph conditions are a hash or array of pairs"
+ end
+
+ ds.
+ limit(*limit_and_offset).
+ order(*self[:order]).
+ where(graph_conditions.map{|k, v| [qualify(table_name, k), qualify(qualifier, v)]}).
+ lateral
+ end
+
+ # Avoid setting duplicate predicate condition when using the lateral subquery
+ # eager limit strategy.
+ def eager_loading_set_predicate_condition(ds, eo)
+ eager_limit_strategy == :lateral_subquery ? ds : super
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ super.where(qualify(self[:model].table_name, self[:primary_key]) => ids)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, filter_by_associations_conditions_key, self[:key_method], self[:key])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(lateral_subquery_filter_limit_strategy_conditions(obj))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualified_primary_key
+ end
+
@@ -1434,0 +1555,46 @@
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, lateral_subquery_filter_limit_strategy_conditions_key, right_primary_key_method, right_primary_key)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ self[:right_key]
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(filter_by_associations_conditions_key => ds.db.from(self[:join_table]).
+ select(*self[:left_key]).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualify(self[:model].table_name, self[:left_primary_key])
+ end
+
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds = super
+ select = ds.opts[:select].dup
+ left_key_alias = self[:left_key_alias]
+ select.pop while (s = select.last).is_a?(Sequel::SQL::AliasedExpression) && (left_key_alias.is_a?(Array) ? left_key_alias.include?(s.alias) : s.alias == left_key_alias)
+ ds = ds.clone(:select=>select)
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ super.
+ select_all(associated_class.table_name).
+ select_append(*qualify(table_name, self[:left_primary_keys]).zip(Array(self[:left_key_alias])).map{|id, aliaz| Sequel.as(id, aliaz)}).
+ where(qualify(table_name, self[:left_primary_key]) => ids)
+ end
+
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ ds.
+ limit(*limit_and_offset).
+ where(qualify(join_table_alias, self[:left_keys]).zip(qualify(ds.opts[:eager_options][:implicit_qualifier], self[:left_primary_key_columns]))).
+ order(*self[:order]).
+ lateral
+ end
+
@@ -2415,0 +2582 @@
+ graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
@@ -2418 +2585,5 @@
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
+ graph_limit_strategy = eo[:limit_strategy]
+ egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
+ graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
+
+ ds = ds.graph(egds, graph_conditions_true || graph_conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
@@ -2500,0 +2672,3 @@
+ if eager_options[:limit_strategy] == :lateral_subquery
+ ds = ds.clone(:eager_options=>eager_options)
+ end
lib/sequel/plugins/dataset_associations.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:36:11.412863328 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:36:11.447864106 +0000
@@ -85 +85 @@
- ds = r.associated_class.dataset
+ klass = r.associated_class
@@ -86,0 +87,19 @@
+
+ if r.send(:filter_by_associations_limit_strategy) == :lateral_subquery
+ ds = r.send(:associated_eager_dataset)
+
+ case r[:type]
+ when :one_to_one, :one_to_many
+ sds = sds.select(*Array(r.qualified_primary_key))
+ else
+ sds = sds.select(*r[:left_primary_keys])
+ ds = ds.select_all(klass.table_name)
+ update_select = true
+ end
+
+ ds = r.send(:apply_lateral_subquery_eager_limit_strategy, ds, sds, r.limit_and_offset)
+ ds = ds.clone(:select=>ds.opts[:select][0,1]) if update_select
+ return ds.clone(:eager=>nil, :eager_graph=>nil)
+ end
+
+ ds = klass.dataset
lib/sequel/plugins/dirty.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:36:11.413863350 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:36:11.447864106 +0000
@@ -237,2 +237,5 @@
- check_missing_initial_value(column)
- iv[column] = get_column_value(column)
+ if db_schema[column]
+ check_missing_initial_value(column)
+ iv[column] = get_column_value(column)
+ end
+
lib/sequel/plugins/many_through_many.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:36:11.415863395 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:36:11.449864150 +0000
@@ -148,0 +149,21 @@
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ qualify(reverse_edges.first[:table], reverse_edges.first[:left])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(qualify(edges.first[:table], edges.first[:right])).zip(Array(qualify(self[:model].table_name, edges.first[:left]))))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ first_edge, *remaining_edges = edges
+ filter_ds = ds.db.from(first_edge[:table]).
+ select(*qualify(first_edge[:table], first_edge[:right])).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj))
+
+ remaining_edges.each do |edge|
+ filter_ds = filter_ds.join(edge[:table], Array(edge[:right]).zip(Array(edge[:left])))
+ end
+
+ ds.where(qualify(self[:model].table_name, self[:left_primary_key]) => filter_ds)
+ end
+
lib/sequel/plugins/mssql_optimistic_locking.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:36:11.415863395 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:36:11.449864150 +0000
@@ -58 +58 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
lib/sequel/plugins/pg_xmin_optimistic_locking.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:36:11.416863417 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:36:11.450864173 +0000
@@ -102 +102 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
lib/sequel/plugins/serialization.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:36:11.416863417 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:36:11.451864195 +0000
@@ -32,0 +33,9 @@
+ # Note that use of an unsafe serialization method can result in an attack vector
+ # (potentially allowing remote code execution) if an attacker has the ability to
+ # store data directly in the underlying column. This would affect the marshal
+ # serialization format, and on older versions of Ruby, potentially the yaml and
+ # json serialization formats as well. It can also affect custom formats. You
+ # should ensure that attackers do not have access to store data directly in the
+ # underlying column when using this plugin (especially when using an unsafe
+ # serialization method).
+ #
@@ -100 +109 @@
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
lib/sequel/version.rb
--- /tmp/d20260504-1933-il36x8/sequel-5.101.0/lib/sequel/version.rb 2026-05-04 02:36:11.420863506 +0000
+++ /tmp/d20260504-1933-il36x8/sequel-5.104.0/lib/sequel/version.rb 2026-05-04 02:36:11.454864261 +0000
@@ -9 +9 @@
- MINOR = 101
+ MINOR = 104 |
Contributor
gem compare bigdecimal 4.0.1 4.1.2Compared versions: ["4.0.1", "4.1.2"]
DIFFERENT require_paths:
4.0.1: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.0.1", "lib"]
4.1.2: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.1.2", "lib"]
DIFFERENT required_ruby_version:
4.0.1: >= 2.5.0
4.1.2: >= 2.6.0
DIFFERENT version:
4.0.1: 4.0.1
4.1.2: 4.1.2
DIFFERENT files:
4.0.1->4.1.2:
* Added:
ext/bigdecimal/div.h +192/-0
ext/bigdecimal/ntt.h +191/-0
sig/big_decimal.rbs +1502/-0
sig/big_decimal_util.rbs +158/-0
sig/big_math.rbs +423/-0
* Changed:
bigdecimal.gemspec +6/-1
ext/bigdecimal/bigdecimal.c +263/-262
ext/bigdecimal/bigdecimal.h +43/-37
ext/bigdecimal/extconf.rb +5/-2
ext/bigdecimal/missing.h +4/-2
ext/bigdecimal/missing/dtoa.c +183/-136
lib/bigdecimal.rb +104/-60
lib/bigdecimal/math.rb +103/-124
lib/bigdecimal/util.rb +1/-1
sample/linear.rb +73/-37
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/nlsolve.rb +47/-30
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/pi.rb +2/-7
(!) Shebang probably lost: #!/usr/local/bin/ruby |
Contributor
|
Contributor
|
Contributor
gem compare --diff sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb
--- /tmp/20260504-1831-1webe1 2026-05-04 02:36:32.135216379 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/connection_checkout_event_callback.rb 2026-05-04 02:36:32.110216596 +0000
@@ -0,0 +1,151 @@
+# frozen-string-literal: true
+#
+# The connection_checkout_event_callback extension modifies a database's
+# connection pool to allow for a checkout event callback. This callback is
+# called with the following arguments:
+#
+# :immediately_available :: Connection immediately available and returned
+# :not_immediately_available :: Connection not immediately available
+# :new_connection :: New connection created and returned
+# Float :: Number of seconds waiting to acquire a connection
+#
+# This is a low-level extension that allows for building telemetry
+# information. It doesn't implement any telemetry reporting itself. The
+# main reason for recording this information is to use it to determine the
+# appropriate size for the connection pool. Having too large a connection
+# pool can waste resources, while having too small a connection pool can
+# result in substantial time to check out a connection. In general, you
+# want to use as small a pool as possible while keeping the time to
+# checkout a connection low.
+#
+# To use the connection checkout event callback, you must first load the
+# extension:
+#
+# DB.extension(:connection_checkout_event_callback)
+#
+# By default, an empty proc is used as the callback so that loading the
+# support doesn't break anything. If you are using the extension, you
+# should set the callback at some point during application startup:
+#
+# DB.pool.on_checkout_event = proc do |event|
+# # ...
+# end
+#
+# When using the sharded connection pool, the callback is also
+# passed a second argument, the requested server shard (generally a
+# symbol), allowing for collection of per-shard telemetry:
+#
+# DB.pool.on_checkout_event = proc do |event, server|
+# # ...
+# end
+#
+# Note that the callback may be called currently by multiple threads.
+# You should use some form of concurrency control inside the callback,
+# such as a mutex or queue.
+#
+# Below is a brief example of usage to determine the percentage of
+# connection requests where a connection was immediately available:
+#
+# mutex = Mutex.new
+# total = immediates = 0
+#
+# DB.pool.on_checkout_event = proc do |event|
+# case event
+# when :immediately_available
+# mutex.synchronize do
+# total += 1
+# immediates += 1
+# end
+# when :not_immediately_available
+# mutex.synchronize do
+# total += 1
+# end
+# end
+# end
+#
+# immediate_percentage = lambda do
+# mutex.synchronize do
+# 100.0 * immediates / total
+# end
+# end
+#
+# Note that this extension only works with the timed_queue
+# and sharded_timed_queue connection pools (the default
+# connection pools when using Ruby 3.2+).
+#
+# Related modules: Sequel::ConnectionCheckoutEventCallbacks::TimedQueue,
+# Sequel::ConnectionCheckoutEventCallbacks::ShardedTimedQueue
+
+#
+module Sequel
+ module ConnectionCheckoutEventCallbacks
+ module TimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available)
+ conn
+ end
+
+ def try_make_new
+ conn = super
+ @on_checkout_event.call(:new_connection) if conn
+ conn
+ end
+
+ def wait_until_available
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer))
+ conn
+ end
+ end
+
+ module ShardedTimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available(queue, server)
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available, server)
+ conn
+ end
+
+ def try_make_new(server)
+ conn = super
+ @on_checkout_event.call(:new_connection, server) if conn
+ conn
+ end
+
+ def wait_until_available(queue, server)
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer), server)
+ conn
+ end
+ end
+ end
+
+ default_callback = proc{}
+
+ Database.register_extension(:connection_checkout_event_callback) do |db|
+ pool = db.pool
+
+ case pool.pool_type
+ when :timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::TimedQueue)
+ when :sharded_timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::ShardedTimedQueue)
+ else
+ raise Error, "the connection_checkout_event_callback extension is only supported when using a timed_queue connection pool"
+ end
+
+ pool.on_checkout_event ||= default_callback
+ end
+end
lib/sequel/extensions/lit_require_frozen.rb
--- /tmp/20260504-1831-mpybwq 2026-05-04 02:36:32.137216361 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/lit_require_frozen.rb 2026-05-04 02:36:32.113216570 +0000
@@ -0,0 +1,131 @@
+# frozen-string-literal: true
+#
+# The lit_require_frozen extension disallows the use of unfrozen strings
+# as literal strings in database and dataset methods. If you try to use an
+# unfrozen string as a literal string for a dataset using this extension,
+# an exception will be raised.
+#
+# While this works for all Ruby versions, it is designed for use on Ruby 3+
+# where all files are using the frozen-string-literal magic comment. In this
+# case, uninterpolated literal strings are frozen, but interpolated strings
+# are not frozen. This allows you to catch potentially dangerous code:
+#
+# # Probably safe, no exception raised
+# DB["SELECT * FROM t WHERE c > :v", v: user_provided_string)
+#
+# # Potentially unsafe, raises Sequel::LitRequireFrozen::Error
+# DB["SELECT * FROM t WHERE c > '#{user_provided_string}'"]
+#
+# The assumption made is that a frozen string is unlikely to contain unsafe
+# input, while an unfrozen string has potentially been interpolated and may
+# contain unsafe input.
+#
+# This disallows the the following cases:
+#
+# * Sequel::LiteralString instances that are unfrozen and are not based on a
+# frozen string
+# * Sequel::SQL::PlaceholderLiteralString instances when the placeholder string
+# is not frozen
+# * Unfrozen strings passed to Database#<< or #[] or Dataset#with_sql
+#
+# To use this extension, load it into the database:
+#
+# DB.extension :lit_require_frozen
+#
+# It can also be loaded into individual datasets:
+#
+# ds = DB[:t].extension(:lit_require_frozen)
+#
+# Assuming you have good test coverage, it is recommended to only load
+# this extension when testing.
+#
+# Related module: Sequel::LitRequireFrozen
+
+#
+module Sequel
+ class LiteralString
+ # The string used when creating the literal string (first argument to
+ # Sequel::LiteralString.new). This may be nil if no string was provided,
+ # or if the litral string was created before this extension was required.
+ attr_reader :source
+
+ def initialize(*a)
+ @source = a.first
+ super
+ end
+ # :nocov:
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
+ # :nocov:
+ end
+
+ module LitRequireFrozen
+ # Error class raised for using unfrozen literal string.
+ class Error < Sequel::Error
+ end
+
+ module DatabaseMethods
+ def self.extended(db)
+ db.extend_datasets(DatasetMethods)
+ end
+
+ # Check given SQL is frozen before running it.
+ def run(sql, opts=OPTS)
+ @default_dataset.with_sql(sql)
+ super
+ end
+ end
+
+ module DatasetMethods
+ # Check given SQL is not an unfrozen string.
+ def with_sql(sql, *args)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+
+ # Check that placeholder string is frozen (or all entries
+ # in placeholder array are frozen).
+ def placeholder_literal_string_sql_append(sql, pls)
+ str = pls.str
+
+ if str.is_a?(Array)
+ str.each do |s|
+ _check_unfrozen_literal_string(s)
+ end
+ else
+ _check_unfrozen_literal_string(str)
+ end
+
+ super
+ end
+
+ private
+
+ # Base method that other methods used to check for whether a string should be allowed
+ # as literal SQL. Allows non-strings as well as frozen strings.
+ def _check_unfrozen_literal_string(str)
+ return if !str.is_a?(String) || str.frozen?
+
+ if str.is_a?(LiteralString)
+ _check_unfrozen_literal_string(str.source)
+ else
+ raise Error, "cannot treat unfrozen string as literal SQL: #{str.inspect}"
+ end
+ end
+
+ # Check literal strings appended to SQL.
+ def literal_literal_string_append(sql, v)
+ _check_unfrozen_literal_string(v)
+ super
+ end
+
+ # Check static SQL is not frozen.
+ def static_sql(sql)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+ end
+ end
+
+ Dataset.register_extension(:lit_require_frozen, LitRequireFrozen::DatasetMethods)
+ Database.register_extension(:lit_require_frozen, LitRequireFrozen::DatabaseMethods)
+end
* Changed:
MIT-LICENSE
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/MIT-LICENSE 2026-05-04 02:36:32.058217047 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/MIT-LICENSE 2026-05-04 02:36:32.098216700 +0000
@@ -2 +2 @@
-Copyright (c) 2008-2023 Jeremy Evans
+Copyright (c) 2008-2026 Jeremy Evans and contributors
lib/sequel/adapters/jdbc/derby.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:36:32.059217039 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:36:32.099216691 +0000
@@ -2,0 +3,2 @@
+# SEQUEL6: Remove
+
lib/sequel/adapters/jdbc/h2.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:36:32.059217039 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:36:32.100216683 +0000
@@ -21 +21 @@
- run("COMMIT TRANSACTION #{transaction_id}", opts)
+ run("COMMIT TRANSACTION #{transaction_id}".freeze, opts)
@@ -39 +39 @@
- run("ROLLBACK TRANSACTION #{transaction_id}", opts)
+ run("ROLLBACK TRANSACTION #{transaction_id}".freeze, opts)
lib/sequel/adapters/postgres.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:36:32.062217012 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:36:32.101216674 +0000
@@ -730 +730 @@
- LiteralString.new("#{prepared_arg_placeholder}#{i}")
+ LiteralString.new("#{prepared_arg_placeholder}#{i}".freeze)
lib/sequel/adapters/shared/mssql.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:36:32.065216987 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:36:32.102216665 +0000
@@ -109 +109 @@
- sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}".freeze
@@ -403 +403 @@
- run(ds.into(name).sql)
+ run(ds.into(name).sql.freeze)
@@ -881 +881 @@
- sql = multi_insert_sql(columns, values)[0]
+ sql = multi_insert_sql(columns, values)[0].freeze
lib/sequel/adapters/shared/mysql.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:36:32.065216987 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:36:32.102216665 +0000
@@ -43 +43 @@
- run("XA COMMIT #{literal(transaction_id)}", opts)
+ run("XA COMMIT #{literal(transaction_id)}".freeze, opts)
@@ -106 +106 @@
- metadata_dataset.with_sql(sql).each do |r|
+ metadata_dataset.with_sql(sql.freeze).each do |r|
@@ -118 +118 @@
- run("XA ROLLBACK #{literal(transaction_id)}", opts)
+ run("XA ROLLBACK #{literal(transaction_id)}".freeze, opts)
@@ -783 +783,2 @@
- ds = db.send(:metadata_dataset).with_sql(((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql).naked
+ sql = ((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql
+ ds = db.send(:metadata_dataset).with_sql(sql.freeze).naked
lib/sequel/adapters/shared/postgres.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:36:32.066216978 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:36:32.103216657 +0000
@@ -342 +342 @@
- run("COMMIT PREPARED #{literal(transaction_id)}", opts)
+ run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -419 +419 @@
- run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT", server_hash)
+ run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT".freeze, server_hash)
@@ -456 +456 @@
- self << create_function_sql(name, definition, opts)
+ self << create_function_sql(name, definition, opts).freeze
@@ -467 +467 @@
- self << create_language_sql(name, opts)
+ self << create_language_sql(name, opts).freeze
@@ -476 +476 @@
- self << create_schema_sql(name, opts)
+ self << create_schema_sql(name, opts).freeze
@@ -512 +512 @@
- self << create_trigger_sql(table, name, function, opts)
+ self << create_trigger_sql(table, name, function, opts).freeze
@@ -545 +545 @@
- run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}".freeze
@@ -555 +555 @@
- self << drop_function_sql(name, opts)
+ self << drop_function_sql(name, opts).freeze
@@ -564 +564 @@
- self << drop_language_sql(name, opts)
+ self << drop_language_sql(name, opts).freeze
@@ -573 +573 @@
- self << drop_schema_sql(name, opts)
+ self << drop_schema_sql(name, opts).freeze
@@ -584 +584 @@
- self << drop_trigger_sql(table, name, opts)
+ self << drop_trigger_sql(table, name, opts).freeze
@@ -748 +748 @@
- self << rename_schema_sql(name, new_name)
+ self << rename_schema_sql(name, new_name).freeze
@@ -759 +759 @@
- run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}".freeze
@@ -772 +772 @@
- seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
+ seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
@@ -787 +787 @@
- run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
+ run("ROLLBACK PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -1472 +1472 @@
- cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}")
+ cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}".freeze)
lib/sequel/adapters/shared/sqlite.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:36:32.067216969 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:36:32.104216648 +0000
@@ -198 +198 @@
- metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)".freeze, input_identifier_meth(opts[:dataset]).call(table_name))
@@ -410 +410 @@
- c[:default] = LiteralString.new(c[:default]) if c[:default]
+ c[:default] = LiteralString.new(c[:default]).freeze if c[:default]
@@ -687 +687 @@
- ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}".freeze)
lib/sequel/adapters/sqlite.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:36:32.067216969 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:36:32.104216648 +0000
@@ -391 +391 @@
- LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/adapters/tinytds.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:36:32.067216969 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:36:32.104216648 +0000
@@ -208 +208 @@
- LiteralString.new("@#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/connection_pool.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/connection_pool.rb 2026-05-04 02:36:32.072216926 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/connection_pool.rb 2026-05-04 02:36:32.105216639 +0000
@@ -133,0 +134,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, *)
+ disconnect_connection(conn)
+ end
+
lib/sequel/connection_pool/sharded_timed_queue.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:36:32.072216926 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:36:32.105216639 +0000
@@ -74 +74 @@
- break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available(queue, server)) && !conns[conn]
@@ -94 +94 @@
- # and then the method will only disconnect connectsion from those specified shards.
+ # and then the method will only disconnect connections from those specified shards.
@@ -98 +98,3 @@
- while conn = queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available(queue, server)
+ nconns += 1
@@ -101 +103 @@
- fill_queue(server)
+ fill_queue(server, nconns)
@@ -135 +137 @@
- fill_queue(server)
+ fill_queue(server, 1)
@@ -170 +172 @@
- while conn = queue.pop(timeout: 0)
+ while conn = available(queue, server)
@@ -227,0 +230,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, _, server)
+ disconnect_pool_connection(conn, server)
+ end
+
@@ -247 +255,4 @@
- def fill_queue(server)
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(server, nconns)
@@ -249 +260 @@
- if queue.num_waiting > 0
+ if nconns > 0 && queue.num_waiting > 0
@@ -251 +262,2 @@
- while queue.num_waiting > 0 && (conn = try_make_new(server))
+ while nconns > 0 && queue.num_waiting > 0 && (conn = try_make_new(server))
+ nconns -= 1
@@ -300 +312 @@
- fill_queue(server)
+ fill_queue(server, to_disconnect.size)
@@ -314 +326 @@
- if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
+ if conn = available(queue, server) || try_make_new(server) || wait_until_available(queue, server)
@@ -319,0 +332,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available(queue, _server)
+ queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available(queue, _server)
+ queue.pop(timeout: @timeout)
lib/sequel/connection_pool/timed_queue.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:36:32.072216926 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:36:32.105216639 +0000
@@ -45 +45 @@
- break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available) && !conns[conn]
@@ -62 +62,3 @@
- while conn = @queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available
+ nconns += 1
@@ -65 +67 @@
- fill_queue
+ fill_queue(nconns)
@@ -97 +99 @@
- fill_queue
+ fill_queue(1)
@@ -159,2 +161,5 @@
- def fill_queue
- if @queue.num_waiting > 0
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(nconns)
+ if nconns > 0 && @queue.num_waiting > 0
@@ -162 +167 @@
- while @queue.num_waiting > 0 && (conn = try_make_new)
+ while nconns > 0 && @queue.num_waiting > 0 && (conn = try_make_new)
@@ -210 +215 @@
- fill_queue
+ fill_queue(to_disconnect.size)
@@ -223 +228 @@
- if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
+ if conn = available || try_make_new || wait_until_available
@@ -228,0 +234,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available
+ @queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available
+ @queue.pop(timeout: @timeout)
lib/sequel/database/schema_generator.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:36:32.073216917 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:36:32.107216622 +0000
@@ -5,0 +6,27 @@
+ module ColumnOptionMerger
+ private
+
+ # Merge given options into the column's default options. For backwards compatibility,
+ # the options take priority, but in cases where the option value overrides the argument
+ # value, and the values are different, we warn as this is likely to be an error in the
+ # code.
+ def _merge_column_options(defaults, opts)
+ defaults.merge!(opts) do |k, defv, v|
+ unless defv == v
+ # :nocov:
+ if RUBY_VERSION >= "3.2"
+ # :nocov:
+ caller_loc = Thread.each_caller_location do |loc|
+ break loc unless loc.path == __FILE__
+ end
+ caller_loc &&= "#{caller_loc.path}:#{caller_loc.lineno}: "
+ end
+ warn("#{caller_loc}#{k.inspect} option value (#{v.inspect}) overrides argument value (#{defv.inspect})")
+ end
+
+ v
+ end
+ end
+ end
+ private_constant :ColumnOptionMerger
+
@@ -19,0 +47,2 @@
+ include ColumnOptionMerger
+
@@ -176 +205 @@
- columns << {:name => name, :type => type}.merge!(opts)
+ columns << _merge_column_options({:name => name, :type => type}, opts)
@@ -182 +211 @@
-
+
@@ -249 +278 @@
- column(name, Integer, opts)
+ column(name, opts.fetch(:type, Integer), opts)
@@ -431,0 +461,2 @@
+ include ColumnOptionMerger
+
@@ -457 +488 @@
- op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
+ op = _merge_column_options({:op => :add_column, :name => name, :type => type}, opts)
@@ -522 +553 @@
- add_column(name, Integer, {:table=>table}.merge!(opts))
+ add_column(name, opts.fetch(:type, Integer), {:table=>table}.merge!(opts))
lib/sequel/database/schema_methods.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:36:32.074216909 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:36:32.107216622 +0000
@@ -822 +822 @@
- run(create_table_as_sql(name, sql, options))
+ run(create_table_as_sql(name, sql, options).freeze)
lib/sequel/dataset/placeholder_literalizer.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:36:32.075216900 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:36:32.108216613 +0000
@@ -119,0 +120,2 @@
+ frags.each(&:freeze)
+ frags.freeze
@@ -127,0 +130 @@
+ ds.opts[:sql].freeze
lib/sequel/dataset/prepared_statements.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:36:32.075216900 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:36:32.108216613 +0000
@@ -57 +57 @@
- sql = prepared_sql
+ sql = prepared_sql.freeze
@@ -226 +226 @@
- with_sql(prepared_sql)
+ with_sql(prepared_sql.freeze)
@@ -290 +290 @@
- sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
+ sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false)).freeze
@@ -322,0 +323,3 @@
+ frags.freeze
+ frags.each(&:freeze)
+ prepared_sql.freeze
@@ -412 +415 @@
- sql = ps.prepared_sql
+ sql = ps.prepared_sql.freeze
lib/sequel/dataset/query.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/dataset/query.rb 2026-05-04 02:36:32.075216900 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/dataset/query.rb 2026-05-04 02:36:32.109216604 +0000
@@ -1304 +1304 @@
- sql = public_send(sql, *args)
+ sql = public_send(sql, *args).freeze
@@ -1477 +1477,4 @@
-
+
+ PAREN_WRAPPER = ["(".freeze, ")".freeze].freeze
+ private_constant :PAREN_WRAPPER
+
@@ -1498 +1501 @@
- LiteralString.new("(#{expr})")
+ SQL::PlaceholderLiteralString.new(PAREN_WRAPPER, [expr])
lib/sequel/dataset/sql.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/dataset/sql.rb 2026-05-04 02:36:32.076216891 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/dataset/sql.rb 2026-05-04 02:36:32.109216604 +0000
@@ -56 +56 @@
- sql << v
+ literal_literal_string_append(sql, v)
@@ -1424,0 +1425,5 @@
+ end
+
+ # Append string to SQL string.
+ def literal_literal_string_append(sql, v)
+ sql << v
lib/sequel/extensions/connection_expiration.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:36:32.077216882 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:36:32.110216596 +0000
@@ -94 +94 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/connection_validator.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:36:32.077216882 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:36:32.111216587 +0000
@@ -121 +121 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/dataset_run.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:36:32.079216865 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:36:32.111216587 +0000
@@ -33 +33 @@
- db.run(sql, :server=>server)
+ db.run(sql.freeze, :server=>server)
@@ -35 +35 @@
- db.run(sql)
+ db.run(sql.freeze)
lib/sequel/extensions/date_arithmetic.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:36:32.079216865 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:36:32.111216587 +0000
@@ -85,2 +85,2 @@
- MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
- MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
+ MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1].freeze).freeze}).freeze
+ MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1].freeze).freeze}).freeze
@@ -88 +88 @@
- DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
+ DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}".freeze).freeze}).freeze
@@ -90 +90 @@
- DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
+ DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.freeze).freeze}).freeze
@@ -110 +110 @@
- placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := ".freeze
@@ -159 +159 @@
- expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ", ", timestamp(", "))}"], value, expr)
+ expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ".freeze, ", timestamp(", "))}"], value, expr)
lib/sequel/extensions/migration.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/extensions/migration.rb 2026-05-04 02:36:32.080216856 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/migration.rb 2026-05-04 02:36:32.113216570 +0000
@@ -189,16 +189,13 @@
- begin
- instance_exec(&block)
- rescue
- just_raise = true
- end
- if just_raise
- Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
- else
- actions = @actions.reverse
- Proc.new do
- actions.each do |a|
- pr = a.last.is_a?(Proc) ? a.pop : nil
- # Allow calling private methods as the reversing methods are private
- send(*a, &pr)
- end
- end
+ instance_exec(&block)
+ rescue NoMethodError => e
+ 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"}
+ rescue => e
+ 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"}
+ else
+ actions = @actions.reverse
+ Proc.new do
+ actions.each do |a|
+ pr = a.last.is_a?(Proc) ? a.pop : nil
+ # Allow calling private methods as the reversing methods are private
+ send(*a, &pr)
+ end
@@ -273 +270 @@
- raise if args.first.is_a?(Array)
+ super if args.first.is_a?(Array)
lib/sequel/extensions/pg_enum.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:36:32.081216848 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:36:32.114216561 +0000
@@ -130 +130 @@
- run(sql)
+ run(sql.freeze)
lib/sequel/model/associations.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/model/associations.rb 2026-05-04 02:36:32.087216796 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/model/associations.rb 2026-05-04 02:36:32.122216492 +0000
@@ -114,0 +115,2 @@
+ when :lateral_subquery
+ apply_lateral_subquery_eager_graph_limit_strategy(ds)
@@ -301 +303,6 @@
- objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
+
+ objects = if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
+ else
+ apply_eager_limit_strategy(ds, strategy, eager_limit).all
+ end
@@ -323 +330 @@
- objects.concat(ds.with_sql(sql).to_a)
+ objects.concat(ds.with_sql(sql.freeze).to_a)
@@ -369 +376,6 @@
- ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ ds = if filter_by_associations_limit_strategy == :lateral_subquery
+ apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
+ else
+ filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ end
+
@@ -756,0 +769,55 @@
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds.
+ where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys))).
+ limit(*limit_and_offset).
+ lateral
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ associated_table_name = associated_class.table_name
+
+ associated_class.
+ from(table_name).
+ select_all(associated_table_name).
+ join(lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset).as(associated_table_name), true).
+ order(*self[:order])
+ end
+
+ # Return an expression to filter the filter by associations dataset to only
+ # rows related to given objects.
+ def _lateral_subquery_filter_limit_strategy_conditions(obj, key, value_method, value_column)
+ value = case obj
+ when Array
+ if key.is_a?(Array)
+ key_methods = Array(value_method)
+ obj.map{|o| key_methods.map{|meth| o.send(meth)}}
+ else
+ obj.map{|o| o.send(value_method)}
+ end
+ when Sequel::Dataset
+ obj.select(*Array(qualify(associated_class.table_name, value_column)))
+ else
+ if key.is_a?(Array)
+ return Array(key).zip(Array(value_method).map{|meth| obj.send(meth)})
+ else
+ obj.send(value_method)
+ end
+ end
+
+ {key => value}
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds.
+ select(*qualify(associated_class.table_name, associated_class.primary_key)).
+ limit(*limit_and_offset).
+ lateral)
+ end
+
+ def apply_lateral_subquery_filter_limit_strategy(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_dataset(self[:model].
+ select(*lateral_subquery_filter_limit_strategy_lateral_dataset_select).
+ join(lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj).as(associated_class.table_name), filter_by_associations_conditions_subquery_conditions(obj)), obj)
+ end
+
@@ -779 +846,6 @@
- apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
+ strategy = eager_limit_strategy
+ if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, arg, limit_and_offset)
+ else
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), strategy)
+ end
@@ -1021 +1093 @@
- # Support a correlated subquery limit strategy when using eager_graph.
+ # Support a lateral_subquery and correlated_subquery limit strategy when using eager_graph.
@@ -1120,0 +1193,48 @@
+ # Use a LATERAL subquery to limit the dataset. Note that this will not
+ # work correctly if the associated dataset uses qualified identifers in the WHERE clause,
+ # as they would reference the containing query instead of the subquery.
+ #
+ # This does not contain the conditions that are necessary to join to the
+ # query, since the necessary qualifier is not passed as an argument.
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ table_name = ds.first_source_alias
+ qualifier = ds.opts[:eager_options][:implicit_qualifier]
+ graph_conditions = self[:_graph_conditions]
+
+ unless Sequel.condition_specifier?(graph_conditions)
+ raise Error, "lateral_subquery eager graph limit strategy only supported when graph conditions are a hash or array of pairs"
+ end
+
+ ds.
+ limit(*limit_and_offset).
+ order(*self[:order]).
+ where(graph_conditions.map{|k, v| [qualify(table_name, k), qualify(qualifier, v)]}).
+ lateral
+ end
+
+ # Avoid setting duplicate predicate condition when using the lateral subquery
+ # eager limit strategy.
+ def eager_loading_set_predicate_condition(ds, eo)
+ eager_limit_strategy == :lateral_subquery ? ds : super
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ super.where(qualify(self[:model].table_name, self[:primary_key]) => ids)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, filter_by_associations_conditions_key, self[:key_method], self[:key])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(lateral_subquery_filter_limit_strategy_conditions(obj))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualified_primary_key
+ end
+
@@ -1434,0 +1555,46 @@
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, lateral_subquery_filter_limit_strategy_conditions_key, right_primary_key_method, right_primary_key)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ self[:right_key]
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(filter_by_associations_conditions_key => ds.db.from(self[:join_table]).
+ select(*self[:left_key]).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualify(self[:model].table_name, self[:left_primary_key])
+ end
+
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds = super
+ select = ds.opts[:select].dup
+ left_key_alias = self[:left_key_alias]
+ select.pop while (s = select.last).is_a?(Sequel::SQL::AliasedExpression) && (left_key_alias.is_a?(Array) ? left_key_alias.include?(s.alias) : s.alias == left_key_alias)
+ ds = ds.clone(:select=>select)
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ super.
+ select_all(associated_class.table_name).
+ select_append(*qualify(table_name, self[:left_primary_keys]).zip(Array(self[:left_key_alias])).map{|id, aliaz| Sequel.as(id, aliaz)}).
+ where(qualify(table_name, self[:left_primary_key]) => ids)
+ end
+
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ ds.
+ limit(*limit_and_offset).
+ where(qualify(join_table_alias, self[:left_keys]).zip(qualify(ds.opts[:eager_options][:implicit_qualifier], self[:left_primary_key_columns]))).
+ order(*self[:order]).
+ lateral
+ end
+
@@ -2415,0 +2582 @@
+ graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
@@ -2418 +2585,5 @@
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
+ graph_limit_strategy = eo[:limit_strategy]
+ egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
+ graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
+
+ ds = ds.graph(egds, graph_conditions_true || graph_conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
@@ -2500,0 +2672,3 @@
+ if eager_options[:limit_strategy] == :lateral_subquery
+ ds = ds.clone(:eager_options=>eager_options)
+ end
lib/sequel/plugins/dataset_associations.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:36:32.090216769 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:36:32.126216457 +0000
@@ -85 +85 @@
- ds = r.associated_class.dataset
+ klass = r.associated_class
@@ -86,0 +87,19 @@
+
+ if r.send(:filter_by_associations_limit_strategy) == :lateral_subquery
+ ds = r.send(:associated_eager_dataset)
+
+ case r[:type]
+ when :one_to_one, :one_to_many
+ sds = sds.select(*Array(r.qualified_primary_key))
+ else
+ sds = sds.select(*r[:left_primary_keys])
+ ds = ds.select_all(klass.table_name)
+ update_select = true
+ end
+
+ ds = r.send(:apply_lateral_subquery_eager_limit_strategy, ds, sds, r.limit_and_offset)
+ ds = ds.clone(:select=>ds.opts[:select][0,1]) if update_select
+ return ds.clone(:eager=>nil, :eager_graph=>nil)
+ end
+
+ ds = klass.dataset
lib/sequel/plugins/dirty.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:36:32.091216761 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:36:32.126216457 +0000
@@ -237,2 +237,5 @@
- check_missing_initial_value(column)
- iv[column] = get_column_value(column)
+ if db_schema[column]
+ check_missing_initial_value(column)
+ iv[column] = get_column_value(column)
+ end
+
lib/sequel/plugins/many_through_many.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:36:32.092216752 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:36:32.128216440 +0000
@@ -148,0 +149,21 @@
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ qualify(reverse_edges.first[:table], reverse_edges.first[:left])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(qualify(edges.first[:table], edges.first[:right])).zip(Array(qualify(self[:model].table_name, edges.first[:left]))))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ first_edge, *remaining_edges = edges
+ filter_ds = ds.db.from(first_edge[:table]).
+ select(*qualify(first_edge[:table], first_edge[:right])).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj))
+
+ remaining_edges.each do |edge|
+ filter_ds = filter_ds.join(edge[:table], Array(edge[:right]).zip(Array(edge[:left])))
+ end
+
+ ds.where(qualify(self[:model].table_name, self[:left_primary_key]) => filter_ds)
+ end
+
lib/sequel/plugins/mssql_optimistic_locking.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:36:32.092216752 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:36:32.128216440 +0000
@@ -58 +58 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
lib/sequel/plugins/pg_xmin_optimistic_locking.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:36:32.093216743 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:36:32.129216431 +0000
@@ -102 +102 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
lib/sequel/plugins/serialization.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:36:32.094216735 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:36:32.130216422 +0000
@@ -32,0 +33,9 @@
+ # Note that use of an unsafe serialization method can result in an attack vector
+ # (potentially allowing remote code execution) if an attacker has the ability to
+ # store data directly in the underlying column. This would affect the marshal
+ # serialization format, and on older versions of Ruby, potentially the yaml and
+ # json serialization formats as well. It can also affect custom formats. You
+ # should ensure that attackers do not have access to store data directly in the
+ # underlying column when using this plugin (especially when using an unsafe
+ # serialization method).
+ #
@@ -100 +109 @@
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
lib/sequel/version.rb
--- /tmp/d20260504-1831-hv93hc/sequel-5.101.0/lib/sequel/version.rb 2026-05-04 02:36:32.097216709 +0000
+++ /tmp/d20260504-1831-hv93hc/sequel-5.104.0/lib/sequel/version.rb 2026-05-04 02:36:32.134216387 +0000
@@ -9 +9 @@
- MINOR = 101
+ MINOR = 104 |
Contributor
gem compare bigdecimal 4.0.1 4.1.2Compared versions: ["4.0.1", "4.1.2"]
DIFFERENT require_paths:
4.0.1: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.0.1", "lib"]
4.1.2: ["/opt/hostedtoolcache/Ruby/4.0.3/x64/lib/ruby/gems/4.0.0/extensions/x86_64-linux/4.0.0/bigdecimal-4.1.2", "lib"]
DIFFERENT required_ruby_version:
4.0.1: >= 2.5.0
4.1.2: >= 2.6.0
DIFFERENT version:
4.0.1: 4.0.1
4.1.2: 4.1.2
DIFFERENT files:
4.0.1->4.1.2:
* Added:
ext/bigdecimal/div.h +192/-0
ext/bigdecimal/ntt.h +191/-0
sig/big_decimal.rbs +1502/-0
sig/big_decimal_util.rbs +158/-0
sig/big_math.rbs +423/-0
* Changed:
bigdecimal.gemspec +6/-1
ext/bigdecimal/bigdecimal.c +263/-262
ext/bigdecimal/bigdecimal.h +43/-37
ext/bigdecimal/extconf.rb +5/-2
ext/bigdecimal/missing.h +4/-2
ext/bigdecimal/missing/dtoa.c +183/-136
lib/bigdecimal.rb +104/-60
lib/bigdecimal/math.rb +103/-124
lib/bigdecimal/util.rb +1/-1
sample/linear.rb +73/-37
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/nlsolve.rb +47/-30
(!) Shebang probably lost: #!/usr/local/bin/ruby
sample/pi.rb +2/-7
(!) Shebang probably lost: #!/usr/local/bin/ruby |
Contributor
gem compare sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT version:
5.101.0: 5.101.0
5.104.0: 5.104.0
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb +151/-0
lib/sequel/extensions/lit_require_frozen.rb +131/-0
* Changed:
MIT-LICENSE +1/-1
lib/sequel/adapters/jdbc/derby.rb +2/-0
lib/sequel/adapters/jdbc/h2.rb +2/-2
lib/sequel/adapters/postgres.rb +1/-1
lib/sequel/adapters/shared/mssql.rb +3/-3
lib/sequel/adapters/shared/mysql.rb +5/-4
lib/sequel/adapters/shared/postgres.rb +16/-16
lib/sequel/adapters/shared/sqlite.rb +3/-3
lib/sequel/adapters/sqlite.rb +1/-1
lib/sequel/adapters/tinytds.rb +1/-1
lib/sequel/connection_pool.rb +6/-0
lib/sequel/connection_pool/sharded_timed_queue.rb +36/-11
lib/sequel/connection_pool/timed_queue.rb +27/-9
lib/sequel/database/schema_generator.rb +36/-5
lib/sequel/database/schema_methods.rb +1/-1
lib/sequel/dataset/placeholder_literalizer.rb +3/-0
lib/sequel/dataset/prepared_statements.rb +7/-4
lib/sequel/dataset/query.rb +6/-3
lib/sequel/dataset/sql.rb +6/-1
lib/sequel/extensions/connection_expiration.rb +1/-1
lib/sequel/extensions/connection_validator.rb +1/-1
lib/sequel/extensions/dataset_run.rb +2/-2
lib/sequel/extensions/date_arithmetic.rb +6/-6
lib/sequel/extensions/migration.rb +14/-17
lib/sequel/extensions/pg_enum.rb +1/-1
lib/sequel/model/associations.rb +180/-6
lib/sequel/plugins/dataset_associations.rb +20/-1
lib/sequel/plugins/dirty.rb +5/-2
lib/sequel/plugins/many_through_many.rb +21/-0
lib/sequel/plugins/mssql_optimistic_locking.rb +1/-1
lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1/-1
lib/sequel/plugins/serialization.rb +10/-1
lib/sequel/version.rb +1/-1
DIFFERENT extra_rdoc_files:
5.101.0->5.104.0:
* Changed:
MIT-LICENSE +1/-1 |
1 similar comment
Contributor
gem compare sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT version:
5.101.0: 5.101.0
5.104.0: 5.104.0
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb +151/-0
lib/sequel/extensions/lit_require_frozen.rb +131/-0
* Changed:
MIT-LICENSE +1/-1
lib/sequel/adapters/jdbc/derby.rb +2/-0
lib/sequel/adapters/jdbc/h2.rb +2/-2
lib/sequel/adapters/postgres.rb +1/-1
lib/sequel/adapters/shared/mssql.rb +3/-3
lib/sequel/adapters/shared/mysql.rb +5/-4
lib/sequel/adapters/shared/postgres.rb +16/-16
lib/sequel/adapters/shared/sqlite.rb +3/-3
lib/sequel/adapters/sqlite.rb +1/-1
lib/sequel/adapters/tinytds.rb +1/-1
lib/sequel/connection_pool.rb +6/-0
lib/sequel/connection_pool/sharded_timed_queue.rb +36/-11
lib/sequel/connection_pool/timed_queue.rb +27/-9
lib/sequel/database/schema_generator.rb +36/-5
lib/sequel/database/schema_methods.rb +1/-1
lib/sequel/dataset/placeholder_literalizer.rb +3/-0
lib/sequel/dataset/prepared_statements.rb +7/-4
lib/sequel/dataset/query.rb +6/-3
lib/sequel/dataset/sql.rb +6/-1
lib/sequel/extensions/connection_expiration.rb +1/-1
lib/sequel/extensions/connection_validator.rb +1/-1
lib/sequel/extensions/dataset_run.rb +2/-2
lib/sequel/extensions/date_arithmetic.rb +6/-6
lib/sequel/extensions/migration.rb +14/-17
lib/sequel/extensions/pg_enum.rb +1/-1
lib/sequel/model/associations.rb +180/-6
lib/sequel/plugins/dataset_associations.rb +20/-1
lib/sequel/plugins/dirty.rb +5/-2
lib/sequel/plugins/many_through_many.rb +21/-0
lib/sequel/plugins/mssql_optimistic_locking.rb +1/-1
lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1/-1
lib/sequel/plugins/serialization.rb +10/-1
lib/sequel/version.rb +1/-1
DIFFERENT extra_rdoc_files:
5.101.0->5.104.0:
* Changed:
MIT-LICENSE +1/-1 |
Contributor
gem compare --diff sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb
--- /tmp/20260504-1904-qas754 2026-05-04 02:36:52.185733780 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/connection_checkout_event_callback.rb 2026-05-04 02:36:52.144733761 +0000
@@ -0,0 +1,151 @@
+# frozen-string-literal: true
+#
+# The connection_checkout_event_callback extension modifies a database's
+# connection pool to allow for a checkout event callback. This callback is
+# called with the following arguments:
+#
+# :immediately_available :: Connection immediately available and returned
+# :not_immediately_available :: Connection not immediately available
+# :new_connection :: New connection created and returned
+# Float :: Number of seconds waiting to acquire a connection
+#
+# This is a low-level extension that allows for building telemetry
+# information. It doesn't implement any telemetry reporting itself. The
+# main reason for recording this information is to use it to determine the
+# appropriate size for the connection pool. Having too large a connection
+# pool can waste resources, while having too small a connection pool can
+# result in substantial time to check out a connection. In general, you
+# want to use as small a pool as possible while keeping the time to
+# checkout a connection low.
+#
+# To use the connection checkout event callback, you must first load the
+# extension:
+#
+# DB.extension(:connection_checkout_event_callback)
+#
+# By default, an empty proc is used as the callback so that loading the
+# support doesn't break anything. If you are using the extension, you
+# should set the callback at some point during application startup:
+#
+# DB.pool.on_checkout_event = proc do |event|
+# # ...
+# end
+#
+# When using the sharded connection pool, the callback is also
+# passed a second argument, the requested server shard (generally a
+# symbol), allowing for collection of per-shard telemetry:
+#
+# DB.pool.on_checkout_event = proc do |event, server|
+# # ...
+# end
+#
+# Note that the callback may be called currently by multiple threads.
+# You should use some form of concurrency control inside the callback,
+# such as a mutex or queue.
+#
+# Below is a brief example of usage to determine the percentage of
+# connection requests where a connection was immediately available:
+#
+# mutex = Mutex.new
+# total = immediates = 0
+#
+# DB.pool.on_checkout_event = proc do |event|
+# case event
+# when :immediately_available
+# mutex.synchronize do
+# total += 1
+# immediates += 1
+# end
+# when :not_immediately_available
+# mutex.synchronize do
+# total += 1
+# end
+# end
+# end
+#
+# immediate_percentage = lambda do
+# mutex.synchronize do
+# 100.0 * immediates / total
+# end
+# end
+#
+# Note that this extension only works with the timed_queue
+# and sharded_timed_queue connection pools (the default
+# connection pools when using Ruby 3.2+).
+#
+# Related modules: Sequel::ConnectionCheckoutEventCallbacks::TimedQueue,
+# Sequel::ConnectionCheckoutEventCallbacks::ShardedTimedQueue
+
+#
+module Sequel
+ module ConnectionCheckoutEventCallbacks
+ module TimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available)
+ conn
+ end
+
+ def try_make_new
+ conn = super
+ @on_checkout_event.call(:new_connection) if conn
+ conn
+ end
+
+ def wait_until_available
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer))
+ conn
+ end
+ end
+
+ module ShardedTimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available(queue, server)
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available, server)
+ conn
+ end
+
+ def try_make_new(server)
+ conn = super
+ @on_checkout_event.call(:new_connection, server) if conn
+ conn
+ end
+
+ def wait_until_available(queue, server)
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer), server)
+ conn
+ end
+ end
+ end
+
+ default_callback = proc{}
+
+ Database.register_extension(:connection_checkout_event_callback) do |db|
+ pool = db.pool
+
+ case pool.pool_type
+ when :timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::TimedQueue)
+ when :sharded_timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::ShardedTimedQueue)
+ else
+ raise Error, "the connection_checkout_event_callback extension is only supported when using a timed_queue connection pool"
+ end
+
+ pool.on_checkout_event ||= default_callback
+ end
+end
lib/sequel/extensions/lit_require_frozen.rb
--- /tmp/20260504-1904-x5a8vc 2026-05-04 02:36:52.191733783 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/lit_require_frozen.rb 2026-05-04 02:36:52.147733763 +0000
@@ -0,0 +1,131 @@
+# frozen-string-literal: true
+#
+# The lit_require_frozen extension disallows the use of unfrozen strings
+# as literal strings in database and dataset methods. If you try to use an
+# unfrozen string as a literal string for a dataset using this extension,
+# an exception will be raised.
+#
+# While this works for all Ruby versions, it is designed for use on Ruby 3+
+# where all files are using the frozen-string-literal magic comment. In this
+# case, uninterpolated literal strings are frozen, but interpolated strings
+# are not frozen. This allows you to catch potentially dangerous code:
+#
+# # Probably safe, no exception raised
+# DB["SELECT * FROM t WHERE c > :v", v: user_provided_string)
+#
+# # Potentially unsafe, raises Sequel::LitRequireFrozen::Error
+# DB["SELECT * FROM t WHERE c > '#{user_provided_string}'"]
+#
+# The assumption made is that a frozen string is unlikely to contain unsafe
+# input, while an unfrozen string has potentially been interpolated and may
+# contain unsafe input.
+#
+# This disallows the the following cases:
+#
+# * Sequel::LiteralString instances that are unfrozen and are not based on a
+# frozen string
+# * Sequel::SQL::PlaceholderLiteralString instances when the placeholder string
+# is not frozen
+# * Unfrozen strings passed to Database#<< or #[] or Dataset#with_sql
+#
+# To use this extension, load it into the database:
+#
+# DB.extension :lit_require_frozen
+#
+# It can also be loaded into individual datasets:
+#
+# ds = DB[:t].extension(:lit_require_frozen)
+#
+# Assuming you have good test coverage, it is recommended to only load
+# this extension when testing.
+#
+# Related module: Sequel::LitRequireFrozen
+
+#
+module Sequel
+ class LiteralString
+ # The string used when creating the literal string (first argument to
+ # Sequel::LiteralString.new). This may be nil if no string was provided,
+ # or if the litral string was created before this extension was required.
+ attr_reader :source
+
+ def initialize(*a)
+ @source = a.first
+ super
+ end
+ # :nocov:
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
+ # :nocov:
+ end
+
+ module LitRequireFrozen
+ # Error class raised for using unfrozen literal string.
+ class Error < Sequel::Error
+ end
+
+ module DatabaseMethods
+ def self.extended(db)
+ db.extend_datasets(DatasetMethods)
+ end
+
+ # Check given SQL is frozen before running it.
+ def run(sql, opts=OPTS)
+ @default_dataset.with_sql(sql)
+ super
+ end
+ end
+
+ module DatasetMethods
+ # Check given SQL is not an unfrozen string.
+ def with_sql(sql, *args)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+
+ # Check that placeholder string is frozen (or all entries
+ # in placeholder array are frozen).
+ def placeholder_literal_string_sql_append(sql, pls)
+ str = pls.str
+
+ if str.is_a?(Array)
+ str.each do |s|
+ _check_unfrozen_literal_string(s)
+ end
+ else
+ _check_unfrozen_literal_string(str)
+ end
+
+ super
+ end
+
+ private
+
+ # Base method that other methods used to check for whether a string should be allowed
+ # as literal SQL. Allows non-strings as well as frozen strings.
+ def _check_unfrozen_literal_string(str)
+ return if !str.is_a?(String) || str.frozen?
+
+ if str.is_a?(LiteralString)
+ _check_unfrozen_literal_string(str.source)
+ else
+ raise Error, "cannot treat unfrozen string as literal SQL: #{str.inspect}"
+ end
+ end
+
+ # Check literal strings appended to SQL.
+ def literal_literal_string_append(sql, v)
+ _check_unfrozen_literal_string(v)
+ super
+ end
+
+ # Check static SQL is not frozen.
+ def static_sql(sql)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+ end
+ end
+
+ Dataset.register_extension(:lit_require_frozen, LitRequireFrozen::DatasetMethods)
+ Database.register_extension(:lit_require_frozen, LitRequireFrozen::DatabaseMethods)
+end
* Changed:
MIT-LICENSE
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/MIT-LICENSE 2026-05-04 02:36:52.091733736 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/MIT-LICENSE 2026-05-04 02:36:52.130733755 +0000
@@ -2 +2 @@
-Copyright (c) 2008-2023 Jeremy Evans
+Copyright (c) 2008-2026 Jeremy Evans and contributors
lib/sequel/adapters/jdbc/derby.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:36:52.093733737 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:36:52.131733755 +0000
@@ -2,0 +3,2 @@
+# SEQUEL6: Remove
+
lib/sequel/adapters/jdbc/h2.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:36:52.093733737 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:36:52.131733755 +0000
@@ -21 +21 @@
- run("COMMIT TRANSACTION #{transaction_id}", opts)
+ run("COMMIT TRANSACTION #{transaction_id}".freeze, opts)
@@ -39 +39 @@
- run("ROLLBACK TRANSACTION #{transaction_id}", opts)
+ run("ROLLBACK TRANSACTION #{transaction_id}".freeze, opts)
lib/sequel/adapters/postgres.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:36:52.096733739 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:36:52.133733756 +0000
@@ -730 +730 @@
- LiteralString.new("#{prepared_arg_placeholder}#{i}")
+ LiteralString.new("#{prepared_arg_placeholder}#{i}".freeze)
lib/sequel/adapters/shared/mssql.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:36:52.096733739 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:36:52.134733757 +0000
@@ -109 +109 @@
- sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}".freeze
@@ -403 +403 @@
- run(ds.into(name).sql)
+ run(ds.into(name).sql.freeze)
@@ -881 +881 @@
- sql = multi_insert_sql(columns, values)[0]
+ sql = multi_insert_sql(columns, values)[0].freeze
lib/sequel/adapters/shared/mysql.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:36:52.097733739 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:36:52.134733757 +0000
@@ -43 +43 @@
- run("XA COMMIT #{literal(transaction_id)}", opts)
+ run("XA COMMIT #{literal(transaction_id)}".freeze, opts)
@@ -106 +106 @@
- metadata_dataset.with_sql(sql).each do |r|
+ metadata_dataset.with_sql(sql.freeze).each do |r|
@@ -118 +118 @@
- run("XA ROLLBACK #{literal(transaction_id)}", opts)
+ run("XA ROLLBACK #{literal(transaction_id)}".freeze, opts)
@@ -783 +783,2 @@
- ds = db.send(:metadata_dataset).with_sql(((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql).naked
+ sql = ((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql
+ ds = db.send(:metadata_dataset).with_sql(sql.freeze).naked
lib/sequel/adapters/shared/postgres.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:36:52.098733740 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:36:52.136733757 +0000
@@ -342 +342 @@
- run("COMMIT PREPARED #{literal(transaction_id)}", opts)
+ run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -419 +419 @@
- run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT", server_hash)
+ run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT".freeze, server_hash)
@@ -456 +456 @@
- self << create_function_sql(name, definition, opts)
+ self << create_function_sql(name, definition, opts).freeze
@@ -467 +467 @@
- self << create_language_sql(name, opts)
+ self << create_language_sql(name, opts).freeze
@@ -476 +476 @@
- self << create_schema_sql(name, opts)
+ self << create_schema_sql(name, opts).freeze
@@ -512 +512 @@
- self << create_trigger_sql(table, name, function, opts)
+ self << create_trigger_sql(table, name, function, opts).freeze
@@ -545 +545 @@
- run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}".freeze
@@ -555 +555 @@
- self << drop_function_sql(name, opts)
+ self << drop_function_sql(name, opts).freeze
@@ -564 +564 @@
- self << drop_language_sql(name, opts)
+ self << drop_language_sql(name, opts).freeze
@@ -573 +573 @@
- self << drop_schema_sql(name, opts)
+ self << drop_schema_sql(name, opts).freeze
@@ -584 +584 @@
- self << drop_trigger_sql(table, name, opts)
+ self << drop_trigger_sql(table, name, opts).freeze
@@ -748 +748 @@
- self << rename_schema_sql(name, new_name)
+ self << rename_schema_sql(name, new_name).freeze
@@ -759 +759 @@
- run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}".freeze
@@ -772 +772 @@
- seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
+ seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
@@ -787 +787 @@
- run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
+ run("ROLLBACK PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -1472 +1472 @@
- cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}")
+ cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}".freeze)
lib/sequel/adapters/shared/sqlite.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:36:52.098733740 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:36:52.136733757 +0000
@@ -198 +198 @@
- metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)".freeze, input_identifier_meth(opts[:dataset]).call(table_name))
@@ -410 +410 @@
- c[:default] = LiteralString.new(c[:default]) if c[:default]
+ c[:default] = LiteralString.new(c[:default]).freeze if c[:default]
@@ -687 +687 @@
- ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}".freeze)
lib/sequel/adapters/sqlite.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:36:52.098733740 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:36:52.137733758 +0000
@@ -391 +391 @@
- LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/adapters/tinytds.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:36:52.099733740 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:36:52.137733758 +0000
@@ -208 +208 @@
- LiteralString.new("@#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/connection_pool.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/connection_pool.rb 2026-05-04 02:36:52.100733741 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/connection_pool.rb 2026-05-04 02:36:52.138733759 +0000
@@ -133,0 +134,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, *)
+ disconnect_connection(conn)
+ end
+
lib/sequel/connection_pool/sharded_timed_queue.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:36:52.100733741 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:36:52.138733759 +0000
@@ -74 +74 @@
- break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available(queue, server)) && !conns[conn]
@@ -94 +94 @@
- # and then the method will only disconnect connectsion from those specified shards.
+ # and then the method will only disconnect connections from those specified shards.
@@ -98 +98,3 @@
- while conn = queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available(queue, server)
+ nconns += 1
@@ -101 +103 @@
- fill_queue(server)
+ fill_queue(server, nconns)
@@ -135 +137 @@
- fill_queue(server)
+ fill_queue(server, 1)
@@ -170 +172 @@
- while conn = queue.pop(timeout: 0)
+ while conn = available(queue, server)
@@ -227,0 +230,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, _, server)
+ disconnect_pool_connection(conn, server)
+ end
+
@@ -247 +255,4 @@
- def fill_queue(server)
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(server, nconns)
@@ -249 +260 @@
- if queue.num_waiting > 0
+ if nconns > 0 && queue.num_waiting > 0
@@ -251 +262,2 @@
- while queue.num_waiting > 0 && (conn = try_make_new(server))
+ while nconns > 0 && queue.num_waiting > 0 && (conn = try_make_new(server))
+ nconns -= 1
@@ -300 +312 @@
- fill_queue(server)
+ fill_queue(server, to_disconnect.size)
@@ -314 +326 @@
- if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
+ if conn = available(queue, server) || try_make_new(server) || wait_until_available(queue, server)
@@ -319,0 +332,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available(queue, _server)
+ queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available(queue, _server)
+ queue.pop(timeout: @timeout)
lib/sequel/connection_pool/timed_queue.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:36:52.100733741 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:36:52.139733759 +0000
@@ -45 +45 @@
- break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available) && !conns[conn]
@@ -62 +62,3 @@
- while conn = @queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available
+ nconns += 1
@@ -65 +67 @@
- fill_queue
+ fill_queue(nconns)
@@ -97 +99 @@
- fill_queue
+ fill_queue(1)
@@ -159,2 +161,5 @@
- def fill_queue
- if @queue.num_waiting > 0
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(nconns)
+ if nconns > 0 && @queue.num_waiting > 0
@@ -162 +167 @@
- while @queue.num_waiting > 0 && (conn = try_make_new)
+ while nconns > 0 && @queue.num_waiting > 0 && (conn = try_make_new)
@@ -210 +215 @@
- fill_queue
+ fill_queue(to_disconnect.size)
@@ -223 +228 @@
- if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
+ if conn = available || try_make_new || wait_until_available
@@ -228,0 +234,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available
+ @queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available
+ @queue.pop(timeout: @timeout)
lib/sequel/database/schema_generator.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:36:52.102733742 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:36:52.140733759 +0000
@@ -5,0 +6,27 @@
+ module ColumnOptionMerger
+ private
+
+ # Merge given options into the column's default options. For backwards compatibility,
+ # the options take priority, but in cases where the option value overrides the argument
+ # value, and the values are different, we warn as this is likely to be an error in the
+ # code.
+ def _merge_column_options(defaults, opts)
+ defaults.merge!(opts) do |k, defv, v|
+ unless defv == v
+ # :nocov:
+ if RUBY_VERSION >= "3.2"
+ # :nocov:
+ caller_loc = Thread.each_caller_location do |loc|
+ break loc unless loc.path == __FILE__
+ end
+ caller_loc &&= "#{caller_loc.path}:#{caller_loc.lineno}: "
+ end
+ warn("#{caller_loc}#{k.inspect} option value (#{v.inspect}) overrides argument value (#{defv.inspect})")
+ end
+
+ v
+ end
+ end
+ end
+ private_constant :ColumnOptionMerger
+
@@ -19,0 +47,2 @@
+ include ColumnOptionMerger
+
@@ -176 +205 @@
- columns << {:name => name, :type => type}.merge!(opts)
+ columns << _merge_column_options({:name => name, :type => type}, opts)
@@ -182 +211 @@
-
+
@@ -249 +278 @@
- column(name, Integer, opts)
+ column(name, opts.fetch(:type, Integer), opts)
@@ -431,0 +461,2 @@
+ include ColumnOptionMerger
+
@@ -457 +488 @@
- op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
+ op = _merge_column_options({:op => :add_column, :name => name, :type => type}, opts)
@@ -522 +553 @@
- add_column(name, Integer, {:table=>table}.merge!(opts))
+ add_column(name, opts.fetch(:type, Integer), {:table=>table}.merge!(opts))
lib/sequel/database/schema_methods.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:36:52.102733742 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:36:52.140733759 +0000
@@ -822 +822 @@
- run(create_table_as_sql(name, sql, options))
+ run(create_table_as_sql(name, sql, options).freeze)
lib/sequel/dataset/placeholder_literalizer.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:36:52.104733743 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:36:52.142733760 +0000
@@ -119,0 +120,2 @@
+ frags.each(&:freeze)
+ frags.freeze
@@ -127,0 +130 @@
+ ds.opts[:sql].freeze
lib/sequel/dataset/prepared_statements.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:36:52.104733743 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:36:52.142733760 +0000
@@ -57 +57 @@
- sql = prepared_sql
+ sql = prepared_sql.freeze
@@ -226 +226 @@
- with_sql(prepared_sql)
+ with_sql(prepared_sql.freeze)
@@ -290 +290 @@
- sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
+ sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false)).freeze
@@ -322,0 +323,3 @@
+ frags.freeze
+ frags.each(&:freeze)
+ prepared_sql.freeze
@@ -412 +415 @@
- sql = ps.prepared_sql
+ sql = ps.prepared_sql.freeze
lib/sequel/dataset/query.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/dataset/query.rb 2026-05-04 02:36:52.104733743 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/dataset/query.rb 2026-05-04 02:36:52.142733760 +0000
@@ -1304 +1304 @@
- sql = public_send(sql, *args)
+ sql = public_send(sql, *args).freeze
@@ -1477 +1477,4 @@
-
+
+ PAREN_WRAPPER = ["(".freeze, ")".freeze].freeze
+ private_constant :PAREN_WRAPPER
+
@@ -1498 +1501 @@
- LiteralString.new("(#{expr})")
+ SQL::PlaceholderLiteralString.new(PAREN_WRAPPER, [expr])
lib/sequel/dataset/sql.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/dataset/sql.rb 2026-05-04 02:36:52.105733743 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/dataset/sql.rb 2026-05-04 02:36:52.143733761 +0000
@@ -56 +56 @@
- sql << v
+ literal_literal_string_append(sql, v)
@@ -1424,0 +1425,5 @@
+ end
+
+ # Append string to SQL string.
+ def literal_literal_string_append(sql, v)
+ sql << v
lib/sequel/extensions/connection_expiration.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:36:52.106733743 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:36:52.144733761 +0000
@@ -94 +94 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/connection_validator.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:36:52.106733743 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:36:52.145733762 +0000
@@ -121 +121 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/dataset_run.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:36:52.107733744 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:36:52.145733762 +0000
@@ -33 +33 @@
- db.run(sql, :server=>server)
+ db.run(sql.freeze, :server=>server)
@@ -35 +35 @@
- db.run(sql)
+ db.run(sql.freeze)
lib/sequel/extensions/date_arithmetic.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:36:52.107733744 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:36:52.146733762 +0000
@@ -85,2 +85,2 @@
- MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
- MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
+ MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1].freeze).freeze}).freeze
+ MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1].freeze).freeze}).freeze
@@ -88 +88 @@
- DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
+ DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}".freeze).freeze}).freeze
@@ -90 +90 @@
- DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
+ DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.freeze).freeze}).freeze
@@ -110 +110 @@
- placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := ".freeze
@@ -159 +159 @@
- expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ", ", timestamp(", "))}"], value, expr)
+ expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ".freeze, ", timestamp(", "))}"], value, expr)
lib/sequel/extensions/migration.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/extensions/migration.rb 2026-05-04 02:36:52.109733745 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/migration.rb 2026-05-04 02:36:52.148733763 +0000
@@ -189,16 +189,13 @@
- begin
- instance_exec(&block)
- rescue
- just_raise = true
- end
- if just_raise
- Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
- else
- actions = @actions.reverse
- Proc.new do
- actions.each do |a|
- pr = a.last.is_a?(Proc) ? a.pop : nil
- # Allow calling private methods as the reversing methods are private
- send(*a, &pr)
- end
- end
+ instance_exec(&block)
+ rescue NoMethodError => e
+ 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"}
+ rescue => e
+ 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"}
+ else
+ actions = @actions.reverse
+ Proc.new do
+ actions.each do |a|
+ pr = a.last.is_a?(Proc) ? a.pop : nil
+ # Allow calling private methods as the reversing methods are private
+ send(*a, &pr)
+ end
@@ -273 +270 @@
- raise if args.first.is_a?(Array)
+ super if args.first.is_a?(Array)
lib/sequel/extensions/pg_enum.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:36:52.110733745 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:36:52.149733764 +0000
@@ -130 +130 @@
- run(sql)
+ run(sql.freeze)
lib/sequel/model/associations.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/model/associations.rb 2026-05-04 02:36:52.117733749 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/model/associations.rb 2026-05-04 02:36:52.156733767 +0000
@@ -114,0 +115,2 @@
+ when :lateral_subquery
+ apply_lateral_subquery_eager_graph_limit_strategy(ds)
@@ -301 +303,6 @@
- objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
+
+ objects = if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
+ else
+ apply_eager_limit_strategy(ds, strategy, eager_limit).all
+ end
@@ -323 +330 @@
- objects.concat(ds.with_sql(sql).to_a)
+ objects.concat(ds.with_sql(sql.freeze).to_a)
@@ -369 +376,6 @@
- ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ ds = if filter_by_associations_limit_strategy == :lateral_subquery
+ apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
+ else
+ filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ end
+
@@ -756,0 +769,55 @@
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds.
+ where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys))).
+ limit(*limit_and_offset).
+ lateral
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ associated_table_name = associated_class.table_name
+
+ associated_class.
+ from(table_name).
+ select_all(associated_table_name).
+ join(lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset).as(associated_table_name), true).
+ order(*self[:order])
+ end
+
+ # Return an expression to filter the filter by associations dataset to only
+ # rows related to given objects.
+ def _lateral_subquery_filter_limit_strategy_conditions(obj, key, value_method, value_column)
+ value = case obj
+ when Array
+ if key.is_a?(Array)
+ key_methods = Array(value_method)
+ obj.map{|o| key_methods.map{|meth| o.send(meth)}}
+ else
+ obj.map{|o| o.send(value_method)}
+ end
+ when Sequel::Dataset
+ obj.select(*Array(qualify(associated_class.table_name, value_column)))
+ else
+ if key.is_a?(Array)
+ return Array(key).zip(Array(value_method).map{|meth| obj.send(meth)})
+ else
+ obj.send(value_method)
+ end
+ end
+
+ {key => value}
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds.
+ select(*qualify(associated_class.table_name, associated_class.primary_key)).
+ limit(*limit_and_offset).
+ lateral)
+ end
+
+ def apply_lateral_subquery_filter_limit_strategy(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_dataset(self[:model].
+ select(*lateral_subquery_filter_limit_strategy_lateral_dataset_select).
+ join(lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj).as(associated_class.table_name), filter_by_associations_conditions_subquery_conditions(obj)), obj)
+ end
+
@@ -779 +846,6 @@
- apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
+ strategy = eager_limit_strategy
+ if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, arg, limit_and_offset)
+ else
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), strategy)
+ end
@@ -1021 +1093 @@
- # Support a correlated subquery limit strategy when using eager_graph.
+ # Support a lateral_subquery and correlated_subquery limit strategy when using eager_graph.
@@ -1120,0 +1193,48 @@
+ # Use a LATERAL subquery to limit the dataset. Note that this will not
+ # work correctly if the associated dataset uses qualified identifers in the WHERE clause,
+ # as they would reference the containing query instead of the subquery.
+ #
+ # This does not contain the conditions that are necessary to join to the
+ # query, since the necessary qualifier is not passed as an argument.
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ table_name = ds.first_source_alias
+ qualifier = ds.opts[:eager_options][:implicit_qualifier]
+ graph_conditions = self[:_graph_conditions]
+
+ unless Sequel.condition_specifier?(graph_conditions)
+ raise Error, "lateral_subquery eager graph limit strategy only supported when graph conditions are a hash or array of pairs"
+ end
+
+ ds.
+ limit(*limit_and_offset).
+ order(*self[:order]).
+ where(graph_conditions.map{|k, v| [qualify(table_name, k), qualify(qualifier, v)]}).
+ lateral
+ end
+
+ # Avoid setting duplicate predicate condition when using the lateral subquery
+ # eager limit strategy.
+ def eager_loading_set_predicate_condition(ds, eo)
+ eager_limit_strategy == :lateral_subquery ? ds : super
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ super.where(qualify(self[:model].table_name, self[:primary_key]) => ids)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, filter_by_associations_conditions_key, self[:key_method], self[:key])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(lateral_subquery_filter_limit_strategy_conditions(obj))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualified_primary_key
+ end
+
@@ -1434,0 +1555,46 @@
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, lateral_subquery_filter_limit_strategy_conditions_key, right_primary_key_method, right_primary_key)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ self[:right_key]
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(filter_by_associations_conditions_key => ds.db.from(self[:join_table]).
+ select(*self[:left_key]).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualify(self[:model].table_name, self[:left_primary_key])
+ end
+
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds = super
+ select = ds.opts[:select].dup
+ left_key_alias = self[:left_key_alias]
+ select.pop while (s = select.last).is_a?(Sequel::SQL::AliasedExpression) && (left_key_alias.is_a?(Array) ? left_key_alias.include?(s.alias) : s.alias == left_key_alias)
+ ds = ds.clone(:select=>select)
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ super.
+ select_all(associated_class.table_name).
+ select_append(*qualify(table_name, self[:left_primary_keys]).zip(Array(self[:left_key_alias])).map{|id, aliaz| Sequel.as(id, aliaz)}).
+ where(qualify(table_name, self[:left_primary_key]) => ids)
+ end
+
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ ds.
+ limit(*limit_and_offset).
+ where(qualify(join_table_alias, self[:left_keys]).zip(qualify(ds.opts[:eager_options][:implicit_qualifier], self[:left_primary_key_columns]))).
+ order(*self[:order]).
+ lateral
+ end
+
@@ -2415,0 +2582 @@
+ graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
@@ -2418 +2585,5 @@
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
+ graph_limit_strategy = eo[:limit_strategy]
+ egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
+ graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
+
+ ds = ds.graph(egds, graph_conditions_true || graph_conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
@@ -2500,0 +2672,3 @@
+ if eager_options[:limit_strategy] == :lateral_subquery
+ ds = ds.clone(:eager_options=>eager_options)
+ end
lib/sequel/plugins/dataset_associations.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:36:52.121733750 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:36:52.163733770 +0000
@@ -85 +85 @@
- ds = r.associated_class.dataset
+ klass = r.associated_class
@@ -86,0 +87,19 @@
+
+ if r.send(:filter_by_associations_limit_strategy) == :lateral_subquery
+ ds = r.send(:associated_eager_dataset)
+
+ case r[:type]
+ when :one_to_one, :one_to_many
+ sds = sds.select(*Array(r.qualified_primary_key))
+ else
+ sds = sds.select(*r[:left_primary_keys])
+ ds = ds.select_all(klass.table_name)
+ update_select = true
+ end
+
+ ds = r.send(:apply_lateral_subquery_eager_limit_strategy, ds, sds, r.limit_and_offset)
+ ds = ds.clone(:select=>ds.opts[:select][0,1]) if update_select
+ return ds.clone(:eager=>nil, :eager_graph=>nil)
+ end
+
+ ds = klass.dataset
lib/sequel/plugins/dirty.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:36:52.121733750 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:36:52.166733772 +0000
@@ -237,2 +237,5 @@
- check_missing_initial_value(column)
- iv[column] = get_column_value(column)
+ if db_schema[column]
+ check_missing_initial_value(column)
+ iv[column] = get_column_value(column)
+ end
+
lib/sequel/plugins/many_through_many.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:36:52.124733752 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:36:52.171733774 +0000
@@ -148,0 +149,21 @@
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ qualify(reverse_edges.first[:table], reverse_edges.first[:left])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(qualify(edges.first[:table], edges.first[:right])).zip(Array(qualify(self[:model].table_name, edges.first[:left]))))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ first_edge, *remaining_edges = edges
+ filter_ds = ds.db.from(first_edge[:table]).
+ select(*qualify(first_edge[:table], first_edge[:right])).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj))
+
+ remaining_edges.each do |edge|
+ filter_ds = filter_ds.join(edge[:table], Array(edge[:right]).zip(Array(edge[:left])))
+ end
+
+ ds.where(qualify(self[:model].table_name, self[:left_primary_key]) => filter_ds)
+ end
+
lib/sequel/plugins/mssql_optimistic_locking.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:36:52.124733752 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:36:52.171733774 +0000
@@ -58 +58 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
lib/sequel/plugins/pg_xmin_optimistic_locking.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:36:52.125733752 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:36:52.173733775 +0000
@@ -102 +102 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
lib/sequel/plugins/serialization.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:36:52.125733752 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:36:52.174733775 +0000
@@ -32,0 +33,9 @@
+ # Note that use of an unsafe serialization method can result in an attack vector
+ # (potentially allowing remote code execution) if an attacker has the ability to
+ # store data directly in the underlying column. This would affect the marshal
+ # serialization format, and on older versions of Ruby, potentially the yaml and
+ # json serialization formats as well. It can also affect custom formats. You
+ # should ensure that attackers do not have access to store data directly in the
+ # underlying column when using this plugin (especially when using an unsafe
+ # serialization method).
+ #
@@ -100 +109 @@
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
lib/sequel/version.rb
--- /tmp/d20260504-1904-vw3u71/sequel-5.101.0/lib/sequel/version.rb 2026-05-04 02:36:52.129733754 +0000
+++ /tmp/d20260504-1904-vw3u71/sequel-5.104.0/lib/sequel/version.rb 2026-05-04 02:36:52.183733780 +0000
@@ -9 +9 @@
- MINOR = 101
+ MINOR = 104 |
Contributor
gem compare --diff sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb
--- /tmp/20260504-1830-1m2xxg 2026-05-04 02:37:12.336769302 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/connection_checkout_event_callback.rb 2026-05-04 02:37:12.313769445 +0000
@@ -0,0 +1,151 @@
+# frozen-string-literal: true
+#
+# The connection_checkout_event_callback extension modifies a database's
+# connection pool to allow for a checkout event callback. This callback is
+# called with the following arguments:
+#
+# :immediately_available :: Connection immediately available and returned
+# :not_immediately_available :: Connection not immediately available
+# :new_connection :: New connection created and returned
+# Float :: Number of seconds waiting to acquire a connection
+#
+# This is a low-level extension that allows for building telemetry
+# information. It doesn't implement any telemetry reporting itself. The
+# main reason for recording this information is to use it to determine the
+# appropriate size for the connection pool. Having too large a connection
+# pool can waste resources, while having too small a connection pool can
+# result in substantial time to check out a connection. In general, you
+# want to use as small a pool as possible while keeping the time to
+# checkout a connection low.
+#
+# To use the connection checkout event callback, you must first load the
+# extension:
+#
+# DB.extension(:connection_checkout_event_callback)
+#
+# By default, an empty proc is used as the callback so that loading the
+# support doesn't break anything. If you are using the extension, you
+# should set the callback at some point during application startup:
+#
+# DB.pool.on_checkout_event = proc do |event|
+# # ...
+# end
+#
+# When using the sharded connection pool, the callback is also
+# passed a second argument, the requested server shard (generally a
+# symbol), allowing for collection of per-shard telemetry:
+#
+# DB.pool.on_checkout_event = proc do |event, server|
+# # ...
+# end
+#
+# Note that the callback may be called currently by multiple threads.
+# You should use some form of concurrency control inside the callback,
+# such as a mutex or queue.
+#
+# Below is a brief example of usage to determine the percentage of
+# connection requests where a connection was immediately available:
+#
+# mutex = Mutex.new
+# total = immediates = 0
+#
+# DB.pool.on_checkout_event = proc do |event|
+# case event
+# when :immediately_available
+# mutex.synchronize do
+# total += 1
+# immediates += 1
+# end
+# when :not_immediately_available
+# mutex.synchronize do
+# total += 1
+# end
+# end
+# end
+#
+# immediate_percentage = lambda do
+# mutex.synchronize do
+# 100.0 * immediates / total
+# end
+# end
+#
+# Note that this extension only works with the timed_queue
+# and sharded_timed_queue connection pools (the default
+# connection pools when using Ruby 3.2+).
+#
+# Related modules: Sequel::ConnectionCheckoutEventCallbacks::TimedQueue,
+# Sequel::ConnectionCheckoutEventCallbacks::ShardedTimedQueue
+
+#
+module Sequel
+ module ConnectionCheckoutEventCallbacks
+ module TimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available)
+ conn
+ end
+
+ def try_make_new
+ conn = super
+ @on_checkout_event.call(:new_connection) if conn
+ conn
+ end
+
+ def wait_until_available
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer))
+ conn
+ end
+ end
+
+ module ShardedTimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available(queue, server)
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available, server)
+ conn
+ end
+
+ def try_make_new(server)
+ conn = super
+ @on_checkout_event.call(:new_connection, server) if conn
+ conn
+ end
+
+ def wait_until_available(queue, server)
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer), server)
+ conn
+ end
+ end
+ end
+
+ default_callback = proc{}
+
+ Database.register_extension(:connection_checkout_event_callback) do |db|
+ pool = db.pool
+
+ case pool.pool_type
+ when :timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::TimedQueue)
+ when :sharded_timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::ShardedTimedQueue)
+ else
+ raise Error, "the connection_checkout_event_callback extension is only supported when using a timed_queue connection pool"
+ end
+
+ pool.on_checkout_event ||= default_callback
+ end
+end
lib/sequel/extensions/lit_require_frozen.rb
--- /tmp/20260504-1830-fo2wff 2026-05-04 02:37:12.410768845 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/lit_require_frozen.rb 2026-05-04 02:37:12.315769432 +0000
@@ -0,0 +1,131 @@
+# frozen-string-literal: true
+#
+# The lit_require_frozen extension disallows the use of unfrozen strings
+# as literal strings in database and dataset methods. If you try to use an
+# unfrozen string as a literal string for a dataset using this extension,
+# an exception will be raised.
+#
+# While this works for all Ruby versions, it is designed for use on Ruby 3+
+# where all files are using the frozen-string-literal magic comment. In this
+# case, uninterpolated literal strings are frozen, but interpolated strings
+# are not frozen. This allows you to catch potentially dangerous code:
+#
+# # Probably safe, no exception raised
+# DB["SELECT * FROM t WHERE c > :v", v: user_provided_string)
+#
+# # Potentially unsafe, raises Sequel::LitRequireFrozen::Error
+# DB["SELECT * FROM t WHERE c > '#{user_provided_string}'"]
+#
+# The assumption made is that a frozen string is unlikely to contain unsafe
+# input, while an unfrozen string has potentially been interpolated and may
+# contain unsafe input.
+#
+# This disallows the the following cases:
+#
+# * Sequel::LiteralString instances that are unfrozen and are not based on a
+# frozen string
+# * Sequel::SQL::PlaceholderLiteralString instances when the placeholder string
+# is not frozen
+# * Unfrozen strings passed to Database#<< or #[] or Dataset#with_sql
+#
+# To use this extension, load it into the database:
+#
+# DB.extension :lit_require_frozen
+#
+# It can also be loaded into individual datasets:
+#
+# ds = DB[:t].extension(:lit_require_frozen)
+#
+# Assuming you have good test coverage, it is recommended to only load
+# this extension when testing.
+#
+# Related module: Sequel::LitRequireFrozen
+
+#
+module Sequel
+ class LiteralString
+ # The string used when creating the literal string (first argument to
+ # Sequel::LiteralString.new). This may be nil if no string was provided,
+ # or if the litral string was created before this extension was required.
+ attr_reader :source
+
+ def initialize(*a)
+ @source = a.first
+ super
+ end
+ # :nocov:
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
+ # :nocov:
+ end
+
+ module LitRequireFrozen
+ # Error class raised for using unfrozen literal string.
+ class Error < Sequel::Error
+ end
+
+ module DatabaseMethods
+ def self.extended(db)
+ db.extend_datasets(DatasetMethods)
+ end
+
+ # Check given SQL is frozen before running it.
+ def run(sql, opts=OPTS)
+ @default_dataset.with_sql(sql)
+ super
+ end
+ end
+
+ module DatasetMethods
+ # Check given SQL is not an unfrozen string.
+ def with_sql(sql, *args)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+
+ # Check that placeholder string is frozen (or all entries
+ # in placeholder array are frozen).
+ def placeholder_literal_string_sql_append(sql, pls)
+ str = pls.str
+
+ if str.is_a?(Array)
+ str.each do |s|
+ _check_unfrozen_literal_string(s)
+ end
+ else
+ _check_unfrozen_literal_string(str)
+ end
+
+ super
+ end
+
+ private
+
+ # Base method that other methods used to check for whether a string should be allowed
+ # as literal SQL. Allows non-strings as well as frozen strings.
+ def _check_unfrozen_literal_string(str)
+ return if !str.is_a?(String) || str.frozen?
+
+ if str.is_a?(LiteralString)
+ _check_unfrozen_literal_string(str.source)
+ else
+ raise Error, "cannot treat unfrozen string as literal SQL: #{str.inspect}"
+ end
+ end
+
+ # Check literal strings appended to SQL.
+ def literal_literal_string_append(sql, v)
+ _check_unfrozen_literal_string(v)
+ super
+ end
+
+ # Check static SQL is not frozen.
+ def static_sql(sql)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+ end
+ end
+
+ Dataset.register_extension(:lit_require_frozen, LitRequireFrozen::DatasetMethods)
+ Database.register_extension(:lit_require_frozen, LitRequireFrozen::DatabaseMethods)
+end
* Changed:
MIT-LICENSE
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/MIT-LICENSE 2026-05-04 02:37:12.264769748 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/MIT-LICENSE 2026-05-04 02:37:12.300769525 +0000
@@ -2 +2 @@
-Copyright (c) 2008-2023 Jeremy Evans
+Copyright (c) 2008-2026 Jeremy Evans and contributors
lib/sequel/adapters/jdbc/derby.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:37:12.266769735 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:37:12.301769519 +0000
@@ -2,0 +3,2 @@
+# SEQUEL6: Remove
+
lib/sequel/adapters/jdbc/h2.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:37:12.266769735 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:37:12.301769519 +0000
@@ -21 +21 @@
- run("COMMIT TRANSACTION #{transaction_id}", opts)
+ run("COMMIT TRANSACTION #{transaction_id}".freeze, opts)
@@ -39 +39 @@
- run("ROLLBACK TRANSACTION #{transaction_id}", opts)
+ run("ROLLBACK TRANSACTION #{transaction_id}".freeze, opts)
lib/sequel/adapters/postgres.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:37:12.269769717 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:37:12.303769507 +0000
@@ -730 +730 @@
- LiteralString.new("#{prepared_arg_placeholder}#{i}")
+ LiteralString.new("#{prepared_arg_placeholder}#{i}".freeze)
lib/sequel/adapters/shared/mssql.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:37:12.269769717 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:37:12.304769500 +0000
@@ -109 +109 @@
- sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}".freeze
@@ -403 +403 @@
- run(ds.into(name).sql)
+ run(ds.into(name).sql.freeze)
@@ -881 +881 @@
- sql = multi_insert_sql(columns, values)[0]
+ sql = multi_insert_sql(columns, values)[0].freeze
lib/sequel/adapters/shared/mysql.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:37:12.270769711 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:37:12.304769500 +0000
@@ -43 +43 @@
- run("XA COMMIT #{literal(transaction_id)}", opts)
+ run("XA COMMIT #{literal(transaction_id)}".freeze, opts)
@@ -106 +106 @@
- metadata_dataset.with_sql(sql).each do |r|
+ metadata_dataset.with_sql(sql.freeze).each do |r|
@@ -118 +118 @@
- run("XA ROLLBACK #{literal(transaction_id)}", opts)
+ run("XA ROLLBACK #{literal(transaction_id)}".freeze, opts)
@@ -783 +783,2 @@
- ds = db.send(:metadata_dataset).with_sql(((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql).naked
+ sql = ((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql
+ ds = db.send(:metadata_dataset).with_sql(sql.freeze).naked
lib/sequel/adapters/shared/postgres.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:37:12.270769711 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:37:12.305769494 +0000
@@ -342 +342 @@
- run("COMMIT PREPARED #{literal(transaction_id)}", opts)
+ run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -419 +419 @@
- run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT", server_hash)
+ run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT".freeze, server_hash)
@@ -456 +456 @@
- self << create_function_sql(name, definition, opts)
+ self << create_function_sql(name, definition, opts).freeze
@@ -467 +467 @@
- self << create_language_sql(name, opts)
+ self << create_language_sql(name, opts).freeze
@@ -476 +476 @@
- self << create_schema_sql(name, opts)
+ self << create_schema_sql(name, opts).freeze
@@ -512 +512 @@
- self << create_trigger_sql(table, name, function, opts)
+ self << create_trigger_sql(table, name, function, opts).freeze
@@ -545 +545 @@
- run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}".freeze
@@ -555 +555 @@
- self << drop_function_sql(name, opts)
+ self << drop_function_sql(name, opts).freeze
@@ -564 +564 @@
- self << drop_language_sql(name, opts)
+ self << drop_language_sql(name, opts).freeze
@@ -573 +573 @@
- self << drop_schema_sql(name, opts)
+ self << drop_schema_sql(name, opts).freeze
@@ -584 +584 @@
- self << drop_trigger_sql(table, name, opts)
+ self << drop_trigger_sql(table, name, opts).freeze
@@ -748 +748 @@
- self << rename_schema_sql(name, new_name)
+ self << rename_schema_sql(name, new_name).freeze
@@ -759 +759 @@
- run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}".freeze
@@ -772 +772 @@
- seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
+ seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
@@ -787 +787 @@
- run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
+ run("ROLLBACK PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -1472 +1472 @@
- cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}")
+ cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}".freeze)
lib/sequel/adapters/shared/sqlite.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:37:12.271769704 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:37:12.305769494 +0000
@@ -198 +198 @@
- metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)".freeze, input_identifier_meth(opts[:dataset]).call(table_name))
@@ -410 +410 @@
- c[:default] = LiteralString.new(c[:default]) if c[:default]
+ c[:default] = LiteralString.new(c[:default]).freeze if c[:default]
@@ -687 +687 @@
- ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}".freeze)
lib/sequel/adapters/sqlite.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:37:12.271769704 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:37:12.306769488 +0000
@@ -391 +391 @@
- LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/adapters/tinytds.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:37:12.271769704 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:37:12.306769488 +0000
@@ -208 +208 @@
- LiteralString.new("@#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/connection_pool.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/connection_pool.rb 2026-05-04 02:37:12.272769698 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/connection_pool.rb 2026-05-04 02:37:12.307769482 +0000
@@ -133,0 +134,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, *)
+ disconnect_connection(conn)
+ end
+
lib/sequel/connection_pool/sharded_timed_queue.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:37:12.273769692 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:37:12.307769482 +0000
@@ -74 +74 @@
- break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available(queue, server)) && !conns[conn]
@@ -94 +94 @@
- # and then the method will only disconnect connectsion from those specified shards.
+ # and then the method will only disconnect connections from those specified shards.
@@ -98 +98,3 @@
- while conn = queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available(queue, server)
+ nconns += 1
@@ -101 +103 @@
- fill_queue(server)
+ fill_queue(server, nconns)
@@ -135 +137 @@
- fill_queue(server)
+ fill_queue(server, 1)
@@ -170 +172 @@
- while conn = queue.pop(timeout: 0)
+ while conn = available(queue, server)
@@ -227,0 +230,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, _, server)
+ disconnect_pool_connection(conn, server)
+ end
+
@@ -247 +255,4 @@
- def fill_queue(server)
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(server, nconns)
@@ -249 +260 @@
- if queue.num_waiting > 0
+ if nconns > 0 && queue.num_waiting > 0
@@ -251 +262,2 @@
- while queue.num_waiting > 0 && (conn = try_make_new(server))
+ while nconns > 0 && queue.num_waiting > 0 && (conn = try_make_new(server))
+ nconns -= 1
@@ -300 +312 @@
- fill_queue(server)
+ fill_queue(server, to_disconnect.size)
@@ -314 +326 @@
- if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
+ if conn = available(queue, server) || try_make_new(server) || wait_until_available(queue, server)
@@ -319,0 +332,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available(queue, _server)
+ queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available(queue, _server)
+ queue.pop(timeout: @timeout)
lib/sequel/connection_pool/timed_queue.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:37:12.273769692 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:37:12.307769482 +0000
@@ -45 +45 @@
- break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available) && !conns[conn]
@@ -62 +62,3 @@
- while conn = @queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available
+ nconns += 1
@@ -65 +67 @@
- fill_queue
+ fill_queue(nconns)
@@ -97 +99 @@
- fill_queue
+ fill_queue(1)
@@ -159,2 +161,5 @@
- def fill_queue
- if @queue.num_waiting > 0
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(nconns)
+ if nconns > 0 && @queue.num_waiting > 0
@@ -162 +167 @@
- while @queue.num_waiting > 0 && (conn = try_make_new)
+ while nconns > 0 && @queue.num_waiting > 0 && (conn = try_make_new)
@@ -210 +215 @@
- fill_queue
+ fill_queue(to_disconnect.size)
@@ -223 +228 @@
- if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
+ if conn = available || try_make_new || wait_until_available
@@ -228,0 +234,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available
+ @queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available
+ @queue.pop(timeout: @timeout)
lib/sequel/database/schema_generator.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:37:12.274769686 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:37:12.309769470 +0000
@@ -5,0 +6,27 @@
+ module ColumnOptionMerger
+ private
+
+ # Merge given options into the column's default options. For backwards compatibility,
+ # the options take priority, but in cases where the option value overrides the argument
+ # value, and the values are different, we warn as this is likely to be an error in the
+ # code.
+ def _merge_column_options(defaults, opts)
+ defaults.merge!(opts) do |k, defv, v|
+ unless defv == v
+ # :nocov:
+ if RUBY_VERSION >= "3.2"
+ # :nocov:
+ caller_loc = Thread.each_caller_location do |loc|
+ break loc unless loc.path == __FILE__
+ end
+ caller_loc &&= "#{caller_loc.path}:#{caller_loc.lineno}: "
+ end
+ warn("#{caller_loc}#{k.inspect} option value (#{v.inspect}) overrides argument value (#{defv.inspect})")
+ end
+
+ v
+ end
+ end
+ end
+ private_constant :ColumnOptionMerger
+
@@ -19,0 +47,2 @@
+ include ColumnOptionMerger
+
@@ -176 +205 @@
- columns << {:name => name, :type => type}.merge!(opts)
+ columns << _merge_column_options({:name => name, :type => type}, opts)
@@ -182 +211 @@
-
+
@@ -249 +278 @@
- column(name, Integer, opts)
+ column(name, opts.fetch(:type, Integer), opts)
@@ -431,0 +461,2 @@
+ include ColumnOptionMerger
+
@@ -457 +488 @@
- op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
+ op = _merge_column_options({:op => :add_column, :name => name, :type => type}, opts)
@@ -522 +553 @@
- add_column(name, Integer, {:table=>table}.merge!(opts))
+ add_column(name, opts.fetch(:type, Integer), {:table=>table}.merge!(opts))
lib/sequel/database/schema_methods.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:37:12.274769686 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:37:12.309769470 +0000
@@ -822 +822 @@
- run(create_table_as_sql(name, sql, options))
+ run(create_table_as_sql(name, sql, options).freeze)
lib/sequel/dataset/placeholder_literalizer.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:37:12.276769673 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:37:12.310769463 +0000
@@ -119,0 +120,2 @@
+ frags.each(&:freeze)
+ frags.freeze
@@ -127,0 +130 @@
+ ds.opts[:sql].freeze
lib/sequel/dataset/prepared_statements.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:37:12.276769673 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:37:12.311769457 +0000
@@ -57 +57 @@
- sql = prepared_sql
+ sql = prepared_sql.freeze
@@ -226 +226 @@
- with_sql(prepared_sql)
+ with_sql(prepared_sql.freeze)
@@ -290 +290 @@
- sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
+ sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false)).freeze
@@ -322,0 +323,3 @@
+ frags.freeze
+ frags.each(&:freeze)
+ prepared_sql.freeze
@@ -412 +415 @@
- sql = ps.prepared_sql
+ sql = ps.prepared_sql.freeze
lib/sequel/dataset/query.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/dataset/query.rb 2026-05-04 02:37:12.276769673 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/dataset/query.rb 2026-05-04 02:37:12.311769457 +0000
@@ -1304 +1304 @@
- sql = public_send(sql, *args)
+ sql = public_send(sql, *args).freeze
@@ -1477 +1477,4 @@
-
+
+ PAREN_WRAPPER = ["(".freeze, ")".freeze].freeze
+ private_constant :PAREN_WRAPPER
+
@@ -1498 +1501 @@
- LiteralString.new("(#{expr})")
+ SQL::PlaceholderLiteralString.new(PAREN_WRAPPER, [expr])
lib/sequel/dataset/sql.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/dataset/sql.rb 2026-05-04 02:37:12.277769667 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/dataset/sql.rb 2026-05-04 02:37:12.311769457 +0000
@@ -56 +56 @@
- sql << v
+ literal_literal_string_append(sql, v)
@@ -1424,0 +1425,5 @@
+ end
+
+ # Append string to SQL string.
+ def literal_literal_string_append(sql, v)
+ sql << v
lib/sequel/extensions/connection_expiration.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:37:12.278769661 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:37:12.313769445 +0000
@@ -94 +94 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/connection_validator.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:37:12.278769661 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:37:12.313769445 +0000
@@ -121 +121 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/dataset_run.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:37:12.278769661 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:37:12.314769439 +0000
@@ -33 +33 @@
- db.run(sql, :server=>server)
+ db.run(sql.freeze, :server=>server)
@@ -35 +35 @@
- db.run(sql)
+ db.run(sql.freeze)
lib/sequel/extensions/date_arithmetic.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:37:12.279769655 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:37:12.314769439 +0000
@@ -85,2 +85,2 @@
- MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
- MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
+ MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1].freeze).freeze}).freeze
+ MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1].freeze).freeze}).freeze
@@ -88 +88 @@
- DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
+ DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}".freeze).freeze}).freeze
@@ -90 +90 @@
- DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
+ DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.freeze).freeze}).freeze
@@ -110 +110 @@
- placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := ".freeze
@@ -159 +159 @@
- expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ", ", timestamp(", "))}"], value, expr)
+ expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ".freeze, ", timestamp(", "))}"], value, expr)
lib/sequel/extensions/migration.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/extensions/migration.rb 2026-05-04 02:37:12.280769649 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/migration.rb 2026-05-04 02:37:12.316769426 +0000
@@ -189,16 +189,13 @@
- begin
- instance_exec(&block)
- rescue
- just_raise = true
- end
- if just_raise
- Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
- else
- actions = @actions.reverse
- Proc.new do
- actions.each do |a|
- pr = a.last.is_a?(Proc) ? a.pop : nil
- # Allow calling private methods as the reversing methods are private
- send(*a, &pr)
- end
- end
+ instance_exec(&block)
+ rescue NoMethodError => e
+ 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"}
+ rescue => e
+ 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"}
+ else
+ actions = @actions.reverse
+ Proc.new do
+ actions.each do |a|
+ pr = a.last.is_a?(Proc) ? a.pop : nil
+ # Allow calling private methods as the reversing methods are private
+ send(*a, &pr)
+ end
@@ -273 +270 @@
- raise if args.first.is_a?(Array)
+ super if args.first.is_a?(Array)
lib/sequel/extensions/pg_enum.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:37:12.282769636 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:37:12.317769420 +0000
@@ -130 +130 @@
- run(sql)
+ run(sql.freeze)
lib/sequel/model/associations.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/model/associations.rb 2026-05-04 02:37:12.287769605 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/model/associations.rb 2026-05-04 02:37:12.323769383 +0000
@@ -114,0 +115,2 @@
+ when :lateral_subquery
+ apply_lateral_subquery_eager_graph_limit_strategy(ds)
@@ -301 +303,6 @@
- objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
+
+ objects = if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
+ else
+ apply_eager_limit_strategy(ds, strategy, eager_limit).all
+ end
@@ -323 +330 @@
- objects.concat(ds.with_sql(sql).to_a)
+ objects.concat(ds.with_sql(sql.freeze).to_a)
@@ -369 +376,6 @@
- ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ ds = if filter_by_associations_limit_strategy == :lateral_subquery
+ apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
+ else
+ filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ end
+
@@ -756,0 +769,55 @@
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds.
+ where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys))).
+ limit(*limit_and_offset).
+ lateral
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ associated_table_name = associated_class.table_name
+
+ associated_class.
+ from(table_name).
+ select_all(associated_table_name).
+ join(lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset).as(associated_table_name), true).
+ order(*self[:order])
+ end
+
+ # Return an expression to filter the filter by associations dataset to only
+ # rows related to given objects.
+ def _lateral_subquery_filter_limit_strategy_conditions(obj, key, value_method, value_column)
+ value = case obj
+ when Array
+ if key.is_a?(Array)
+ key_methods = Array(value_method)
+ obj.map{|o| key_methods.map{|meth| o.send(meth)}}
+ else
+ obj.map{|o| o.send(value_method)}
+ end
+ when Sequel::Dataset
+ obj.select(*Array(qualify(associated_class.table_name, value_column)))
+ else
+ if key.is_a?(Array)
+ return Array(key).zip(Array(value_method).map{|meth| obj.send(meth)})
+ else
+ obj.send(value_method)
+ end
+ end
+
+ {key => value}
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds.
+ select(*qualify(associated_class.table_name, associated_class.primary_key)).
+ limit(*limit_and_offset).
+ lateral)
+ end
+
+ def apply_lateral_subquery_filter_limit_strategy(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_dataset(self[:model].
+ select(*lateral_subquery_filter_limit_strategy_lateral_dataset_select).
+ join(lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj).as(associated_class.table_name), filter_by_associations_conditions_subquery_conditions(obj)), obj)
+ end
+
@@ -779 +846,6 @@
- apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
+ strategy = eager_limit_strategy
+ if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, arg, limit_and_offset)
+ else
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), strategy)
+ end
@@ -1021 +1093 @@
- # Support a correlated subquery limit strategy when using eager_graph.
+ # Support a lateral_subquery and correlated_subquery limit strategy when using eager_graph.
@@ -1120,0 +1193,48 @@
+ # Use a LATERAL subquery to limit the dataset. Note that this will not
+ # work correctly if the associated dataset uses qualified identifers in the WHERE clause,
+ # as they would reference the containing query instead of the subquery.
+ #
+ # This does not contain the conditions that are necessary to join to the
+ # query, since the necessary qualifier is not passed as an argument.
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ table_name = ds.first_source_alias
+ qualifier = ds.opts[:eager_options][:implicit_qualifier]
+ graph_conditions = self[:_graph_conditions]
+
+ unless Sequel.condition_specifier?(graph_conditions)
+ raise Error, "lateral_subquery eager graph limit strategy only supported when graph conditions are a hash or array of pairs"
+ end
+
+ ds.
+ limit(*limit_and_offset).
+ order(*self[:order]).
+ where(graph_conditions.map{|k, v| [qualify(table_name, k), qualify(qualifier, v)]}).
+ lateral
+ end
+
+ # Avoid setting duplicate predicate condition when using the lateral subquery
+ # eager limit strategy.
+ def eager_loading_set_predicate_condition(ds, eo)
+ eager_limit_strategy == :lateral_subquery ? ds : super
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ super.where(qualify(self[:model].table_name, self[:primary_key]) => ids)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, filter_by_associations_conditions_key, self[:key_method], self[:key])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(lateral_subquery_filter_limit_strategy_conditions(obj))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualified_primary_key
+ end
+
@@ -1434,0 +1555,46 @@
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, lateral_subquery_filter_limit_strategy_conditions_key, right_primary_key_method, right_primary_key)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ self[:right_key]
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(filter_by_associations_conditions_key => ds.db.from(self[:join_table]).
+ select(*self[:left_key]).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualify(self[:model].table_name, self[:left_primary_key])
+ end
+
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds = super
+ select = ds.opts[:select].dup
+ left_key_alias = self[:left_key_alias]
+ select.pop while (s = select.last).is_a?(Sequel::SQL::AliasedExpression) && (left_key_alias.is_a?(Array) ? left_key_alias.include?(s.alias) : s.alias == left_key_alias)
+ ds = ds.clone(:select=>select)
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ super.
+ select_all(associated_class.table_name).
+ select_append(*qualify(table_name, self[:left_primary_keys]).zip(Array(self[:left_key_alias])).map{|id, aliaz| Sequel.as(id, aliaz)}).
+ where(qualify(table_name, self[:left_primary_key]) => ids)
+ end
+
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ ds.
+ limit(*limit_and_offset).
+ where(qualify(join_table_alias, self[:left_keys]).zip(qualify(ds.opts[:eager_options][:implicit_qualifier], self[:left_primary_key_columns]))).
+ order(*self[:order]).
+ lateral
+ end
+
@@ -2415,0 +2582 @@
+ graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
@@ -2418 +2585,5 @@
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
+ graph_limit_strategy = eo[:limit_strategy]
+ egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
+ graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
+
+ ds = ds.graph(egds, graph_conditions_true || graph_conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
@@ -2500,0 +2672,3 @@
+ if eager_options[:limit_strategy] == :lateral_subquery
+ ds = ds.clone(:eager_options=>eager_options)
+ end
lib/sequel/plugins/dataset_associations.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:37:12.291769581 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:37:12.327769358 +0000
@@ -85 +85 @@
- ds = r.associated_class.dataset
+ klass = r.associated_class
@@ -86,0 +87,19 @@
+
+ if r.send(:filter_by_associations_limit_strategy) == :lateral_subquery
+ ds = r.send(:associated_eager_dataset)
+
+ case r[:type]
+ when :one_to_one, :one_to_many
+ sds = sds.select(*Array(r.qualified_primary_key))
+ else
+ sds = sds.select(*r[:left_primary_keys])
+ ds = ds.select_all(klass.table_name)
+ update_select = true
+ end
+
+ ds = r.send(:apply_lateral_subquery_eager_limit_strategy, ds, sds, r.limit_and_offset)
+ ds = ds.clone(:select=>ds.opts[:select][0,1]) if update_select
+ return ds.clone(:eager=>nil, :eager_graph=>nil)
+ end
+
+ ds = klass.dataset
lib/sequel/plugins/dirty.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:37:12.292769574 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:37:12.328769352 +0000
@@ -237,2 +237,5 @@
- check_missing_initial_value(column)
- iv[column] = get_column_value(column)
+ if db_schema[column]
+ check_missing_initial_value(column)
+ iv[column] = get_column_value(column)
+ end
+
lib/sequel/plugins/many_through_many.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:37:12.293769568 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:37:12.330769340 +0000
@@ -148,0 +149,21 @@
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ qualify(reverse_edges.first[:table], reverse_edges.first[:left])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(qualify(edges.first[:table], edges.first[:right])).zip(Array(qualify(self[:model].table_name, edges.first[:left]))))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ first_edge, *remaining_edges = edges
+ filter_ds = ds.db.from(first_edge[:table]).
+ select(*qualify(first_edge[:table], first_edge[:right])).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj))
+
+ remaining_edges.each do |edge|
+ filter_ds = filter_ds.join(edge[:table], Array(edge[:right]).zip(Array(edge[:left])))
+ end
+
+ ds.where(qualify(self[:model].table_name, self[:left_primary_key]) => filter_ds)
+ end
+
lib/sequel/plugins/mssql_optimistic_locking.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:37:12.294769562 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:37:12.330769340 +0000
@@ -58 +58 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
lib/sequel/plugins/pg_xmin_optimistic_locking.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:37:12.295769556 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:37:12.331769333 +0000
@@ -102 +102 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
lib/sequel/plugins/serialization.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:37:12.295769556 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:37:12.332769327 +0000
@@ -32,0 +33,9 @@
+ # Note that use of an unsafe serialization method can result in an attack vector
+ # (potentially allowing remote code execution) if an attacker has the ability to
+ # store data directly in the underlying column. This would affect the marshal
+ # serialization format, and on older versions of Ruby, potentially the yaml and
+ # json serialization formats as well. It can also affect custom formats. You
+ # should ensure that attackers do not have access to store data directly in the
+ # underlying column when using this plugin (especially when using an unsafe
+ # serialization method).
+ #
@@ -100 +109 @@
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
lib/sequel/version.rb
--- /tmp/d20260504-1830-kczwjk/sequel-5.101.0/lib/sequel/version.rb 2026-05-04 02:37:12.299769531 +0000
+++ /tmp/d20260504-1830-kczwjk/sequel-5.104.0/lib/sequel/version.rb 2026-05-04 02:37:12.336769302 +0000
@@ -9 +9 @@
- MINOR = 101
+ MINOR = 104 |
Contributor
|
Contributor
gem compare sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT version:
5.101.0: 5.101.0
5.104.0: 5.104.0
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb +151/-0
lib/sequel/extensions/lit_require_frozen.rb +131/-0
* Changed:
MIT-LICENSE +1/-1
lib/sequel/adapters/jdbc/derby.rb +2/-0
lib/sequel/adapters/jdbc/h2.rb +2/-2
lib/sequel/adapters/postgres.rb +1/-1
lib/sequel/adapters/shared/mssql.rb +3/-3
lib/sequel/adapters/shared/mysql.rb +5/-4
lib/sequel/adapters/shared/postgres.rb +16/-16
lib/sequel/adapters/shared/sqlite.rb +3/-3
lib/sequel/adapters/sqlite.rb +1/-1
lib/sequel/adapters/tinytds.rb +1/-1
lib/sequel/connection_pool.rb +6/-0
lib/sequel/connection_pool/sharded_timed_queue.rb +36/-11
lib/sequel/connection_pool/timed_queue.rb +27/-9
lib/sequel/database/schema_generator.rb +36/-5
lib/sequel/database/schema_methods.rb +1/-1
lib/sequel/dataset/placeholder_literalizer.rb +3/-0
lib/sequel/dataset/prepared_statements.rb +7/-4
lib/sequel/dataset/query.rb +6/-3
lib/sequel/dataset/sql.rb +6/-1
lib/sequel/extensions/connection_expiration.rb +1/-1
lib/sequel/extensions/connection_validator.rb +1/-1
lib/sequel/extensions/dataset_run.rb +2/-2
lib/sequel/extensions/date_arithmetic.rb +6/-6
lib/sequel/extensions/migration.rb +14/-17
lib/sequel/extensions/pg_enum.rb +1/-1
lib/sequel/model/associations.rb +180/-6
lib/sequel/plugins/dataset_associations.rb +20/-1
lib/sequel/plugins/dirty.rb +5/-2
lib/sequel/plugins/many_through_many.rb +21/-0
lib/sequel/plugins/mssql_optimistic_locking.rb +1/-1
lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1/-1
lib/sequel/plugins/serialization.rb +10/-1
lib/sequel/version.rb +1/-1
DIFFERENT extra_rdoc_files:
5.101.0->5.104.0:
* Changed:
MIT-LICENSE +1/-1 |
Contributor
gem compare --diff sequel 5.101.0 5.104.0Compared versions: ["5.101.0", "5.104.0"]
DIFFERENT files:
5.101.0->5.104.0:
* Added:
lib/sequel/extensions/connection_checkout_event_callback.rb
--- /tmp/20260504-1888-klbdc 2026-05-04 02:37:42.161776864 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/connection_checkout_event_callback.rb 2026-05-04 02:37:42.131777110 +0000
@@ -0,0 +1,151 @@
+# frozen-string-literal: true
+#
+# The connection_checkout_event_callback extension modifies a database's
+# connection pool to allow for a checkout event callback. This callback is
+# called with the following arguments:
+#
+# :immediately_available :: Connection immediately available and returned
+# :not_immediately_available :: Connection not immediately available
+# :new_connection :: New connection created and returned
+# Float :: Number of seconds waiting to acquire a connection
+#
+# This is a low-level extension that allows for building telemetry
+# information. It doesn't implement any telemetry reporting itself. The
+# main reason for recording this information is to use it to determine the
+# appropriate size for the connection pool. Having too large a connection
+# pool can waste resources, while having too small a connection pool can
+# result in substantial time to check out a connection. In general, you
+# want to use as small a pool as possible while keeping the time to
+# checkout a connection low.
+#
+# To use the connection checkout event callback, you must first load the
+# extension:
+#
+# DB.extension(:connection_checkout_event_callback)
+#
+# By default, an empty proc is used as the callback so that loading the
+# support doesn't break anything. If you are using the extension, you
+# should set the callback at some point during application startup:
+#
+# DB.pool.on_checkout_event = proc do |event|
+# # ...
+# end
+#
+# When using the sharded connection pool, the callback is also
+# passed a second argument, the requested server shard (generally a
+# symbol), allowing for collection of per-shard telemetry:
+#
+# DB.pool.on_checkout_event = proc do |event, server|
+# # ...
+# end
+#
+# Note that the callback may be called currently by multiple threads.
+# You should use some form of concurrency control inside the callback,
+# such as a mutex or queue.
+#
+# Below is a brief example of usage to determine the percentage of
+# connection requests where a connection was immediately available:
+#
+# mutex = Mutex.new
+# total = immediates = 0
+#
+# DB.pool.on_checkout_event = proc do |event|
+# case event
+# when :immediately_available
+# mutex.synchronize do
+# total += 1
+# immediates += 1
+# end
+# when :not_immediately_available
+# mutex.synchronize do
+# total += 1
+# end
+# end
+# end
+#
+# immediate_percentage = lambda do
+# mutex.synchronize do
+# 100.0 * immediates / total
+# end
+# end
+#
+# Note that this extension only works with the timed_queue
+# and sharded_timed_queue connection pools (the default
+# connection pools when using Ruby 3.2+).
+#
+# Related modules: Sequel::ConnectionCheckoutEventCallbacks::TimedQueue,
+# Sequel::ConnectionCheckoutEventCallbacks::ShardedTimedQueue
+
+#
+module Sequel
+ module ConnectionCheckoutEventCallbacks
+ module TimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available)
+ conn
+ end
+
+ def try_make_new
+ conn = super
+ @on_checkout_event.call(:new_connection) if conn
+ conn
+ end
+
+ def wait_until_available
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer))
+ conn
+ end
+ end
+
+ module ShardedTimedQueue
+ # The callback that is called with connection checkout events.
+ attr_accessor :on_checkout_event
+
+ private
+
+ def available(queue, server)
+ conn = super
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available, server)
+ conn
+ end
+
+ def try_make_new(server)
+ conn = super
+ @on_checkout_event.call(:new_connection, server) if conn
+ conn
+ end
+
+ def wait_until_available(queue, server)
+ timer = Sequel.start_timer
+ conn = super
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer), server)
+ conn
+ end
+ end
+ end
+
+ default_callback = proc{}
+
+ Database.register_extension(:connection_checkout_event_callback) do |db|
+ pool = db.pool
+
+ case pool.pool_type
+ when :timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::TimedQueue)
+ when :sharded_timed_queue
+ db.pool.extend(ConnectionCheckoutEventCallbacks::ShardedTimedQueue)
+ else
+ raise Error, "the connection_checkout_event_callback extension is only supported when using a timed_queue connection pool"
+ end
+
+ pool.on_checkout_event ||= default_callback
+ end
+end
lib/sequel/extensions/lit_require_frozen.rb
--- /tmp/20260504-1888-ppkw38 2026-05-04 02:37:42.164776839 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/lit_require_frozen.rb 2026-05-04 02:37:42.134777086 +0000
@@ -0,0 +1,131 @@
+# frozen-string-literal: true
+#
+# The lit_require_frozen extension disallows the use of unfrozen strings
+# as literal strings in database and dataset methods. If you try to use an
+# unfrozen string as a literal string for a dataset using this extension,
+# an exception will be raised.
+#
+# While this works for all Ruby versions, it is designed for use on Ruby 3+
+# where all files are using the frozen-string-literal magic comment. In this
+# case, uninterpolated literal strings are frozen, but interpolated strings
+# are not frozen. This allows you to catch potentially dangerous code:
+#
+# # Probably safe, no exception raised
+# DB["SELECT * FROM t WHERE c > :v", v: user_provided_string)
+#
+# # Potentially unsafe, raises Sequel::LitRequireFrozen::Error
+# DB["SELECT * FROM t WHERE c > '#{user_provided_string}'"]
+#
+# The assumption made is that a frozen string is unlikely to contain unsafe
+# input, while an unfrozen string has potentially been interpolated and may
+# contain unsafe input.
+#
+# This disallows the the following cases:
+#
+# * Sequel::LiteralString instances that are unfrozen and are not based on a
+# frozen string
+# * Sequel::SQL::PlaceholderLiteralString instances when the placeholder string
+# is not frozen
+# * Unfrozen strings passed to Database#<< or #[] or Dataset#with_sql
+#
+# To use this extension, load it into the database:
+#
+# DB.extension :lit_require_frozen
+#
+# It can also be loaded into individual datasets:
+#
+# ds = DB[:t].extension(:lit_require_frozen)
+#
+# Assuming you have good test coverage, it is recommended to only load
+# this extension when testing.
+#
+# Related module: Sequel::LitRequireFrozen
+
+#
+module Sequel
+ class LiteralString
+ # The string used when creating the literal string (first argument to
+ # Sequel::LiteralString.new). This may be nil if no string was provided,
+ # or if the litral string was created before this extension was required.
+ attr_reader :source
+
+ def initialize(*a)
+ @source = a.first
+ super
+ end
+ # :nocov:
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
+ # :nocov:
+ end
+
+ module LitRequireFrozen
+ # Error class raised for using unfrozen literal string.
+ class Error < Sequel::Error
+ end
+
+ module DatabaseMethods
+ def self.extended(db)
+ db.extend_datasets(DatasetMethods)
+ end
+
+ # Check given SQL is frozen before running it.
+ def run(sql, opts=OPTS)
+ @default_dataset.with_sql(sql)
+ super
+ end
+ end
+
+ module DatasetMethods
+ # Check given SQL is not an unfrozen string.
+ def with_sql(sql, *args)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+
+ # Check that placeholder string is frozen (or all entries
+ # in placeholder array are frozen).
+ def placeholder_literal_string_sql_append(sql, pls)
+ str = pls.str
+
+ if str.is_a?(Array)
+ str.each do |s|
+ _check_unfrozen_literal_string(s)
+ end
+ else
+ _check_unfrozen_literal_string(str)
+ end
+
+ super
+ end
+
+ private
+
+ # Base method that other methods used to check for whether a string should be allowed
+ # as literal SQL. Allows non-strings as well as frozen strings.
+ def _check_unfrozen_literal_string(str)
+ return if !str.is_a?(String) || str.frozen?
+
+ if str.is_a?(LiteralString)
+ _check_unfrozen_literal_string(str.source)
+ else
+ raise Error, "cannot treat unfrozen string as literal SQL: #{str.inspect}"
+ end
+ end
+
+ # Check literal strings appended to SQL.
+ def literal_literal_string_append(sql, v)
+ _check_unfrozen_literal_string(v)
+ super
+ end
+
+ # Check static SQL is not frozen.
+ def static_sql(sql)
+ _check_unfrozen_literal_string(sql)
+ super
+ end
+ end
+ end
+
+ Dataset.register_extension(:lit_require_frozen, LitRequireFrozen::DatasetMethods)
+ Database.register_extension(:lit_require_frozen, LitRequireFrozen::DatabaseMethods)
+end
* Changed:
MIT-LICENSE
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/MIT-LICENSE 2026-05-04 02:37:42.066777644 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/MIT-LICENSE 2026-05-04 02:37:42.115777242 +0000
@@ -2 +2 @@
-Copyright (c) 2008-2023 Jeremy Evans
+Copyright (c) 2008-2026 Jeremy Evans and contributors
lib/sequel/adapters/jdbc/derby.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:37:42.071777603 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/jdbc/derby.rb 2026-05-04 02:37:42.117777225 +0000
@@ -2,0 +3,2 @@
+# SEQUEL6: Remove
+
lib/sequel/adapters/jdbc/h2.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:37:42.071777603 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/jdbc/h2.rb 2026-05-04 02:37:42.117777225 +0000
@@ -21 +21 @@
- run("COMMIT TRANSACTION #{transaction_id}", opts)
+ run("COMMIT TRANSACTION #{transaction_id}".freeze, opts)
@@ -39 +39 @@
- run("ROLLBACK TRANSACTION #{transaction_id}", opts)
+ run("ROLLBACK TRANSACTION #{transaction_id}".freeze, opts)
lib/sequel/adapters/postgres.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:37:42.074777578 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/postgres.rb 2026-05-04 02:37:42.119777209 +0000
@@ -730 +730 @@
- LiteralString.new("#{prepared_arg_placeholder}#{i}")
+ LiteralString.new("#{prepared_arg_placeholder}#{i}".freeze)
lib/sequel/adapters/shared/mssql.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:37:42.075777570 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/shared/mssql.rb 2026-05-04 02:37:42.120777201 +0000
@@ -109 +109 @@
- sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}"
+ sql = "DECLARE #{declarations.join(', ')}; EXECUTE @RC = #{name} #{values.join(', ')}; SELECT #{names.join(', ')}".freeze
@@ -403 +403 @@
- run(ds.into(name).sql)
+ run(ds.into(name).sql.freeze)
@@ -881 +881 @@
- sql = multi_insert_sql(columns, values)[0]
+ sql = multi_insert_sql(columns, values)[0].freeze
lib/sequel/adapters/shared/mysql.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:37:42.075777570 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/shared/mysql.rb 2026-05-04 02:37:42.121777193 +0000
@@ -43 +43 @@
- run("XA COMMIT #{literal(transaction_id)}", opts)
+ run("XA COMMIT #{literal(transaction_id)}".freeze, opts)
@@ -106 +106 @@
- metadata_dataset.with_sql(sql).each do |r|
+ metadata_dataset.with_sql(sql.freeze).each do |r|
@@ -118 +118 @@
- run("XA ROLLBACK #{literal(transaction_id)}", opts)
+ run("XA ROLLBACK #{literal(transaction_id)}".freeze, opts)
@@ -783 +783,2 @@
- ds = db.send(:metadata_dataset).with_sql(((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql).naked
+ sql = ((opts[:extended] && (db.mariadb? || db.server_version < 50700)) ? 'EXPLAIN EXTENDED ' : 'EXPLAIN ') + select_sql
+ ds = db.send(:metadata_dataset).with_sql(sql.freeze).naked
lib/sequel/adapters/shared/postgres.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:37:42.076777562 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/shared/postgres.rb 2026-05-04 02:37:42.122777184 +0000
@@ -342 +342 @@
- run("COMMIT PREPARED #{literal(transaction_id)}", opts)
+ run("COMMIT PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -419 +419 @@
- run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT", server_hash)
+ run("ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(column)} DROP DEFAULT".freeze, server_hash)
@@ -456 +456 @@
- self << create_function_sql(name, definition, opts)
+ self << create_function_sql(name, definition, opts).freeze
@@ -467 +467 @@
- self << create_language_sql(name, opts)
+ self << create_language_sql(name, opts).freeze
@@ -476 +476 @@
- self << create_schema_sql(name, opts)
+ self << create_schema_sql(name, opts).freeze
@@ -512 +512 @@
- self << create_trigger_sql(table, name, function, opts)
+ self << create_trigger_sql(table, name, function, opts).freeze
@@ -545 +545 @@
- run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}".freeze
@@ -555 +555 @@
- self << drop_function_sql(name, opts)
+ self << drop_function_sql(name, opts).freeze
@@ -564 +564 @@
- self << drop_language_sql(name, opts)
+ self << drop_language_sql(name, opts).freeze
@@ -573 +573 @@
- self << drop_schema_sql(name, opts)
+ self << drop_schema_sql(name, opts).freeze
@@ -584 +584 @@
- self << drop_trigger_sql(table, name, opts)
+ self << drop_trigger_sql(table, name, opts).freeze
@@ -748 +748 @@
- self << rename_schema_sql(name, new_name)
+ self << rename_schema_sql(name, new_name).freeze
@@ -759 +759 @@
- run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}".freeze
@@ -772 +772 @@
- seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
+ seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq.freeze)))
@@ -787 +787 @@
- run("ROLLBACK PREPARED #{literal(transaction_id)}", opts)
+ run("ROLLBACK PREPARED #{literal(transaction_id)}".freeze, opts)
@@ -1472 +1472 @@
- cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}")
+ cols[-1] = Sequel.lit("PERIOD #{quote_identifier(cols[-1])}".freeze)
lib/sequel/adapters/shared/sqlite.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:37:42.077777554 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/shared/sqlite.rb 2026-05-04 02:37:42.122777184 +0000
@@ -198 +198 @@
- metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)", input_identifier_meth(opts[:dataset]).call(table_name))
+ metadata_dataset.with_sql("PRAGMA table_#{'x' if sqlite_version > 33100}info(?)".freeze, input_identifier_meth(opts[:dataset]).call(table_name))
@@ -410 +410 @@
- c[:default] = LiteralString.new(c[:default]) if c[:default]
+ c[:default] = LiteralString.new(c[:default]).freeze if c[:default]
@@ -687 +687 @@
- ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}".freeze)
lib/sequel/adapters/sqlite.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:37:42.078777546 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/sqlite.rb 2026-05-04 02:37:42.122777184 +0000
@@ -391 +391 @@
- LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/adapters/tinytds.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:37:42.078777546 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/adapters/tinytds.rb 2026-05-04 02:37:42.122777184 +0000
@@ -208 +208 @@
- LiteralString.new("@#{k.to_s.gsub('.', '__')}")
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}".freeze)
lib/sequel/connection_pool.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/connection_pool.rb 2026-05-04 02:37:42.079777537 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/connection_pool.rb 2026-05-04 02:37:42.124777168 +0000
@@ -133,0 +134,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, *)
+ disconnect_connection(conn)
+ end
+
lib/sequel/connection_pool/sharded_timed_queue.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:37:42.080777529 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/connection_pool/sharded_timed_queue.rb 2026-05-04 02:37:42.124777168 +0000
@@ -74 +74 @@
- break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available(queue, server)) && !conns[conn]
@@ -94 +94 @@
- # and then the method will only disconnect connectsion from those specified shards.
+ # and then the method will only disconnect connections from those specified shards.
@@ -98 +98,3 @@
- while conn = queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available(queue, server)
+ nconns += 1
@@ -101 +103 @@
- fill_queue(server)
+ fill_queue(server, nconns)
@@ -135 +137 @@
- fill_queue(server)
+ fill_queue(server, 1)
@@ -170 +172 @@
- while conn = queue.pop(timeout: 0)
+ while conn = available(queue, server)
@@ -227,0 +230,6 @@
+ # Only for use by extension that need to disconnect a connection inside acquire.
+ # Takes the connection and any arguments accepted by acquire.
+ def disconnect_acquired_connection(conn, _, server)
+ disconnect_pool_connection(conn, server)
+ end
+
@@ -247 +255,4 @@
- def fill_queue(server)
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(server, nconns)
@@ -249 +260 @@
- if queue.num_waiting > 0
+ if nconns > 0 && queue.num_waiting > 0
@@ -251 +262,2 @@
- while queue.num_waiting > 0 && (conn = try_make_new(server))
+ while nconns > 0 && queue.num_waiting > 0 && (conn = try_make_new(server))
+ nconns -= 1
@@ -300 +312 @@
- fill_queue(server)
+ fill_queue(server, to_disconnect.size)
@@ -314 +326 @@
- if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
+ if conn = available(queue, server) || try_make_new(server) || wait_until_available(queue, server)
@@ -319,0 +332,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available(queue, _server)
+ queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available(queue, _server)
+ queue.pop(timeout: @timeout)
lib/sequel/connection_pool/timed_queue.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:37:42.080777529 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/connection_pool/timed_queue.rb 2026-05-04 02:37:42.124777168 +0000
@@ -45 +45 @@
- break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
+ break unless (conn = available) && !conns[conn]
@@ -62 +62,3 @@
- while conn = @queue.pop(timeout: 0)
+ nconns = 0
+ while conn = available
+ nconns += 1
@@ -65 +67 @@
- fill_queue
+ fill_queue(nconns)
@@ -97 +99 @@
- fill_queue
+ fill_queue(1)
@@ -159,2 +161,5 @@
- def fill_queue
- if @queue.num_waiting > 0
+ #
+ # nconns specifies the maximum number of connections to add, which should
+ # be the number of connections that were disconnected.
+ def fill_queue(nconns)
+ if nconns > 0 && @queue.num_waiting > 0
@@ -162 +167 @@
- while @queue.num_waiting > 0 && (conn = try_make_new)
+ while nconns > 0 && @queue.num_waiting > 0 && (conn = try_make_new)
@@ -210 +215 @@
- fill_queue
+ fill_queue(to_disconnect.size)
@@ -223 +228 @@
- if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
+ if conn = available || try_make_new || wait_until_available
@@ -228,0 +234,13 @@
+ end
+
+ # Return the next connection in the pool if there is one available. Returns nil
+ # if no connection is currently available.
+ def available
+ @queue.pop(timeout: 0)
+ end
+
+ # Return the next connection in the pool if there is one available. If not, wait
+ # until the timeout for a connection to become available. If there is still no
+ # available connection, return nil.
+ def wait_until_available
+ @queue.pop(timeout: @timeout)
lib/sequel/database/schema_generator.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:37:42.082777513 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/database/schema_generator.rb 2026-05-04 02:37:42.126777151 +0000
@@ -5,0 +6,27 @@
+ module ColumnOptionMerger
+ private
+
+ # Merge given options into the column's default options. For backwards compatibility,
+ # the options take priority, but in cases where the option value overrides the argument
+ # value, and the values are different, we warn as this is likely to be an error in the
+ # code.
+ def _merge_column_options(defaults, opts)
+ defaults.merge!(opts) do |k, defv, v|
+ unless defv == v
+ # :nocov:
+ if RUBY_VERSION >= "3.2"
+ # :nocov:
+ caller_loc = Thread.each_caller_location do |loc|
+ break loc unless loc.path == __FILE__
+ end
+ caller_loc &&= "#{caller_loc.path}:#{caller_loc.lineno}: "
+ end
+ warn("#{caller_loc}#{k.inspect} option value (#{v.inspect}) overrides argument value (#{defv.inspect})")
+ end
+
+ v
+ end
+ end
+ end
+ private_constant :ColumnOptionMerger
+
@@ -19,0 +47,2 @@
+ include ColumnOptionMerger
+
@@ -176 +205 @@
- columns << {:name => name, :type => type}.merge!(opts)
+ columns << _merge_column_options({:name => name, :type => type}, opts)
@@ -182 +211 @@
-
+
@@ -249 +278 @@
- column(name, Integer, opts)
+ column(name, opts.fetch(:type, Integer), opts)
@@ -431,0 +461,2 @@
+ include ColumnOptionMerger
+
@@ -457 +488 @@
- op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
+ op = _merge_column_options({:op => :add_column, :name => name, :type => type}, opts)
@@ -522 +553 @@
- add_column(name, Integer, {:table=>table}.merge!(opts))
+ add_column(name, opts.fetch(:type, Integer), {:table=>table}.merge!(opts))
lib/sequel/database/schema_methods.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:37:42.082777513 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/database/schema_methods.rb 2026-05-04 02:37:42.126777151 +0000
@@ -822 +822 @@
- run(create_table_as_sql(name, sql, options))
+ run(create_table_as_sql(name, sql, options).freeze)
lib/sequel/dataset/placeholder_literalizer.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:37:42.084777496 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/dataset/placeholder_literalizer.rb 2026-05-04 02:37:42.128777135 +0000
@@ -119,0 +120,2 @@
+ frags.each(&:freeze)
+ frags.freeze
@@ -127,0 +130 @@
+ ds.opts[:sql].freeze
lib/sequel/dataset/prepared_statements.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:37:42.084777496 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/dataset/prepared_statements.rb 2026-05-04 02:37:42.128777135 +0000
@@ -57 +57 @@
- sql = prepared_sql
+ sql = prepared_sql.freeze
@@ -226 +226 @@
- with_sql(prepared_sql)
+ with_sql(prepared_sql.freeze)
@@ -290 +290 @@
- sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false))
+ sql = literal(Sequel::SQL::PlaceholderLiteralString.new(@opts[:prepared_sql_frags], @opts[:bind_arguments], false)).freeze
@@ -322,0 +323,3 @@
+ frags.freeze
+ frags.each(&:freeze)
+ prepared_sql.freeze
@@ -412 +415 @@
- sql = ps.prepared_sql
+ sql = ps.prepared_sql.freeze
lib/sequel/dataset/query.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/dataset/query.rb 2026-05-04 02:37:42.085777488 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/dataset/query.rb 2026-05-04 02:37:42.129777127 +0000
@@ -1304 +1304 @@
- sql = public_send(sql, *args)
+ sql = public_send(sql, *args).freeze
@@ -1477 +1477,4 @@
-
+
+ PAREN_WRAPPER = ["(".freeze, ")".freeze].freeze
+ private_constant :PAREN_WRAPPER
+
@@ -1498 +1501 @@
- LiteralString.new("(#{expr})")
+ SQL::PlaceholderLiteralString.new(PAREN_WRAPPER, [expr])
lib/sequel/dataset/sql.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/dataset/sql.rb 2026-05-04 02:37:42.085777488 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/dataset/sql.rb 2026-05-04 02:37:42.129777127 +0000
@@ -56 +56 @@
- sql << v
+ literal_literal_string_append(sql, v)
@@ -1424,0 +1425,5 @@
+ end
+
+ # Append string to SQL string.
+ def literal_literal_string_append(sql, v)
+ sql << v
lib/sequel/extensions/connection_expiration.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:37:42.086777480 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/connection_expiration.rb 2026-05-04 02:37:42.131777110 +0000
@@ -94 +94 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/connection_validator.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:37:42.087777472 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/connection_validator.rb 2026-05-04 02:37:42.131777110 +0000
@@ -121 +121 @@
- disconnect_connection(conn)
+ disconnect_acquired_connection(conn, *a)
lib/sequel/extensions/dataset_run.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:37:42.087777472 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/dataset_run.rb 2026-05-04 02:37:42.132777102 +0000
@@ -33 +33 @@
- db.run(sql, :server=>server)
+ db.run(sql.freeze, :server=>server)
@@ -35 +35 @@
- db.run(sql)
+ db.run(sql.freeze)
lib/sequel/extensions/date_arithmetic.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:37:42.087777472 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/date_arithmetic.rb 2026-05-04 02:37:42.132777102 +0000
@@ -85,2 +85,2 @@
- MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
- MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
+ MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1].freeze).freeze}).freeze
+ MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1].freeze).freeze}).freeze
@@ -88 +88 @@
- DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}").freeze}).freeze
+ DERBY_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit("SQL_TSI_#{s.to_s.upcase[0...-1]}".freeze).freeze}).freeze
@@ -90 +90 @@
- DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s).freeze}).freeze
+ DB2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.freeze).freeze}).freeze
@@ -110 +110 @@
- placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
+ placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := ".freeze
@@ -159 +159 @@
- expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ", ", timestamp(", "))}"], value, expr)
+ expr = Sequel.lit(["{fn timestampadd(#{sql_unit}, ".freeze, ", timestamp(", "))}"], value, expr)
lib/sequel/extensions/migration.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/extensions/migration.rb 2026-05-04 02:37:42.090777447 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/migration.rb 2026-05-04 02:37:42.134777086 +0000
@@ -189,16 +189,13 @@
- begin
- instance_exec(&block)
- rescue
- just_raise = true
- end
- if just_raise
- Proc.new{raise Sequel::Error, "irreversible migration method used in #{block.source_location.first}, you may need to write your own down method"}
- else
- actions = @actions.reverse
- Proc.new do
- actions.each do |a|
- pr = a.last.is_a?(Proc) ? a.pop : nil
- # Allow calling private methods as the reversing methods are private
- send(*a, &pr)
- end
- end
+ instance_exec(&block)
+ rescue NoMethodError => e
+ 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"}
+ rescue => e
+ 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"}
+ else
+ actions = @actions.reverse
+ Proc.new do
+ actions.each do |a|
+ pr = a.last.is_a?(Proc) ? a.pop : nil
+ # Allow calling private methods as the reversing methods are private
+ send(*a, &pr)
+ end
@@ -273 +270 @@
- raise if args.first.is_a?(Array)
+ super if args.first.is_a?(Array)
lib/sequel/extensions/pg_enum.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:37:42.091777439 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/extensions/pg_enum.rb 2026-05-04 02:37:42.136777069 +0000
@@ -130 +130 @@
- run(sql)
+ run(sql.freeze)
lib/sequel/model/associations.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/model/associations.rb 2026-05-04 02:37:42.099777373 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/model/associations.rb 2026-05-04 02:37:42.144777004 +0000
@@ -114,0 +115,2 @@
+ when :lateral_subquery
+ apply_lateral_subquery_eager_graph_limit_strategy(ds)
@@ -301 +303,6 @@
- objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
+
+ objects = if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, ids, eager_limit).all
+ else
+ apply_eager_limit_strategy(ds, strategy, eager_limit).all
+ end
@@ -323 +330 @@
- objects.concat(ds.with_sql(sql).to_a)
+ objects.concat(ds.with_sql(sql.freeze).to_a)
@@ -369 +376,6 @@
- ds = filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ ds = if filter_by_associations_limit_strategy == :lateral_subquery
+ apply_lateral_subquery_filter_limit_strategy(associated_eager_dataset, obj)
+ else
+ filter_by_associations_conditions_dataset.where(filter_by_associations_conditions_subquery_conditions(obj))
+ end
+
@@ -756,0 +769,55 @@
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds.
+ where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys))).
+ limit(*limit_and_offset).
+ lateral
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ associated_table_name = associated_class.table_name
+
+ associated_class.
+ from(table_name).
+ select_all(associated_table_name).
+ join(lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset).as(associated_table_name), true).
+ order(*self[:order])
+ end
+
+ # Return an expression to filter the filter by associations dataset to only
+ # rows related to given objects.
+ def _lateral_subquery_filter_limit_strategy_conditions(obj, key, value_method, value_column)
+ value = case obj
+ when Array
+ if key.is_a?(Array)
+ key_methods = Array(value_method)
+ obj.map{|o| key_methods.map{|meth| o.send(meth)}}
+ else
+ obj.map{|o| o.send(value_method)}
+ end
+ when Sequel::Dataset
+ obj.select(*Array(qualify(associated_class.table_name, value_column)))
+ else
+ if key.is_a?(Array)
+ return Array(key).zip(Array(value_method).map{|meth| obj.send(meth)})
+ else
+ obj.send(value_method)
+ end
+ end
+
+ {key => value}
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds.
+ select(*qualify(associated_class.table_name, associated_class.primary_key)).
+ limit(*limit_and_offset).
+ lateral)
+ end
+
+ def apply_lateral_subquery_filter_limit_strategy(ds, obj)
+ lateral_subquery_filter_limit_strategy_filter_dataset(self[:model].
+ select(*lateral_subquery_filter_limit_strategy_lateral_dataset_select).
+ join(lateral_subquery_filter_limit_strategy_lateral_dataset(ds, obj).as(associated_class.table_name), filter_by_associations_conditions_subquery_conditions(obj)), obj)
+ end
+
@@ -779 +846,6 @@
- apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
+ strategy = eager_limit_strategy
+ if strategy == :lateral_subquery
+ apply_lateral_subquery_eager_limit_strategy(ds, arg, limit_and_offset)
+ else
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), strategy)
+ end
@@ -1021 +1093 @@
- # Support a correlated subquery limit strategy when using eager_graph.
+ # Support a lateral_subquery and correlated_subquery limit strategy when using eager_graph.
@@ -1120,0 +1193,48 @@
+ # Use a LATERAL subquery to limit the dataset. Note that this will not
+ # work correctly if the associated dataset uses qualified identifers in the WHERE clause,
+ # as they would reference the containing query instead of the subquery.
+ #
+ # This does not contain the conditions that are necessary to join to the
+ # query, since the necessary qualifier is not passed as an argument.
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ table_name = ds.first_source_alias
+ qualifier = ds.opts[:eager_options][:implicit_qualifier]
+ graph_conditions = self[:_graph_conditions]
+
+ unless Sequel.condition_specifier?(graph_conditions)
+ raise Error, "lateral_subquery eager graph limit strategy only supported when graph conditions are a hash or array of pairs"
+ end
+
+ ds.
+ limit(*limit_and_offset).
+ order(*self[:order]).
+ where(graph_conditions.map{|k, v| [qualify(table_name, k), qualify(qualifier, v)]}).
+ lateral
+ end
+
+ # Avoid setting duplicate predicate condition when using the lateral subquery
+ # eager limit strategy.
+ def eager_loading_set_predicate_condition(ds, eo)
+ eager_limit_strategy == :lateral_subquery ? ds : super
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ super.where(qualify(self[:model].table_name, self[:primary_key]) => ids)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, filter_by_associations_conditions_key, self[:key_method], self[:key])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(lateral_subquery_filter_limit_strategy_conditions(obj))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualified_primary_key
+ end
+
@@ -1434,0 +1555,46 @@
+ def lateral_subquery_filter_limit_strategy_conditions(obj)
+ _lateral_subquery_filter_limit_strategy_conditions(obj, lateral_subquery_filter_limit_strategy_conditions_key, right_primary_key_method, right_primary_key)
+ end
+
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ self[:right_key]
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(filter_by_associations_conditions_key).zip(Array(filter_by_associations_conditions_associated_keys)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ ds.where(filter_by_associations_conditions_key => ds.db.from(self[:join_table]).
+ select(*self[:left_key]).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj)))
+ end
+
+ def lateral_subquery_filter_limit_strategy_lateral_dataset_select
+ qualify(self[:model].table_name, self[:left_primary_key])
+ end
+
+ def lateral_subquery_eager_limit_strategy_lateral_dataset(ds, limit_and_offset)
+ ds = super
+ select = ds.opts[:select].dup
+ left_key_alias = self[:left_key_alias]
+ select.pop while (s = select.last).is_a?(Sequel::SQL::AliasedExpression) && (left_key_alias.is_a?(Array) ? left_key_alias.include?(s.alias) : s.alias == left_key_alias)
+ ds = ds.clone(:select=>select)
+ end
+
+ def apply_lateral_subquery_eager_limit_strategy(ds, ids, limit_and_offset)
+ table_name = self[:model].table_name
+ super.
+ select_all(associated_class.table_name).
+ select_append(*qualify(table_name, self[:left_primary_keys]).zip(Array(self[:left_key_alias])).map{|id, aliaz| Sequel.as(id, aliaz)}).
+ where(qualify(table_name, self[:left_primary_key]) => ids)
+ end
+
+ def apply_lateral_subquery_eager_graph_limit_strategy(ds)
+ ds.
+ limit(*limit_and_offset).
+ where(qualify(join_table_alias, self[:left_keys]).zip(qualify(ds.opts[:eager_options][:implicit_qualifier], self[:left_primary_key_columns]))).
+ order(*self[:order]).
+ lateral
+ end
+
@@ -2415,0 +2582 @@
+ graph_conditions = opts[:_graph_conditions] = use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions
@@ -2418 +2585,5 @@
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
+ graph_limit_strategy = eo[:limit_strategy]
+ egds = opts.apply_eager_graph_limit_strategy(graph_limit_strategy, eager_graph_dataset(opts, eo))
+ graph_conditions_true = true if graph_limit_strategy == :lateral_subquery
+
+ ds = ds.graph(egds, graph_conditions_true || graph_conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep), &graph_block)
@@ -2500,0 +2672,3 @@
+ if eager_options[:limit_strategy] == :lateral_subquery
+ ds = ds.clone(:eager_options=>eager_options)
+ end
lib/sequel/plugins/dataset_associations.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:37:42.104777332 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/plugins/dataset_associations.rb 2026-05-04 02:37:42.149776963 +0000
@@ -85 +85 @@
- ds = r.associated_class.dataset
+ klass = r.associated_class
@@ -86,0 +87,19 @@
+
+ if r.send(:filter_by_associations_limit_strategy) == :lateral_subquery
+ ds = r.send(:associated_eager_dataset)
+
+ case r[:type]
+ when :one_to_one, :one_to_many
+ sds = sds.select(*Array(r.qualified_primary_key))
+ else
+ sds = sds.select(*r[:left_primary_keys])
+ ds = ds.select_all(klass.table_name)
+ update_select = true
+ end
+
+ ds = r.send(:apply_lateral_subquery_eager_limit_strategy, ds, sds, r.limit_and_offset)
+ ds = ds.clone(:select=>ds.opts[:select][0,1]) if update_select
+ return ds.clone(:eager=>nil, :eager_graph=>nil)
+ end
+
+ ds = klass.dataset
lib/sequel/plugins/dirty.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:37:42.105777324 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/plugins/dirty.rb 2026-05-04 02:37:42.150776954 +0000
@@ -237,2 +237,5 @@
- check_missing_initial_value(column)
- iv[column] = get_column_value(column)
+ if db_schema[column]
+ check_missing_initial_value(column)
+ iv[column] = get_column_value(column)
+ end
+
lib/sequel/plugins/many_through_many.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:37:42.107777308 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/plugins/many_through_many.rb 2026-05-04 02:37:42.153776930 +0000
@@ -148,0 +149,21 @@
+ def lateral_subquery_filter_limit_strategy_conditions_key
+ qualify(reverse_edges.first[:table], reverse_edges.first[:left])
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_lateral_dataset(ds)
+ ds.where(Array(qualify(edges.first[:table], edges.first[:right])).zip(Array(qualify(self[:model].table_name, edges.first[:left]))))
+ end
+
+ def lateral_subquery_filter_limit_strategy_filter_dataset(ds, obj)
+ first_edge, *remaining_edges = edges
+ filter_ds = ds.db.from(first_edge[:table]).
+ select(*qualify(first_edge[:table], first_edge[:right])).
+ where(lateral_subquery_filter_limit_strategy_conditions(obj))
+
+ remaining_edges.each do |edge|
+ filter_ds = filter_ds.join(edge[:table], Array(edge[:right]).zip(Array(edge[:left])))
+ end
+
+ ds.where(qualify(self[:model].table_name, self[:left_primary_key]) => filter_ds)
+ end
+
lib/sequel/plugins/mssql_optimistic_locking.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:37:42.107777308 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/plugins/mssql_optimistic_locking.rb 2026-05-04 02:37:42.153776930 +0000
@@ -58 +58 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.output(nil, [Sequel[:inserted][lc]]).update_sql(columns).freeze)).all
lib/sequel/plugins/pg_xmin_optimistic_locking.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:37:42.109777291 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/plugins/pg_xmin_optimistic_locking.rb 2026-05-04 02:37:42.154776922 +0000
@@ -102 +102 @@
- rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns).freeze)).all
lib/sequel/plugins/serialization.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:37:42.110777283 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/plugins/serialization.rb 2026-05-04 02:37:42.155776913 +0000
@@ -32,0 +33,9 @@
+ # Note that use of an unsafe serialization method can result in an attack vector
+ # (potentially allowing remote code execution) if an attacker has the ability to
+ # store data directly in the underlying column. This would affect the marshal
+ # serialization format, and on older versions of Ruby, potentially the yaml and
+ # json serialization formats as well. It can also affect custom formats. You
+ # should ensure that attackers do not have access to store data directly in the
+ # underlying column when using this plugin (especially when using an unsafe
+ # serialization method).
+ #
@@ -100 +109 @@
- v = v.unpack('m')[0] unless v[0..1] == "\x04\x08"
+ v = v.unpack('m')[0] unless v.start_with?("\x04\x08")
lib/sequel/version.rb
--- /tmp/d20260504-1888-jhtntp/sequel-5.101.0/lib/sequel/version.rb 2026-05-04 02:37:42.114777250 +0000
+++ /tmp/d20260504-1888-jhtntp/sequel-5.104.0/lib/sequel/version.rb 2026-05-04 02:37:42.161776864 +0000
@@ -9 +9 @@
- MINOR = 101
+ MINOR = 104 |
Contributor
Author
|
Looks like sequel is up-to-date now, so this is no longer needed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps sequel from 5.101.0 to 5.104.0.
Changelog
Sourced from sequel's changelog.
Commits
4642b4aBump version to 5.104.067d897cFix some minor issues in the Sequel for SQL users guidebdf0c1eAdd JRuby 10.1 to matrixf060c6ctest: fix must_raise assertions on minitest 6.0.5d40f4d3Skip lateral_subquery association tests on MSSQL and SQLAnywhere025bdf7Simplify lateral_subquery integration testingb826e2bSupport :lateral_subquery as a filter by associations limit strategy in the d...98a70d2Refactor :lateral_subquery eager limit strategy support1ad2e96Support :lateral_subquery as an eager limit strategy for many_through_many/on...49c7f74Refactor :lateral_subquery filter by associations limit strategy codeDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)