From 98b3e1cddfc4cd01ccc68c0c90919b0f258e8b56 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sat, 6 Jun 2026 15:54:38 +0200 Subject: [PATCH 1/5] Add parallelization support. --- lib/sequel_rails/railtie.rb | 1 + lib/sequel_rails/railties/database.rake | 4 +- lib/sequel_rails/railties/test_databases.rb | 63 +++++++++++++++++++ lib/sequel_rails/storage.rb | 4 ++ lib/sequel_rails/storage/postgres.rb | 7 ++- .../lib/sequel_rails/storage/postgres_spec.rb | 4 +- spec/lib/sequel_rails/storage_spec.rb | 22 +++++++ 7 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 lib/sequel_rails/railties/test_databases.rb diff --git a/lib/sequel_rails/railtie.rb b/lib/sequel_rails/railtie.rb index 8ac5657f..2bb03a84 100755 --- a/lib/sequel_rails/railtie.rb +++ b/lib/sequel_rails/railtie.rb @@ -16,6 +16,7 @@ require 'sequel_rails/railties/log_subscriber' require 'sequel_rails/railties/i18n_support' require 'sequel_rails/railties/controller_runtime' +require 'sequel_rails/railties/test_databases' require 'sequel_rails/sequel/database/active_support_notification' require 'action_dispatch/middleware/session/sequel_store' diff --git a/lib/sequel_rails/railties/database.rake b/lib/sequel_rails/railties/database.rake index 683e47b1..fb9c0a88 100644 --- a/lib/sequel_rails/railties/database.rake +++ b/lib/sequel_rails/railties/database.rake @@ -54,7 +54,7 @@ namespace sequel_rails_namespace do db_for_current_env args.with_defaults(:env => Rails.env) - filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, 'db', 'structure.sql') + filename = SequelRails::Storage.structure_path if SequelRails::Storage.dump_environment args.env, filename ::File.open filename, 'a' do |file| file << SequelRails::Migrations.dump_schema_information(:sql => true) @@ -69,7 +69,7 @@ namespace sequel_rails_namespace do task :load, [:env] => :environment do |_t, args| args.with_defaults(:env => Rails.env) - filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, 'db', 'structure.sql') + filename = SequelRails::Storage.structure_path unless SequelRails::Storage.load_environment args.env, filename abort "Could not load structure for #{args.env}." end diff --git a/lib/sequel_rails/railties/test_databases.rb b/lib/sequel_rails/railties/test_databases.rb new file mode 100644 index 00000000..cccda452 --- /dev/null +++ b/lib/sequel_rails/railties/test_databases.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "tempfile" +require "active_support/testing/parallelization" +require "sequel_rails/storage" + +module SequelRails + module Railties + module TestDatabases # :nodoc: + ActiveSupport::Testing::Parallelization.before_fork_hook do + # Drop the parent's connections before forking so children do not inherit live + # sockets. The db object associated with Sequel::Model and children is reused. + Sequel::DATABASES.each(&:disconnect) + end + + ActiveSupport::Testing::Parallelization.after_fork_hook do |i| + # On macOS, libpq's TCP connection path logs through the os_log subsystem while + # establishing a connection. After fork, os_log's per-process state is invalid in + # the child and the first connection segfaults inside _os_log_preferences_refresh. + # Disabling os_log activity tracing avoids this. + ENV["OS_ACTIVITY_MODE"] = "disable" + + # Contstruct a worker-specific db config. + base_config = SequelRails.configuration.environments[Rails.env.to_s] + base_database = base_config["database"] + + db_config = base_config.except("url").merge("database" => "#{base_database}_#{i}") + + Kernel.silence_warnings do + SequelRails::Storage.drop_environment(db_config) + + # Attempt to create a database from a template (postgres only) if supported, + # otherwise create an empty database and load the schema into it. + if base_config.respond_to?(:template) + # Clone the parent database. This is faster and ensures OIDs and other + # database-level state is identical. + db_config.merge!("template" => base_database, "maintenance_db" => "postgres") + SequelRails::Storage.create_environment(db_config) + else + # Create a blank worker database and load the schema into it. + SequelRails::Storage.create_environment(db_config) + filename = SequelRails::Storage.structure_path + SequelRails::Storage.load_environment(db_config, filename) + end + end + + # Model classes capture their database object when they are defined, so + # every already-loaded model references the boot-time database object. + # Update the reference to the worker database. + db = Sequel::Model.db + + # TODO: Ideally the logic below is captured in a reconnect() method on the db. + db.disconnect + db.opts[:database] = db_config["database"] + + # Re-initialize extensions with the new database connection. + db.instance_variable_get(:@loaded_extensions).each do |ext| + ::Sequel::Database::EXTENSIONS[ext].call(db) + end + end + end + end +end diff --git a/lib/sequel_rails/storage.rb b/lib/sequel_rails/storage.rb index 9be2e096..a8fac3d3 100644 --- a/lib/sequel_rails/storage.rb +++ b/lib/sequel_rails/storage.rb @@ -8,6 +8,10 @@ module SequelRails module Storage + def self.structure_path + ENV['DB_STRUCTURE'] || File.join(Rails.root, 'db', 'structure.sql') + end + def self.create_all with_local_repositories { |config| create_environment(config) } end diff --git a/lib/sequel_rails/storage/postgres.rb b/lib/sequel_rails/storage/postgres.rb index 5fe5b6f9..f50fa92e 100644 --- a/lib/sequel_rails/storage/postgres.rb +++ b/lib/sequel_rails/storage/postgres.rb @@ -23,6 +23,7 @@ def _drop commands = ['dropdb'] add_connection_settings commands add_flag commands, '--if-exists' + add_flag commands, '--force' commands << database safe_exec commands end @@ -32,9 +33,9 @@ def _dump(filename) with_pgpassword do commands = ['pg_dump'] add_connection_settings commands - add_flag commands, '-s' - add_flag commands, '-x' - add_flag commands, '-O' + add_flag commands, '--schema-only' + add_flag commands, '--no-privileges' + add_flag commands, '--no-owner' add_option commands, '--file', filename commands << database safe_exec commands diff --git a/spec/lib/sequel_rails/storage/postgres_spec.rb b/spec/lib/sequel_rails/storage/postgres_spec.rb index 2927b89a..4e905c6b 100644 --- a/spec/lib/sequel_rails/storage/postgres_spec.rb +++ b/spec/lib/sequel_rails/storage/postgres_spec.rb @@ -69,7 +69,7 @@ describe '#_drop' do it 'uses the dropdb command' do expect(subject).to receive(:`).with( - "dropdb --username\\=#{username} --host\\=#{host} --port\\=#{port} --if-exists #{database}" + "dropdb --username\\=#{username} --host\\=#{host} --port\\=#{port} --if-exists --force #{database}" ) subject._drop end @@ -98,7 +98,7 @@ let(:dump_file_name) { 'dump.sql' } it 'uses the pg_dump command' do expect(subject).to receive(:`).with( - "pg_dump --username\\=#{username} --host\\=#{host} --port\\=#{port} -s -x -O --file\\=#{dump_file_name} #{database}" + "pg_dump --username\\=#{username} --host\\=#{host} --port\\=#{port} --schema-only --no-privileges --no-owner --file\\=#{dump_file_name} #{database}" ) subject._dump dump_file_name end diff --git a/spec/lib/sequel_rails/storage_spec.rb b/spec/lib/sequel_rails/storage_spec.rb index 299c2365..a3bf1c5f 100644 --- a/spec/lib/sequel_rails/storage_spec.rb +++ b/spec/lib/sequel_rails/storage_spec.rb @@ -123,4 +123,26 @@ end end end + describe '.structure_path' do + around do |example| + original = ENV['DB_STRUCTURE'] + example.run + ensure + if original.nil? + ENV.delete('DB_STRUCTURE') + else + ENV['DB_STRUCTURE'] = original + end + end + + it 'defaults to db/structure.sql under the Rails root' do + ENV.delete('DB_STRUCTURE') + expect(described_class.structure_path).to eq(File.join(Rails.root, 'db', 'structure.sql')) + end + + it 'uses the DB_STRUCTURE environment variable when set' do + ENV['DB_STRUCTURE'] = '/custom/path/structure.sql' + expect(described_class.structure_path).to eq('/custom/path/structure.sql') + end + end end From a4c4a6b8b32f78c1f8a43cdf68c548ee33dd750a Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sat, 6 Jun 2026 16:21:35 +0200 Subject: [PATCH 2/5] Drop unnecessary before hook, properly detect parallelization support and add 8.1 to test matrix. --- .github/workflows/ci.yml | 7 ++ lib/sequel_rails/railties/test_databases.rb | 94 ++++++++++----------- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0620dd90..6fceb939 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,7 @@ jobs: - "7.1" - "7.2" - "8.0" + - "8.1" sequel: - "~> 5.0" ruby: @@ -97,6 +98,12 @@ jobs: rails: "8.0" - ruby: "jruby" rails: "8.0" + - ruby: "3.0" + rails: "8.1" + - ruby: "3.1" + rails: "8.1" + - ruby: "jruby" + rails: "8.1" name: Rails ${{ matrix.rails }}, Ruby ${{ matrix.ruby }}, Sequel ${{ matrix.sequel }} env: diff --git a/lib/sequel_rails/railties/test_databases.rb b/lib/sequel_rails/railties/test_databases.rb index cccda452..1f779b0d 100644 --- a/lib/sequel_rails/railties/test_databases.rb +++ b/lib/sequel_rails/railties/test_databases.rb @@ -1,61 +1,57 @@ # frozen_string_literal: true -require "tempfile" -require "active_support/testing/parallelization" -require "sequel_rails/storage" - module SequelRails module Railties module TestDatabases # :nodoc: - ActiveSupport::Testing::Parallelization.before_fork_hook do - # Drop the parent's connections before forking so children do not inherit live - # sockets. The db object associated with Sequel::Model and children is reused. - Sequel::DATABASES.each(&:disconnect) - end - - ActiveSupport::Testing::Parallelization.after_fork_hook do |i| - # On macOS, libpq's TCP connection path logs through the os_log subsystem while - # establishing a connection. After fork, os_log's per-process state is invalid in - # the child and the first connection segfaults inside _os_log_preferences_refresh. - # Disabling os_log activity tracing avoids this. - ENV["OS_ACTIVITY_MODE"] = "disable" - - # Contstruct a worker-specific db config. - base_config = SequelRails.configuration.environments[Rails.env.to_s] - base_database = base_config["database"] - - db_config = base_config.except("url").merge("database" => "#{base_database}_#{i}") - - Kernel.silence_warnings do - SequelRails::Storage.drop_environment(db_config) - - # Attempt to create a database from a template (postgres only) if supported, - # otherwise create an empty database and load the schema into it. - if base_config.respond_to?(:template) - # Clone the parent database. This is faster and ensures OIDs and other - # database-level state is identical. - db_config.merge!("template" => base_database, "maintenance_db" => "postgres") - SequelRails::Storage.create_environment(db_config) - else - # Create a blank worker database and load the schema into it. - SequelRails::Storage.create_environment(db_config) - filename = SequelRails::Storage.structure_path - SequelRails::Storage.load_environment(db_config, filename) + if ActiveSupport.respond_to?(:parallelize_test_databases) + require "tempfile" + require "sequel_rails/storage" + require "active_support/testing/parallelization" + + ActiveSupport::Testing::Parallelization.after_fork_hook do |i| + # On macOS, libpq's TCP connection path logs through the os_log subsystem while + # establishing a connection. After fork, os_log's per-process state is invalid in + # the child and the first connection segfaults inside _os_log_preferences_refresh. + # Disabling os_log activity tracing avoids this. + ENV["OS_ACTIVITY_MODE"] = "disable" + + # Contstruct a worker-specific db config. + base_config = SequelRails.configuration.environments[Rails.env.to_s] + base_database = base_config["database"] + + db_config = base_config.except("url").merge("database" => "#{base_database}_#{i}") + + Kernel.silence_warnings do + SequelRails::Storage.drop_environment(db_config) + + # Attempt to create a database from a template (postgres only) if supported, + # otherwise create an empty database and load the schema into it. + if base_config.respond_to?(:template) + # Clone the parent database. This is faster and ensures OIDs and other + # database-level state is identical. + db_config.merge!("template" => base_database, "maintenance_db" => "postgres") + SequelRails::Storage.create_environment(db_config) + else + # Create a blank worker database and load the schema into it. + SequelRails::Storage.create_environment(db_config) + filename = SequelRails::Storage.structure_path + SequelRails::Storage.load_environment(db_config, filename) + end end - end - # Model classes capture their database object when they are defined, so - # every already-loaded model references the boot-time database object. - # Update the reference to the worker database. - db = Sequel::Model.db + # Model classes capture their database object when they are defined, so + # every already-loaded model references the boot-time database object. + # Update the reference to the worker database. + db = Sequel::Model.db - # TODO: Ideally the logic below is captured in a reconnect() method on the db. - db.disconnect - db.opts[:database] = db_config["database"] + # TODO: Ideally the logic below is captured in a reconnect() method on the db. + db.disconnect + db.opts[:database] = db_config["database"] - # Re-initialize extensions with the new database connection. - db.instance_variable_get(:@loaded_extensions).each do |ext| - ::Sequel::Database::EXTENSIONS[ext].call(db) + # Re-initialize extensions with the new database connection. + db.instance_variable_get(:@loaded_extensions).each do |ext| + ::Sequel::Database::EXTENSIONS[ext].call(db) + end end end end From 00e3d492860f2790e5531e013ee83d5edd8644a3 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sat, 6 Jun 2026 16:22:29 +0200 Subject: [PATCH 3/5] Adjust matrix. --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fceb939..eede7732 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,10 @@ jobs: rails: "8.0" - ruby: "jruby" rails: "8.0" + - ruby: "2.6" + rails: "8.1" + - ruby: "2.7" + rails: "8.1" - ruby: "3.0" rails: "8.1" - ruby: "3.1" From c7854366a9ccb51e630388b5a49a124ced9ff35e Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sat, 6 Jun 2026 16:23:13 +0200 Subject: [PATCH 4/5] Add 8.1 gemfile. --- ci/rails-8.1.gemfile | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 ci/rails-8.1.gemfile diff --git a/ci/rails-8.1.gemfile b/ci/rails-8.1.gemfile new file mode 100644 index 00000000..3bd5502b --- /dev/null +++ b/ci/rails-8.1.gemfile @@ -0,0 +1,27 @@ +source 'https://rubygems.org' + +gem 'railties', '~> 8.1.0' +gem 'activemodel', '~> 8.1.0' +gem 'actionpack', '~> 8.1.0' + +gemspec :path => '../' + +gem 'sequel', "#{ENV['SEQUEL']}" + +gem 'fakefs', '>= 1.8.0', :require => 'fakefs/safe' + +gem 'rspec-rails', '~> 5.0' + +# MRI/Rubinius Adapter Dependencies +platform :ruby do + gem 'pg' + gem 'mysql2' + gem 'sqlite3' +end + +# JRuby Adapter Dependencies +platform :jruby do + gem 'jdbc-sqlite3' + gem 'jdbc-mysql' + gem 'jdbc-postgres' +end From 48c87362efa5ab02b22cdefd0bdba5e8b79a2645 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sat, 6 Jun 2026 16:26:55 +0200 Subject: [PATCH 5/5] Style adjustments. --- lib/sequel_rails/railties/test_databases.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/sequel_rails/railties/test_databases.rb b/lib/sequel_rails/railties/test_databases.rb index 1f779b0d..f1acf88e 100644 --- a/lib/sequel_rails/railties/test_databases.rb +++ b/lib/sequel_rails/railties/test_databases.rb @@ -4,22 +4,22 @@ module SequelRails module Railties module TestDatabases # :nodoc: if ActiveSupport.respond_to?(:parallelize_test_databases) - require "tempfile" - require "sequel_rails/storage" - require "active_support/testing/parallelization" + require 'tempfile' + require 'sequel_rails/storage' + require 'active_support/testing/parallelization' ActiveSupport::Testing::Parallelization.after_fork_hook do |i| # On macOS, libpq's TCP connection path logs through the os_log subsystem while # establishing a connection. After fork, os_log's per-process state is invalid in # the child and the first connection segfaults inside _os_log_preferences_refresh. # Disabling os_log activity tracing avoids this. - ENV["OS_ACTIVITY_MODE"] = "disable" + ENV['OS_ACTIVITY_MODE'] = 'disable' # Contstruct a worker-specific db config. base_config = SequelRails.configuration.environments[Rails.env.to_s] - base_database = base_config["database"] + base_database = base_config['database'] - db_config = base_config.except("url").merge("database" => "#{base_database}_#{i}") + db_config = base_config.except('url').merge('database' => "#{base_database}_#{i}") Kernel.silence_warnings do SequelRails::Storage.drop_environment(db_config) @@ -29,7 +29,7 @@ module TestDatabases # :nodoc: if base_config.respond_to?(:template) # Clone the parent database. This is faster and ensures OIDs and other # database-level state is identical. - db_config.merge!("template" => base_database, "maintenance_db" => "postgres") + db_config.merge!('template' => base_database, 'maintenance_db' => 'postgres') SequelRails::Storage.create_environment(db_config) else # Create a blank worker database and load the schema into it. @@ -46,7 +46,7 @@ module TestDatabases # :nodoc: # TODO: Ideally the logic below is captured in a reconnect() method on the db. db.disconnect - db.opts[:database] = db_config["database"] + db.opts[:database] = db_config['database'] # Re-initialize extensions with the new database connection. db.instance_variable_get(:@loaded_extensions).each do |ext|