Merge pull request #47425 from adrianna-chang-shopify/ac-fix-no-autoincrement-sqlite

Ensure `AUTOINCREMENT` declaration is preserved when altering SQLite tables
This commit is contained in:
Eileen M. Uchitelle 2023-02-27 11:11:07 -05:00 committed by GitHub
commit 08d75b4140
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 8 deletions

@ -0,0 +1,41 @@
# frozen_string_literal: true
module ActiveRecord
module ConnectionAdapters
module SQLite3
class Column < ConnectionAdapters::Column # :nodoc:
def initialize(*, auto_increment: nil, **)
super
@auto_increment = auto_increment
end
def auto_increment?
@auto_increment
end
def init_with(coder)
@auto_increment = coder["auto_increment"]
super
end
def encode_with(coder)
coder["auto_increment"] = @auto_increment
super
end
def ==(other)
other.is_a?(Column) &&
super &&
auto_increment? == other.auto_increment?
end
alias :eql? :==
def hash
Column.hash ^
super.hash ^
auto_increment?.hash
end
end
end
end
end

@ -145,7 +145,8 @@ def new_column_from_field(table_name, field)
type_metadata,
field["notnull"].to_i == 0,
default_function,
collation: field["collation"]
collation: field["collation"],
auto_increment: field["auto_increment"],
)
end

@ -2,6 +2,7 @@
require "active_record/connection_adapters/abstract_adapter"
require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/sqlite3/column"
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
require "active_record/connection_adapters/sqlite3/quoting"
require "active_record/connection_adapters/sqlite3/database_statements"
@ -527,13 +528,21 @@ def copy_table(from, to, options = {})
default = type.deserialize(column.default)
end
column_type = column.bigint? ? :bigint : column.type
@definition.column(column_name, column_type,
limit: column.limit, default: default,
precision: column.precision, scale: column.scale,
null: column.null, collation: column.collation,
column_options = {
limit: column.limit,
precision: column.precision,
scale: column.scale,
null: column.null,
collation: column.collation,
primary_key: column_name == from_primary_key
)
}
unless column.auto_increment?
column_options[:default] = default
end
column_type = column.bigint? ? :bigint : column.type
@definition.column(column_name, column_type, **column_options)
end
yield @definition if block_given?
@ -603,10 +612,12 @@ def translate_exception(exception, message:, sql:, binds:)
end
end
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i.freeze
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
def table_structure_with_collation(table_name, basic_structure)
collation_hash = {}
auto_increments = {}
sql = <<~SQL
SELECT sql FROM
(SELECT * FROM sqlite_master UNION ALL
@ -628,6 +639,7 @@ def table_structure_with_collation(table_name, basic_structure)
# This regex will match the column name and collation type and will save
# the value in $1 and $2 respectively.
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
end
basic_structure.map do |column|
@ -637,6 +649,10 @@ def table_structure_with_collation(table_name, basic_structure)
column["collation"] = collation_hash[column_name]
end
if auto_increments.has_key?(column_name)
column["auto_increment"] = true
end
column
end
else

@ -561,6 +561,27 @@ def test_remove_column_preserves_index_options
Barcode.reset_column_information
end
def test_auto_increment_preserved_on_table_changes
connection = Barcode.connection
connection.create_table :barcodes, force: true do |t|
t.string :code
end
pk_column = connection.columns("barcodes").find { |col| col.name == "id" }
sql = connection.exec_query("SELECT sql FROM sqlite_master WHERE tbl_name='barcodes'").rows.first.first
assert(pk_column.auto_increment?)
assert(sql.match?("PRIMARY KEY AUTOINCREMENT"))
connection.change_column(:barcodes, :code, :integer)
pk_column = connection.columns("barcodes").find { |col| col.name == "id" }
sql = connection.exec_query("SELECT sql FROM sqlite_master WHERE tbl_name='barcodes'").rows.first.first
assert(pk_column.auto_increment?)
assert(sql.match?("PRIMARY KEY AUTOINCREMENT"))
end
def test_supports_extensions
assert_not @conn.supports_extensions?, "does not support extensions"
end

@ -262,6 +262,9 @@ def show
RUBY
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
# The existing schema cache dump will contain ActiveRecord::ConnectionAdapters::SQLite3::Column objects
require "active_record/connection_adapters/sqlite3/column"
require "#{app_path}/config/environment"
assert_nil ActiveRecord::Base.connection_pool.schema_cache
@ -294,6 +297,9 @@ def show
RUBY
switch_env("DATABASE_URL", "mysql2://127.0.0.1:1") do
# The existing schema cache dump will contain ActiveRecord::ConnectionAdapters::SQLite3::Column objects
require "active_record/connection_adapters/sqlite3/column"
require "#{app_path}/config/environment"
assert ActiveRecord::Base.connection_pool.schema_cache.data_sources("posts")