-
Notifications
You must be signed in to change notification settings - Fork 21.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change Default Primary Keys to BIGINT #26266
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module ActiveRecord | ||
module ConnectionAdapters | ||
module SQLite3 | ||
module ColumnDumper | ||
private | ||
|
||
def default_primary_key?(column) | ||
schema_type(column) == :integer | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,11 +104,21 @@ def index_name_for_remove(table_name, options = {}) | |
|
||
class V5_0 < V5_1 | ||
def create_table(table_name, options = {}) | ||
if ActiveRecord::Base.connection.adapter_name == "PostgreSQL" | ||
connection_name = self.connection.adapter_name | ||
if connection_name == "PostgreSQL" | ||
if options[:id] == :uuid && !options[:default] | ||
options[:default] = "uuid_generate_v4()" | ||
end | ||
end | ||
|
||
# Since 5.1 Postgres adapter uses bigserial type for primary | ||
# keys by default and MySQL uses bigint. This compat layer makes old migrations utilize | ||
# serial/int type instead -- the way it used to work before 5.1. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My worry here is just that enumerating the adapter types implies not-so-great things about the compatibility story for out-of-tree adapters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @matthewd @yahonda @metaskills Is there a decent set of defaults that will work well? In your previous comment, you suggested what I have in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's where I was thinking we could define defaults that must work well, and teach our own adapters to support them. I'm fine with forcing changes onto adapters* -- I just don't want them to have to reach outside their classes to patch this * Ultimately we'd like to get to a point where we don't do that so much, but right now, it's par for the course. So while big-picture unfortunate, it's not a negative against a particular approach here & now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand. If you think that's the best way to go, I'll make the change. Can you help me by pointing me to a good spot to place this logic? I want to get it right the first time... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really do think that this is fine. I'm in favor of opting other adapters into the new behavior by default. I'm not aware of any adapter that won't behave the same as the MySQL case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that this required code changes in every adapter makes me hesitate though. Ideally it would either "just work" for third party adapters or require opting in. We should avoid having the default behavior require code changes for them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sgrif Agreed -- that's why I was hoping there was a good default. it sounds like the if options[:id].blank?
if self.connection.class.const_defined?("Compatibility")
options.merge(self.connection.class::Compatibility::INTEGER_PRIMARY_KEY_OPTIONS)
else
options[:id] = :integer
options[:auto_increment] = true
end
end There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: #27272 made it possible to set the single set of defaults. Change pushed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Falling back to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @yahonda |
||
if options[:id].blank? | ||
options[:id] = :integer | ||
options[:auto_increment] = true | ||
end | ||
|
||
super | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
require "cases/helper" | ||
|
||
class MysqlLegacyMigrationTest < ActiveRecord::Mysql2TestCase | ||
self.use_transactional_tests = false | ||
|
||
class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0] | ||
def change | ||
create_table :legacy_integer_pk do |table| | ||
table.string :foo | ||
end | ||
|
||
create_table :override_pk, id: :bigint do |table| | ||
table.string :bar | ||
end | ||
end | ||
end | ||
|
||
def setup | ||
super | ||
@connection = ActiveRecord::Base.connection | ||
|
||
@migration_verbose_old = ActiveRecord::Migration.verbose | ||
ActiveRecord::Migration.verbose = false | ||
|
||
migrations = [GenerateTableWithoutBigint.new(nil, 1)] | ||
|
||
ActiveRecord::Migrator.new(:up, migrations).migrate | ||
end | ||
|
||
def teardown | ||
ActiveRecord::Migration.verbose = @migration_verbose_old | ||
@connection.drop_table("legacy_integer_pk") | ||
@connection.drop_table("override_pk") | ||
ActiveRecord::SchemaMigration.delete_all rescue nil | ||
super | ||
end | ||
|
||
def test_create_table_uses_integer_as_pkey_by_default | ||
col = column(:legacy_integer_pk, :id) | ||
assert_equal "int(11)", sql_type_for(col) | ||
assert col.auto_increment? | ||
end | ||
|
||
def test_create_tables_respects_pk_column_type_override | ||
col = column(:override_pk, :id) | ||
assert_equal "bigint(20)", sql_type_for(col) | ||
end | ||
|
||
private | ||
|
||
def column(table_name, column_name) | ||
ActiveRecord::Base.connection | ||
.columns(table_name.to_s) | ||
.detect { |c| c.name == column_name.to_s } | ||
end | ||
|
||
def sql_type_for(col) | ||
col && col.sql_type | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
require "cases/helper" | ||
|
||
class PostgresqlLegacyMigrationTest < ActiveRecord::PostgreSQLTestCase | ||
class GenerateTableWithoutBigserial < ActiveRecord::Migration[5.0] | ||
def change | ||
create_table :legacy_integer_pk do |table| | ||
table.string :foo | ||
end | ||
|
||
create_table :override_pk, id: :bigint do |table| | ||
table.string :bar | ||
end | ||
end | ||
end | ||
|
||
def setup | ||
super | ||
|
||
@migration_verbose_old = ActiveRecord::Migration.verbose | ||
ActiveRecord::Migration.verbose = false | ||
|
||
migrations = [GenerateTableWithoutBigserial.new(nil, 1)] | ||
ActiveRecord::Migrator.new(:up, migrations).migrate | ||
end | ||
|
||
def teardown | ||
ActiveRecord::Migration.verbose = @migration_verbose_old | ||
|
||
super | ||
end | ||
|
||
def test_create_table_uses_serial_as_pkey_by_default | ||
col = column(:legacy_integer_pk, :id) | ||
assert_equal "integer", sql_type_for(col) | ||
assert col.serial? | ||
end | ||
|
||
def test_create_tables_respects_pk_column_type_override | ||
col = column(:override_pk, :id) | ||
assert_equal "bigint", sql_type_for(col) | ||
end | ||
|
||
private | ||
|
||
def column(table_name, column_name) | ||
ActiveRecord::Base.connection. | ||
columns(table_name.to_s). | ||
detect { |c| c.name == column_name.to_s } | ||
end | ||
|
||
def sql_type_for(col) | ||
col && col.sql_type | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this module is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to remove code that is not directly related to this PR (postgres and mysql)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR adds
sqlite3/schema_dumper.rb
even though does not change sqlite3 behavior (only change postgres and mysql). Why?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kamipo Ah -- forgot that was added (this PR has been going on for awhile 😄 ). The parent class has it set as
bigint
, so we're locking SQLite atinteger
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see. Thanks. This (the parent class has it set as
bigint
) means that all third party adapters should choose whether implement default bigint pk (and implementCompatibility::V5_0
) or overridedef default_primary_key?(column) schema_type(column) == :integer; end
, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure other adapters would actually need to change here -- SQLite is special cased because it has no bigint type, but it's unique in that regard. However, as long as https://github.com/rails/rails/pull/26266/files#diff-2a8be25f82da6b3935cc6a41300a1b01R112 is specific to those two adapters, I do agree that we should invert this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think the most compatible choice would be to default to pushing everyone to bigint. SQLite just happens to spell that 'integer'.
We've decided everyone's database should use bigint PKs, and that's not something that individual adapters should be revisiting -- their only interest should be if they need to do something special to represent bigint (again, as SQLite does).
(As Sean noted, though, this means the migration compatibility thing needs to change.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matthewd @sgrif Trying to follow... what needs to change exactly? I'm reading conflicting opinions and want to get it right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either https://github.com/rails/rails/pull/26266/files#diff-2a8be25f82da6b3935cc6a41300a1b01R112 needs to be changed to apply to everything except SQLite, or the default implementation of
default_primary_key?
needs to be reverted to the original implementation on the abstract adapter, and overridden on PostgreSQL and MySQL2. As this is written, any out of tree adapters are going to get incorrect behavior.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @sgrif -- will do.