未验证 提交 6a778721 编写于 作者: M Matthew Draper 提交者: GitHub

Merge pull request #31230 from dinahshi/postgresql_extract_sql

Extract sql fragment generators from PostgreSQL adapter
......@@ -600,7 +600,7 @@ def remove_columns(table_name, *column_names)
# to provide these in a migration's +change+ method so it can be reverted.
# In that case, +type+ and +options+ will be used by #add_column.
def remove_column(table_name, column_name, type = nil, options = {})
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
end
# Changes the column's definition according to the new options.
......@@ -1365,6 +1365,20 @@ def can_remove_index_by_name?(options)
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
end
def add_column_for_alter(table_name, column_name, type, options = {})
td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
schema_creation.accept(AddColumnDefinition.new(cd))
end
def remove_column_for_alter(table_name, column_name, type = nil, options = {})
"DROP COLUMN #{quote_column_name(column_name)}"
end
def remove_columns_for_alter(table_name, *column_names)
column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
end
def insert_versions_sql(versions)
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
......
......@@ -299,7 +299,7 @@ def create_table(table_name, **options) #:nodoc:
def bulk_change_table(table_name, operations) #:nodoc:
sqls = operations.flat_map do |command, args|
table, arguments = args.shift, args
method = :"#{command}_sql"
method = :"#{command}_for_alter"
if respond_to?(method, true)
send(method, table, *arguments)
......@@ -372,11 +372,11 @@ def change_column_comment(table_name, column_name, comment) #:nodoc:
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
rename_column_indexes(table_name, column_name, new_column_name)
end
......@@ -671,13 +671,7 @@ def translate_exception(exception, message)
end
end
def add_column_sql(table_name, column_name, type, options = {})
td = create_table_definition(table_name)
cd = td.new_column_definition(column_name, type, options)
schema_creation.accept(AddColumnDefinition.new(cd))
end
def change_column_sql(table_name, column_name, type, options = {})
def change_column_for_alter(table_name, column_name, type, options = {})
column = column_for(table_name, column_name)
type ||= column.sql_type
......@@ -698,7 +692,7 @@ def change_column_sql(table_name, column_name, type, options = {})
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
def rename_column_sql(table_name, column_name, new_column_name)
def rename_column_for_alter(table_name, column_name, new_column_name)
column = column_for(table_name, column_name)
options = {
default: column.default,
......@@ -712,31 +706,23 @@ def rename_column_sql(table_name, column_name, new_column_name)
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
def remove_column_sql(table_name, column_name, type = nil, options = {})
"DROP #{quote_column_name(column_name)}"
end
def remove_columns_sql(table_name, *column_names)
column_names.map { |column_name| remove_column_sql(table_name, column_name) }
end
def add_index_sql(table_name, column_name, options = {})
def add_index_for_alter(table_name, column_name, options = {})
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
index_algorithm[0, 0] = ", " if index_algorithm.present?
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
end
def remove_index_sql(table_name, options = {})
def remove_index_for_alter(table_name, options = {})
index_name = index_name_for_remove(table_name, options)
"DROP INDEX #{quote_column_name(index_name)}"
end
def add_timestamps_sql(table_name, options = {})
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
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_sql(table_name, options = {})
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
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
# MySQL is too stupid to create a temporary table for use subquery, so we have
......
......@@ -399,51 +399,24 @@ def add_column(table_name, column_name, type, options = {}) #:nodoc:
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
quoted_table_name = quote_table_name(table_name)
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options)
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
end
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
cast_as_type = type_to_sql(options[:cast_as], options)
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
end
execute sql
change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
sqls, procs = change_column_for_alter(table_name, column_name, type, options)
execute "ALTER TABLE #{quoted_table_name} #{sqls.join(", ")}"
procs.each(&:call)
end
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
clear_cache!
column = column_for(table_name, column_name)
return unless column
default = extract_new_default_value(default_or_changes)
alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
if default.nil?
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
execute alter_column_query % "DROP DEFAULT"
else
execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
end
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
end
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
end
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
end
# Adds comment for given table column or drops it if +comment+ is a +nil+
......@@ -679,6 +652,53 @@ def extract_foreign_key_action(specifier)
end
end
def change_column_for_alter(table_name, column_name, type, options = {})
clear_cache!
quoted_column_name = quote_column_name(column_name)
sql_type = type_to_sql(type, options)
sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
end
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
cast_as_type = type_to_sql(options[:cast_as], options)
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
end
sqls = [sql]
procs = []
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)
procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
[sqls, procs]
end
# Changes the default value of a table column.
def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
clear_cache!
column = column_for(table_name, column_name)
return unless column
default = extract_new_default_value(default_or_changes)
alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
if default.nil?
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
alter_column_query % "DROP DEFAULT"
else
alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
end
end
def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
clear_cache!
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
end
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
......
......@@ -16,6 +16,15 @@ def self.find(version)
V5_2 = Current
class V5_1 < V5_2
if adapter_name == "PostgreSQL"
def change_column(table_name, column_name, type, options = {})
unless options[:null] || options[:default].nil?
column = connection.send(:column_for, table_name, column_name)
connection.execute("UPDATE #{connection.quote_table_name(table_name)} SET #{connection.quote_column_name(column_name)}=#{connection.quote_default_expression(options[:default], column)} WHERE #{connection.quote_column_name(column_name)} IS NULL") if column
end
connection.change_column(table_name, column_name, type, options)
end
end
end
class V5_0 < V5_1
......
......@@ -174,10 +174,10 @@ def test_logs_name_show_variable
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
def test_logs_name_rename_column_sql
def test_logs_name_rename_column_for_alter
@connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
@subscriber.logged.clear
@connection.send(:rename_column_sql, "bar_baz", "foo", "foo2")
@connection.send(:rename_column_for_alter, "bar_baz", "foo", "foo2")
assert_equal "SCHEMA", @subscriber.logged[0][1]
ensure
@connection.execute "DROP TABLE `bar_baz`"
......
......@@ -126,6 +126,23 @@ def test_legacy_migrations_raises_exception_when_inherited
end
assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
if current_adapter?(:PostgreSQLAdapter)
class Testing < ActiveRecord::Base
end
def test_legacy_change_column_with_null_executes_update
migration = Class.new(ActiveRecord::Migration[5.1]) {
def migrate(x)
change_column :testings, :foo, :string, null: false, default: "foobar"
end
}.new
t = Testing.create!
ActiveRecord::Migrator.new(:up, [migration]).migrate
assert_equal ["foobar"], Testing.all.map(&:foo)
end
end
end
end
end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册