Skip to content

Commit

Permalink
Merge pull request #47425 from adrianna-chang-shopify/ac-fix-no-autoi…
Browse files Browse the repository at this point in the history
…ncrement-sqlite

Ensure `AUTOINCREMENT` declaration is preserved when altering SQLite tables
  • Loading branch information
eileencodes committed Feb 27, 2023
2 parents 65ae7d1 + ee330cb commit 08d75b4
Show file tree
Hide file tree
Showing 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
Expand Up @@ -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

Expand Down
Expand Up @@ -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"
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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
Expand 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|
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions railties/test/application/initializers/frameworks_test.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 08d75b4

Please sign in to comment.