-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Add foreign key in the same statement as adding a column for PostgreSQL #45305
base: main
Are you sure you want to change the base?
Conversation
if current_adapter?(:PostgreSQLAdapter) | ||
test "add_reference combines adding column and foreign key" do | ||
@connection.create_table :testings | ||
assert_queries(1) do |
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.
If I understand it well, it was one query before as well. Should we check more on query itself for proper format to be used?
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.
It was 2 queries before:
rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
Lines 976 to 978 in a466272
def add_reference(table_name, ref_name, **options) | |
ReferenceDefinition.new(ref_name, **options).add(table_name, self) | |
end |
rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
Lines 187 to 199 in a466272
def add(table_name, connection) | |
columns.each do |name, type, options| | |
connection.add_column(table_name, name, type, **options) | |
end | |
if index | |
connection.add_index(table_name, column_names, **index_options(table_name)) | |
end | |
if foreign_key | |
connection.add_foreign_key(table_name, foreign_table_name, **foreign_key_options) | |
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.
Ahh, ok. I got confused by your example code mentioning following.
ALTER TABLE "bars" ADD "foo_id" bigint, ADD CONSTRAINT "fk_rails_9e5a99443d" FOREIGN KEY ("foo_id") REFERENCES "foos" ("id");
@fatkodima looks good. I did quick check and this syntax is supported for all PostgreSQL versions supported by ActiveRecord (9.3+). 💪 Btw. I did some checks locally with your example script and I got |
Rerun again: got |
6ddd3e0
to
353c81c
Compare
Co-authored-by: Cody Cutrer <cody@instructure.com>
353c81c
to
1130627
Compare
@@ -22,6 +22,7 @@ def accept(o) | |||
|
|||
private | |||
def visit_AlterTable(o) | |||
set_current_table(o.name) |
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.
Would you let me know what is the purpose of setting table_name instance variable here?
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.
It is used to set the current table we create or alter and is needed here https://github.com/rails/rails/pull/45305/files#diff-5b1b920fc3bebbb1d4c144b302603e5003a1d9e5a493e3234bf764b9e26fbe30R181-R182 to be able to calculate the foreign key name based on the "from" and "to" table names in the foreign_key_options
method.
We can manually calculate the foreign key name and use something like
# We need to get foreign key/ primary key columns here somehow,
# which is also done for us in `foreign_key_options`.
name = @conn.foreign_key_name(...)
options = @conn.foreign_key_options(nil, to_table, options.merge(name: name))
ForeignKeyDefinition.new(nil, to_table, options) <=== note that we set nil as table name
but this is ugly and more work that needed. Wdyt?
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 for the update. I am not very familiar with design patterns, but I am not sure if it is good practice to set instance variables within a method that uses the visitor pattern with something like "set_something".
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.
That is also the way how compilers generate the code.
@byroot Can you suggest us here?
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 super well versed in this design, but I don't think it's a big deal?
The visitor pattern for what I know of it, does down a tree to collect information and generate a result, so it doesn't shock me to keep some state when visiting.
But I think @tenderlove is much more well versed than me in Arel.
@@ -42,6 +43,7 @@ def visit_AddColumnDefinition(o) | |||
end | |||
|
|||
def visit_TableDefinition(o) | |||
set_current_table(o.name) |
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 have a same question here.
When using
add_reference
, PostgreSQL adds a new column and a foreign key separately. For large tables, adding a foreign key leads to problems, because the dbms needs to validate all the rows which takes a lot of time while holding exclusive locks on both tables.There are 2 approaches to solve this:
Example:
23s
vs2ms
.I added support only for PostgreSQL, because for other dbms' this will not add any benefits.
Based on #41863, added @ccutrer as a co-author.