mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add bulk alter support for PostgreSQL
This commit is contained in:
parent
07788c7ad8
commit
8dd4868cc1
4 changed files with 68 additions and 13 deletions
|
@ -364,6 +364,31 @@ module ActiveRecord
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bulk_change_table(table_name, operations)
|
||||||
|
sql_fragments = []
|
||||||
|
non_combinable_operations = []
|
||||||
|
|
||||||
|
operations.each do |command, args|
|
||||||
|
table, arguments = args.shift, args
|
||||||
|
method = :"#{command}_for_alter"
|
||||||
|
|
||||||
|
if respond_to?(method, true)
|
||||||
|
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
||||||
|
sql_fragments << sqls
|
||||||
|
non_combinable_operations << procs if procs.present?
|
||||||
|
else
|
||||||
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
||||||
|
non_combinable_operations.each(&:call)
|
||||||
|
sql_fragments = []
|
||||||
|
non_combinable_operations = []
|
||||||
|
send(command, table, *arguments)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
|
||||||
|
non_combinable_operations.each(&:call)
|
||||||
|
end
|
||||||
|
|
||||||
# Renames a table.
|
# Renames a table.
|
||||||
# Also renames a table's primary key sequence if the sequence name exists and
|
# Also renames a table's primary key sequence if the sequence name exists and
|
||||||
# matches the Active Record default.
|
# matches the Active Record default.
|
||||||
|
@ -394,7 +419,7 @@ module ActiveRecord
|
||||||
|
|
||||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||||
clear_cache!
|
clear_cache!
|
||||||
sqls, procs = change_column_for_alter(table_name, column_name, type, options)
|
sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
|
||||||
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
|
||||||
procs.each(&:call)
|
procs.each(&:call)
|
||||||
end
|
end
|
||||||
|
@ -665,12 +690,10 @@ module ActiveRecord
|
||||||
|
|
||||||
def change_column_for_alter(table_name, column_name, type, options = {})
|
def change_column_for_alter(table_name, column_name, type, options = {})
|
||||||
sqls = [change_column_sql(table_name, column_name, type, options)]
|
sqls = [change_column_sql(table_name, column_name, type, options)]
|
||||||
procs = []
|
|
||||||
sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
|
sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
|
||||||
sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
||||||
procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
||||||
|
sqls
|
||||||
[sqls, procs]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -694,6 +717,14 @@ module ActiveRecord
|
||||||
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_timestamps_for_alter(table_name, options = {})
|
||||||
|
[add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_timestamps_for_alter(table_name, options = {})
|
||||||
|
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
|
||||||
|
end
|
||||||
|
|
||||||
def add_index_opclass(quoted_columns, **options)
|
def add_index_opclass(quoted_columns, **options)
|
||||||
opclasses = options_for_index_columns(options[:opclass])
|
opclasses = options_for_index_columns(options[:opclass])
|
||||||
quoted_columns.each do |name, column|
|
quoted_columns.each do |name, column|
|
||||||
|
|
|
@ -122,6 +122,10 @@ module ActiveRecord
|
||||||
include PostgreSQL::SchemaStatements
|
include PostgreSQL::SchemaStatements
|
||||||
include PostgreSQL::DatabaseStatements
|
include PostgreSQL::DatabaseStatements
|
||||||
|
|
||||||
|
def supports_bulk_alter?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def supports_index_sort_order?
|
def supports_index_sort_order?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -801,8 +801,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
||||||
t.integer :age
|
t.integer :age
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adding an index fires a query every time to check if an index already exists or not
|
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||||
assert_queries(3) do
|
expected_query_count = {
|
||||||
|
"Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
|
||||||
|
"PostgreSQLAdapter" => 2,
|
||||||
|
}.fetch(classname) {
|
||||||
|
raise "need an expected query count for #{classname}"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_queries(expected_query_count) do
|
||||||
with_bulk_change_table do |t|
|
with_bulk_change_table do |t|
|
||||||
t.index :username, unique: true, name: :awesome_username_index
|
t.index :username, unique: true, name: :awesome_username_index
|
||||||
t.index [:name, :age]
|
t.index [:name, :age]
|
||||||
|
@ -826,7 +833,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
||||||
|
|
||||||
assert index(:index_delete_me_on_name)
|
assert index(:index_delete_me_on_name)
|
||||||
|
|
||||||
assert_queries(3) do
|
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||||
|
expected_query_count = {
|
||||||
|
"Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
|
||||||
|
"PostgreSQLAdapter" => 2,
|
||||||
|
}.fetch(classname) {
|
||||||
|
raise "need an expected query count for #{classname}"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_queries(expected_query_count) do
|
||||||
with_bulk_change_table do |t|
|
with_bulk_change_table do |t|
|
||||||
t.remove_index :name
|
t.remove_index :name
|
||||||
t.index :name, name: :new_name_index, unique: true
|
t.index :name, name: :new_name_index, unique: true
|
||||||
|
@ -848,10 +863,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
|
||||||
assert ! column(:name).default
|
assert ! column(:name).default
|
||||||
assert_equal :date, column(:birthdate).type
|
assert_equal :date, column(:birthdate).type
|
||||||
|
|
||||||
# One query for columns (delete_me table)
|
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
|
||||||
# One query for primary key (delete_me table)
|
expected_query_count = {
|
||||||
# One query to do the bulk change
|
"Mysql2Adapter" => 3, # one query for columns, one query for primary key, one query to do the bulk change
|
||||||
assert_queries(3, ignore_none: true) do
|
"PostgreSQLAdapter" => 2, # one query for columns, one for bulk change
|
||||||
|
}.fetch(classname) {
|
||||||
|
raise "need an expected query count for #{classname}"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_queries(expected_query_count, ignore_none: true) do
|
||||||
with_bulk_change_table do |t|
|
with_bulk_change_table do |t|
|
||||||
t.change :name, :string, default: "NONAME"
|
t.change :name, :string, default: "NONAME"
|
||||||
t.change :birthdate, :datetime
|
t.change :birthdate, :datetime
|
||||||
|
|
|
@ -112,7 +112,7 @@ module ActiveRecord
|
||||||
# instead examining the SQL content.
|
# instead examining the SQL content.
|
||||||
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im]
|
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im]
|
||||||
mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
|
mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
|
||||||
postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
|
postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i, /^\s*SELECT\b.*::regtype::oid\b/im]
|
||||||
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
|
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
|
||||||
|
|
||||||
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
|
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
|
||||||
|
|
Loading…
Reference in a new issue