From a457b121a0eda2bc7438515fac0fcb49a3cd1c8a Mon Sep 17 00:00:00 2001 From: fatkodima Date: Tue, 9 Apr 2024 20:22:55 +0300 Subject: [PATCH] Fix copying virtual columns when altering a table in sqlite3 --- .../connection_adapters/sqlite3_adapter.rb | 26 ++++++++++++------- .../cases/adapters/sqlite3/copy_table_test.rb | 14 ++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index d55072aa06c77..bb689eaf83309 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -578,12 +578,6 @@ def copy_table(from, to, options = {}) options[:rename][column.name.to_sym] || column.name) : column.name - if column.has_default? - type = lookup_cast_type_from_column(column) - default = type.deserialize(column.default) - default = -> { column.default_function } if default.nil? - end - column_options = { limit: column.limit, precision: column.precision, @@ -593,19 +587,31 @@ def copy_table(from, to, options = {}) primary_key: column_name == from_primary_key } - unless column.auto_increment? - column_options[:default] = default + if column.virtual? + column_options[:as] = column.default_function + column_options[:stored] = column.virtual_stored? + column_options[:type] = column.type + elsif column.has_default? + type = lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + default = -> { column.default_function } if default.nil? + + unless column.auto_increment? + column_options[:default] = default + end end - column_type = column.bigint? ? :bigint : column.type + column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type) @definition.column(column_name, column_type, **column_options) end yield @definition if block_given? end copy_table_indexes(from, to, options[:rename] || {}) + + columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name) copy_table_contents(from, to, - @definition.columns.map(&:name), + columns_to_copy, options[:rename] || {}) end diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index f41fe09bbcd80..c55b4d3e66e3a 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -85,6 +85,20 @@ def test_copy_table_with_binary_column test_copy_table "binaries", "binaries2" end + def test_copy_table_with_virtual_column + @connection.create_table :virtual_columns, force: true do |t| + t.string :name + t.virtual :upper_name, type: :string, as: "UPPER(name)", stored: true + end + + test_copy_table("virtual_columns", "virtual_columns2") do + column = @connection.columns("virtual_columns2").find { |col| col.name == "upper_name" } + assert_predicate column, :virtual_stored? + assert_equal :string, column.type + assert_equal "UPPER(name)", column.default_function + end + end + private def copy_table(from, to, options = {}) @connection.send(:copy_table, from, to, { temporary: true }.merge(options))