diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0620dd9..eede773 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,16 @@ 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" + rails: "8.1" + - ruby: "jruby" + rails: "8.1" name: Rails ${{ matrix.rails }}, Ruby ${{ matrix.ruby }}, Sequel ${{ matrix.sequel }} env: diff --git a/ci/rails-8.1.gemfile b/ci/rails-8.1.gemfile new file mode 100644 index 0000000..3bd5502 --- /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 diff --git a/lib/sequel_rails/railtie.rb b/lib/sequel_rails/railtie.rb index 8ac5657..2bb03a8 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 683e47b..fb9c0a8 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 0000000..f1acf88 --- /dev/null +++ b/lib/sequel_rails/railties/test_databases.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +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' + + 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 +end diff --git a/lib/sequel_rails/storage.rb b/lib/sequel_rails/storage.rb index 9be2e09..a8fac3d 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 5fe5b6f..f50fa92 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 2927b89..4e905c6 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 299c236..a3bf1c5 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