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
Show reason for referential integrity failure #44943
Conversation
@danini-the-panini thanks for the PR and the stack overflow link. It's good feedback on the feature. I think we could consider including the error message from the database in the error message that gets raised, rather than logging. This way it works for all databases and it's harder to miss the message. To do this Is this something you'd be interested in working on? If not I am happy to make a PR. |
@ghiculescu thank you for your feedback. I think I'm catching your drift here.
Let me play around in this PR and see if I can get it into a place we can align on. |
48a800e
to
ecd7b26
Compare
@ghiculescu I've changed it to bubble up the original error. Not sure what the best way would be to include the original hint to the developer? What do you think if we did the following in if ActiveRecord.verify_foreign_keys_for_fixtures
begin
conn.check_all_foreign_keys_valid
rescue ActiveRecord::StatementInvalid => error
raise "Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations\nCaused by\n#{error.message}"
end
end A more advanced approach would be to investigate the actual error from PG and give the developer the exact fixture files to look at. |
ecd7b26
to
1509500
Compare
I like that for Postgres. Will the same approach work for SQLite? |
Will have to look into the other adapters and see what's possible. I don't see why it wouldn't though. |
Would you address CI failures? |
1509500
to
f4a6647
Compare
I've managed to amend the SQLite adapter to raise a similar error. |
efa2ea4
to
5cb85e4
Compare
I fixed the errors pertaining to my code changes but it now appears to be failing for reasons completely unrelated. Is it maybe since I rebased off main? |
Could you update the changelog? |
19d05e5
to
09370e6
Compare
ba5d24e
to
49b5825
Compare
49b5825
to
85efff2
Compare
deprecate :all_foreign_keys_valid? | ||
|
||
# Override to check all foreign key constraints in a database. | ||
def check_all_foreign_keys_valid |
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.
The current implementation looks cleaner, however it depends on the all_foreign_keys_valid?
which is marked as duplicate.
Therefore it raises deprecation warnings and all_foreign_keys_valid? cannot be removed in the future versions of Rails.
Would you check_all_foreign_keys_valid
not depending on all_foreign_keys_valid?
?
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 don't see a way of doing this without breaking 3rd party adapters. An alternative would be to create a custom deprecator that we can "turn off" during the check_all_foreign_keys_valid
method?
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've removed the dependency. This will mean that fixture integrity checks in 3rd party adapters that implement all_foreign_keys_valid?
will no longer work until they override check_all_foreign_keys_valid
instead.
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 if I understand your concern yet, How about creating another private method _all_foreign_keys_valid?
?
def all_foreign_keys_valid?
# Just call the private `_all_foreign_keys_valid` method
_all_foreign_keys_valid?
end
deprecate :all_foreign_keys_valid?
def check_all_foreign_keys_valid
# Making use of the private `_all_foreign_keys_valid?` method
# so that it does not depend on the deprecated public `all_foreign_keys_valid?` method
end
private
def _all_foreign_keys_valid?
# implement the current `all_foreign_keys_valid?` method logic here
end
Then we can remove the public all_foreign_keys_valid?
method in future.
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.
Aha, good idea, thank you 🙏🏻
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.
Wait, that makes no difference since there isn't any real "logic" in the abstract adapter, and this doesn't solve the issue with 3rd party abstract adapters, since they will still be overriding all_foreign_keys_valid?
, which will no longer be used by the fixtures integrity check. Unless we just don't deprecate it until we find a way to remove it safely.
How do you deprecate a method for "overriding"? Unless we do some reflection to see if the current adapter has overridden all_foreign_keys_valid?
and not check_all_foreign_keys_valid
and raise a deprecation warning if that's the case. For example, something like this:
def all_foreign_keys_valid?
true
end
deprecate :all_foreign_keys_valid?
def check_all_foreign_keys_valid
return if self.class.method(:all_foreign_keys_valid?).owner == AbstractAdapter
raise unless all_foreign_keys_valid?
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.
I am also a 3rd party adapter maintainer and there is no way for every third-party adapter to call super
so we do not have to do something like return if self.class.method(:all_foreign_keys_valid?).owner == AbstractAdapter
.
Here is one example rsim/oracle-enhanced#1770 Also we can notify by adding mentions to some of 3rd party adapter maintainers if necessary.
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.
Okay, in that case the current implementation should be sufficient
e703dbb
to
ac80afe
Compare
ac80afe
to
96b7df7
Compare
Previously, if there were any referential integrity errors in your fixtures, the underlying Postgres and SQLite adapters would throw an error. However, the original error message is swallowed in `all_foreign_keys_valid?` and return false. This gave the developer very little information on where to start debugging. This replaces `all_foreign_keys_valid?` with `check_all_foreign_keys_valid` to raise more useful errors instead of just returning false
96b7df7
to
5a53634
Compare
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.
Maybe you don't need to deprecate all_foreign_keys_valid?
?
Looks like this PR is abandoned, so I'll try to bring some attention to it with this comment. So, the issue it tries to solve is still very much relevant. I was adding https://github.com/djezzzl/database_consistency this gem to a project I work on. It generated a hundred new migrations, and referential integrity checks stoped passing because our fixtures were inconsistent. I solved this with putting a debugger in where If you don't want to break existing adapters, which is understandable, I suggest adding logging there and moving on, that's a lot better than nothing. Another approach, not the cleanest one, would be not swallowing this error at all. The only place you use this method is the place where you raise when it returns false. This would break the "method ends with ? -> it returns a bool" intuition, but would improve user experience here. I know nobody asked by advice here, but can't rephrase it so it sounds nicer, sorry. Feel free to ignore it. Also, rails maintainers, thanks for the work you're doing for all those years. And @danini-the-panini thank you for the patch. |
rails#42674 added the ability to have Rails verify foreign keys when creating fixtures. Feedback from users since then is it would be handy to know *which* foreign keys are being violated. See rails#44943 and rails#47780 for attempts to fix this. This PR rolls up some of the ideas from those PRs into one that's hopefully mergable. - [x] `all_foreign_keys_valid?` is deprecated in favour of `check_all_foreign_keys_valid!`. - [x] Postgres and Sqlite adapters raise an error with detail about the foreign key error. - [x] Authors of other PRs added as co-authors here to get credit. - [x] Deprecations updated to work with new deprecation APIs. - [x] Tests updated. Here's what the error messages will now look like. Postgres: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: PG::ForeignKeyViolation: ERROR: insert or update on table "fk_pointing_to_non_existent_objects" violates foreign key constraint "fk_that_will_be_broken" DETAIL: Key (fk_object_to_point_to_id)=(980190962) is not present in table "fk_object_to_point_tos". CONTEXT: SQL statement "UPDATE pg_constraint SET convalidated=false WHERE conname = 'fk_that_will_be_broken' AND connamespace::regnamespace = 'public'::regnamespace; ALTER TABLE public.fk_pointing_to_non_existent_objects VALIDATE CONSTRAINT fk_that_will_be_broken;" PL/pgSQL function inline_code_block line 16 at EXECUTE ``` Sqlite: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: Foreign key violations found: fk_pointing_to_non_existent_objects ``` Closes rails#47780 Closes rails#44943 Co-Authored-By: s-mage <s-mage@users.noreply.github.com> Co-Authored-By: danini-the-panini <danini-the-panini@users.noreply.github.com>
rails#42674 added the ability to have Rails verify foreign keys when creating fixtures. Feedback from users since then is it would be handy to know *which* foreign keys are being violated. See rails#44943 and rails#47780 for attempts to fix this. This PR rolls up some of the ideas from those PRs into one that's hopefully mergable. - [x] `all_foreign_keys_valid?` is deprecated in favour of `check_all_foreign_keys_valid!`. - [x] Postgres and Sqlite adapters raise an error with detail about the foreign key error. - [x] Authors of other PRs added as co-authors here to get credit. - [x] Deprecations updated to work with new deprecation APIs. - [x] Tests updated. Here's what the error messages will now look like. Postgres: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: PG::ForeignKeyViolation: ERROR: insert or update on table "fk_pointing_to_non_existent_objects" violates foreign key constraint "fk_that_will_be_broken" DETAIL: Key (fk_object_to_point_to_id)=(980190962) is not present in table "fk_object_to_point_tos". CONTEXT: SQL statement "UPDATE pg_constraint SET convalidated=false WHERE conname = 'fk_that_will_be_broken' AND connamespace::regnamespace = 'public'::regnamespace; ALTER TABLE public.fk_pointing_to_non_existent_objects VALIDATE CONSTRAINT fk_that_will_be_broken;" PL/pgSQL function inline_code_block line 16 at EXECUTE ``` Sqlite: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: Foreign key violations found: fk_pointing_to_non_existent_objects ``` Closes rails#47780 Closes rails#44943 Co-Authored-By: s-mage <s-mage@users.noreply.github.com> Co-Authored-By: danini-the-panini <danini-the-panini@users.noreply.github.com>
rails#42674 added the ability to have Rails verify foreign keys when creating fixtures. Feedback from users since then is it would be handy to know *which* foreign keys are being violated. See rails#44943 and rails#47780 for attempts to fix this. This PR rolls up some of the ideas from those PRs into one that's hopefully mergable. - [x] `all_foreign_keys_valid?` is deprecated in favour of `check_all_foreign_keys_valid!`. - [x] Postgres and Sqlite adapters raise an error with detail about the foreign key error. - [x] Authors of other PRs added as co-authors here to get credit. - [x] Deprecations updated to work with new deprecation APIs. - [x] Tests updated. Here's what the error messages will now look like. Postgres: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: PG::ForeignKeyViolation: ERROR: insert or update on table "fk_pointing_to_non_existent_objects" violates foreign key constraint "fk_that_will_be_broken" DETAIL: Key (fk_object_to_point_to_id)=(980190962) is not present in table "fk_object_to_point_tos". CONTEXT: SQL statement "UPDATE pg_constraint SET convalidated=false WHERE conname = 'fk_that_will_be_broken' AND connamespace::regnamespace = 'public'::regnamespace; ALTER TABLE public.fk_pointing_to_non_existent_objects VALIDATE CONSTRAINT fk_that_will_be_broken;" PL/pgSQL function inline_code_block line 16 at EXECUTE ``` Sqlite: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: Foreign key violations found: fk_pointing_to_non_existent_objects ``` Closes rails#47780 Closes rails#44943 Co-Authored-By: s-mage <s-mage@users.noreply.github.com> Co-Authored-By: danini-the-panini <danini-the-panini@users.noreply.github.com>
rails#42674 added the ability to have Rails verify foreign keys when creating fixtures. Feedback from users since then is it would be handy to know *which* foreign keys are being violated. See rails#44943 and rails#47780 for attempts to fix this. This PR rolls up some of the ideas from those PRs into one that's hopefully mergable. - [x] `all_foreign_keys_valid?` is deprecated in favour of `check_all_foreign_keys_valid!`. - [x] Postgres and Sqlite adapters raise an error with detail about the foreign key error. - [x] Authors of other PRs added as co-authors here to get credit. - [x] Deprecations updated to work with new deprecation APIs. - [x] Tests updated. Here's what the error messages will now look like. Postgres: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: PG::ForeignKeyViolation: ERROR: insert or update on table "fk_pointing_to_non_existent_objects" violates foreign key constraint "fk_that_will_be_broken" DETAIL: Key (fk_object_to_point_to_id)=(980190962) is not present in table "fk_object_to_point_tos". CONTEXT: SQL statement "UPDATE pg_constraint SET convalidated=false WHERE conname = 'fk_that_will_be_broken' AND connamespace::regnamespace = 'public'::regnamespace; ALTER TABLE public.fk_pointing_to_non_existent_objects VALIDATE CONSTRAINT fk_that_will_be_broken;" PL/pgSQL function inline_code_block line 16 at EXECUTE ``` Sqlite: ``` Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database: Foreign key violations found: fk_pointing_to_non_existent_objects ``` Closes rails#47780 Closes rails#44943 Co-Authored-By: s-mage <s-mage@users.noreply.github.com> Co-Authored-By: danini-the-panini <danini-the-panini@users.noreply.github.com>
Summary
Currently, if there is a referential integrity error in your fixtures, it will throw an error. However, the original error message from postgres is swallowed in
all_foreign_keys_valid?
and returns false. This gives the developer very little information on where to start debugging.My proposed solution is to raise the original exception from the underlying adapter for postgres, and raise a similar error from the SQLite adapter.
Other Information
Stack overflow explaining the issue: https://stackoverflow.com/questions/71054865/tips-for-debugging-fixture-foreign-key-errors-in-rails