mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Allow truncate
for SQLite3 adapter and add rails db:seed:replant
(#34779)
* Add `ActiveRecord::Base.connection.truncate` for SQLite3 adapter. SQLite doesn't support `TRUNCATE TABLE`, but SQLite3 adapter can support `ActiveRecord::Base.connection.truncate` by using `DELETE FROM`. `DELETE` without `WHERE` uses "The Truncate Optimization", see https://www.sqlite.org/lang_delete.html. * Add `rails db:seed:replant` that truncates database tables and loads the seeds Closes #34765
This commit is contained in:
parent
076e8eddf4
commit
a8c0ebccbd
10 changed files with 334 additions and 1 deletions
|
@ -1,3 +1,12 @@
|
||||||
|
* Add `rails db:seed:replant` that truncates tables of each database
|
||||||
|
for current environment and loads the seeds.
|
||||||
|
|
||||||
|
*bogdanvlviv*, *DHH*
|
||||||
|
|
||||||
|
* Add `ActiveRecord::Base.connection.truncate` for SQLite3 adapter.
|
||||||
|
|
||||||
|
*bogdanvlviv*
|
||||||
|
|
||||||
* Deprecate mismatched collation comparison for uniqueness validator.
|
* Deprecate mismatched collation comparison for uniqueness validator.
|
||||||
|
|
||||||
Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
|
Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
|
||||||
|
|
|
@ -154,6 +154,10 @@ module ActiveRecord
|
||||||
@statements.clear
|
@statements.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def truncate(table_name, name = nil)
|
||||||
|
execute "DELETE FROM #{quote_table_name(table_name)}", name
|
||||||
|
end
|
||||||
|
|
||||||
def supports_index_sort_order?
|
def supports_index_sort_order?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -66,6 +66,11 @@ db_namespace = namespace :db do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# desc "Truncates tables of each database for current environment"
|
||||||
|
task truncate_all: [:load_config, :check_protected_environments] do
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.truncate_all
|
||||||
|
end
|
||||||
|
|
||||||
# desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
|
# desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
|
||||||
task purge: [:load_config, :check_protected_environments] do
|
task purge: [:load_config, :check_protected_environments] do
|
||||||
ActiveRecord::Tasks::DatabaseTasks.purge_current
|
ActiveRecord::Tasks::DatabaseTasks.purge_current
|
||||||
|
@ -223,6 +228,11 @@ db_namespace = namespace :db do
|
||||||
ActiveRecord::Tasks::DatabaseTasks.load_seed
|
ActiveRecord::Tasks::DatabaseTasks.load_seed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
namespace :seed do
|
||||||
|
desc "Truncates tables of each database for current environment and loads the seeds"
|
||||||
|
task replant: [:load_config, :truncate_all, :seed]
|
||||||
|
end
|
||||||
|
|
||||||
namespace :fixtures do
|
namespace :fixtures do
|
||||||
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
|
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
|
||||||
task load: :load_config do
|
task load: :load_config do
|
||||||
|
|
|
@ -182,6 +182,24 @@ module ActiveRecord
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def truncate_tables(configuration)
|
||||||
|
ActiveRecord::Base.connected_to(database: { truncation: configuration }) do
|
||||||
|
table_names = ActiveRecord::Base.connection.tables
|
||||||
|
internal_table_names = [
|
||||||
|
ActiveRecord::Base.schema_migrations_table_name,
|
||||||
|
ActiveRecord::Base.internal_metadata_table_name
|
||||||
|
]
|
||||||
|
|
||||||
|
class_for_adapter(configuration["adapter"]).new(configuration).truncate_tables(*table_names.without(*internal_table_names))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_all(environment = env)
|
||||||
|
ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
|
||||||
|
truncate_tables db_config.config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def migrate
|
def migrate
|
||||||
check_target_version
|
check_target_version
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,16 @@ module ActiveRecord
|
||||||
connection.recreate_database configuration["database"], creation_options
|
connection.recreate_database configuration["database"], creation_options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def truncate_tables(*table_names)
|
||||||
|
return if table_names.empty?
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.disable_referential_integrity do
|
||||||
|
table_names.each do |table_name|
|
||||||
|
ActiveRecord::Base.connection.truncate(table_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def charset
|
def charset
|
||||||
connection.charset
|
connection.charset
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,6 +48,18 @@ module ActiveRecord
|
||||||
create true
|
create true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def truncate_tables(*table_names)
|
||||||
|
return if table_names.empty?
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.disable_referential_integrity do
|
||||||
|
quoted_table_names = table_names.map do |table_name|
|
||||||
|
ActiveRecord::Base.connection.quote_table_name(table_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.execute "TRUNCATE TABLE #{quoted_table_names.join(", ")}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def structure_dump(filename, extra_flags)
|
def structure_dump(filename, extra_flags)
|
||||||
set_psql_env
|
set_psql_env
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,16 @@ module ActiveRecord
|
||||||
create
|
create
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def truncate_tables(*table_names)
|
||||||
|
return if table_names.empty?
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.disable_referential_integrity do
|
||||||
|
table_names.each do |table_name|
|
||||||
|
ActiveRecord::Base.connection.truncate(table_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def charset
|
def charset
|
||||||
connection.encoding
|
connection.encoding
|
||||||
end
|
end
|
||||||
|
|
18
activerecord/test/cases/adapters/sqlite3/connection_test.rb
Normal file
18
activerecord/test/cases/adapters/sqlite3/connection_test.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "cases/helper"
|
||||||
|
|
||||||
|
class SQLite3ConnectionTest < ActiveRecord::SQLite3TestCase
|
||||||
|
fixtures :comments
|
||||||
|
|
||||||
|
def test_truncate
|
||||||
|
rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
|
||||||
|
count = rows.first.values.first
|
||||||
|
assert_operator count, :>, 0
|
||||||
|
|
||||||
|
ActiveRecord::Base.connection.truncate("comments")
|
||||||
|
rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
|
||||||
|
count = rows.first.values.first
|
||||||
|
assert_equal 0, count
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
require "cases/helper"
|
require "cases/helper"
|
||||||
require "active_record/tasks/database_tasks"
|
require "active_record/tasks/database_tasks"
|
||||||
|
require "models/author"
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
module DatabaseTasksSetupper
|
module DatabaseTasksSetupper
|
||||||
|
@ -944,6 +945,127 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless in_memory_db?
|
||||||
|
class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
|
||||||
|
self.use_transactional_tests = false
|
||||||
|
|
||||||
|
fixtures :authors, :author_addresses
|
||||||
|
|
||||||
|
def test_truncate_tables
|
||||||
|
assert_operator Author.count, :>, 0
|
||||||
|
assert_operator AuthorAddress.count, :>, 0
|
||||||
|
|
||||||
|
old_configurations = ActiveRecord::Base.configurations
|
||||||
|
configurations = { development: ActiveRecord::Base.configurations["arunit"] }
|
||||||
|
ActiveRecord::Base.configurations = configurations
|
||||||
|
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.stub(:root, nil) do
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
|
||||||
|
ActiveSupport::StringInquirer.new("development")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 0, Author.count
|
||||||
|
assert_equal 0, AuthorAddress.count
|
||||||
|
ensure
|
||||||
|
ActiveRecord::Base.configurations = old_configurations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DatabaseTasksTruncateAllWithMultipleDatabasesTest < ActiveRecord::TestCase
|
||||||
|
def setup
|
||||||
|
@configurations = {
|
||||||
|
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
|
||||||
|
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
|
||||||
|
"production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_truncate_all_databases_for_environment
|
||||||
|
with_stubbed_configurations do
|
||||||
|
assert_called_with(
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks,
|
||||||
|
:truncate_tables,
|
||||||
|
[
|
||||||
|
["database" => "test-db"],
|
||||||
|
["database" => "secondary-test-db"]
|
||||||
|
]
|
||||||
|
) do
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
|
||||||
|
ActiveSupport::StringInquirer.new("test")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_truncate_all_databases_with_url_for_environment
|
||||||
|
with_stubbed_configurations do
|
||||||
|
assert_called_with(
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks,
|
||||||
|
:truncate_tables,
|
||||||
|
[
|
||||||
|
["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
|
||||||
|
["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
|
||||||
|
]
|
||||||
|
) do
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
|
||||||
|
ActiveSupport::StringInquirer.new("production")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_truncate_all_development_databases_when_env_was_no_specified
|
||||||
|
with_stubbed_configurations do
|
||||||
|
assert_called_with(
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks,
|
||||||
|
:truncate_tables,
|
||||||
|
[
|
||||||
|
["database" => "dev-db"],
|
||||||
|
["database" => "secondary-dev-db"]
|
||||||
|
]
|
||||||
|
) do
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
|
||||||
|
ActiveSupport::StringInquirer.new("development")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_truncate_all_development_databases_when_env_is_development
|
||||||
|
old_env = ENV["RAILS_ENV"]
|
||||||
|
ENV["RAILS_ENV"] = "development"
|
||||||
|
|
||||||
|
with_stubbed_configurations do
|
||||||
|
assert_called_with(
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks,
|
||||||
|
:truncate_tables,
|
||||||
|
[
|
||||||
|
["database" => "dev-db"],
|
||||||
|
["database" => "secondary-dev-db"]
|
||||||
|
]
|
||||||
|
) do
|
||||||
|
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
|
||||||
|
ActiveSupport::StringInquirer.new("development")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
ENV["RAILS_ENV"] = old_env
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def with_stubbed_configurations
|
||||||
|
old_configurations = ActiveRecord::Base.configurations
|
||||||
|
ActiveRecord::Base.configurations = @configurations
|
||||||
|
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
ActiveRecord::Base.configurations = old_configurations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class DatabaseTasksCharsetTest < ActiveRecord::TestCase
|
class DatabaseTasksCharsetTest < ActiveRecord::TestCase
|
||||||
include DatabaseTasksSetupper
|
include DatabaseTasksSetupper
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "isolation/abstract_unit"
|
require "isolation/abstract_unit"
|
||||||
|
require "env_helpers"
|
||||||
|
|
||||||
module ApplicationTests
|
module ApplicationTests
|
||||||
module RakeTests
|
module RakeTests
|
||||||
class RakeDbsTest < ActiveSupport::TestCase
|
class RakeDbsTest < ActiveSupport::TestCase
|
||||||
include ActiveSupport::Testing::Isolation
|
include ActiveSupport::Testing::Isolation, EnvHelpers
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
build_app
|
build_app
|
||||||
|
@ -139,6 +140,59 @@ module ApplicationTests
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "db:truncate_all truncates all not internal tables" do
|
||||||
|
Dir.chdir(app_path) do
|
||||||
|
rails "generate", "model", "book", "title:string"
|
||||||
|
rails "db:migrate"
|
||||||
|
require "#{app_path}/config/environment"
|
||||||
|
Book.create!(title: "Remote")
|
||||||
|
assert_equal 1, Book.count
|
||||||
|
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
|
||||||
|
rails "db:truncate_all"
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
schema_migrations,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
internal_metadata,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal 0, Book.count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "db:truncate_all does not truncate any tables when environment is protected" do
|
||||||
|
with_rails_env "production" do
|
||||||
|
Dir.chdir(app_path) do
|
||||||
|
rails "generate", "model", "book", "title:string"
|
||||||
|
rails "db:migrate"
|
||||||
|
require "#{app_path}/config/environment"
|
||||||
|
Book.create!(title: "Remote")
|
||||||
|
assert_equal 1, Book.count
|
||||||
|
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"")
|
||||||
|
|
||||||
|
output = rails("db:truncate_all", allow_failure: true)
|
||||||
|
assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
schema_migrations,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
internal_metadata,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal 1, Book.count
|
||||||
|
assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\""))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def db_migrate_and_status(expected_database)
|
def db_migrate_and_status(expected_database)
|
||||||
rails "generate", "model", "book", "title:string"
|
rails "generate", "model", "book", "title:string"
|
||||||
rails "db:migrate"
|
rails "db:migrate"
|
||||||
|
@ -387,6 +441,72 @@ module ApplicationTests
|
||||||
|
|
||||||
assert_equal "test", test_environment.call
|
assert_equal "test", test_environment.call
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "db:seed:replant truncates all not internal tables and loads the seeds" do
|
||||||
|
Dir.chdir(app_path) do
|
||||||
|
rails "generate", "model", "book", "title:string"
|
||||||
|
rails "db:migrate"
|
||||||
|
require "#{app_path}/config/environment"
|
||||||
|
Book.create!(title: "Remote")
|
||||||
|
assert_equal 1, Book.count
|
||||||
|
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
|
||||||
|
app_file "db/seeds.rb", <<-RUBY
|
||||||
|
Book.create!(title: "Rework")
|
||||||
|
Book.create!(title: "Ruby Under a Microscope")
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
rails "db:seed:replant"
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
schema_migrations,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
internal_metadata,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal 2, Book.count
|
||||||
|
assert_not_predicate Book.where(title: "Remote"), :exists?
|
||||||
|
assert_predicate Book.where(title: "Rework"), :exists?
|
||||||
|
assert_predicate Book.where(title: "Ruby Under a Microscope"), :exists?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "db:seed:replant does not truncate any tables and does not load the seeds when environment is protected" do
|
||||||
|
with_rails_env "production" do
|
||||||
|
Dir.chdir(app_path) do
|
||||||
|
rails "generate", "model", "book", "title:string"
|
||||||
|
rails "db:migrate"
|
||||||
|
require "#{app_path}/config/environment"
|
||||||
|
Book.create!(title: "Remote")
|
||||||
|
assert_equal 1, Book.count
|
||||||
|
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"")
|
||||||
|
|
||||||
|
app_file "db/seeds.rb", <<-RUBY
|
||||||
|
Book.create!(title: "Rework")
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
output = rails("db:seed:replant", allow_failure: true)
|
||||||
|
assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
schema_migrations,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
internal_metadata,
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
|
||||||
|
)
|
||||||
|
assert_equal 1, Book.count
|
||||||
|
assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\""))
|
||||||
|
assert_not_predicate Book.where(title: "Rework"), :exists?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue