Support enforced: option on foreign keys#2793
Merged
Merged
Conversation
c45491b to
d73fae2
Compare
Collaborator
Author
|
rails/rails#57377 has been opened to Rails. |
894c5df to
748a42a
Compare
Collaborator
Author
|
rails/rails#57377 has been merged. |
Mirrors rails/rails#57377 (commit 6cb88eae), which adds an `enforced:` option to `add_foreign_key` (and a `change_foreign_key` helper) for PostgreSQL 18.4+ NOT ENFORCED constraints. The Ruby option name follows SQL:2011's [NOT] ENFORCED constraint characteristic (carried into SQL:2016). Oracle does not implement the [NOT] ENFORCED keyword; instead, the adapter exposes Oracle's pre-existing 4-state constraint model via the independent `enforced:` and `validate:` Ruby options. enforced | validate | Oracle state ---------|----------|---------------------------------------- true | true | ENABLE VALIDATE true | false | ENABLE NOVALIDATE false | true | DISABLE VALIDATE (blocks DML, ORA-25128) false | false | DISABLE NOVALIDATE (~ PG NOT ENFORCED) Both Ruby-API defaults are `true` and the options are independent. Users wanting PostgreSQL-NOT-ENFORCED semantics on Oracle should pass both `enforced: false, validate: false`; passing only `enforced: false` yields DISABLE VALIDATE (intentional, but blocks DML). The matrix is also reproduced in source above visit_ForeignKeyDefinition. - supports_enforced_foreign_keys? returns true unconditionally - visit_ForeignKeyDefinition emits ` DISABLE` (before any ` VALIDATE` / ` NOVALIDATE`) and emits explicit ` VALIDATE` when DISABLE is set and validate is true, because Oracle's bare DISABLE defaults to NOVALIDATE - foreign_keys introspection selects c.status from all_constraints; c.status -> options[:enforced]=false, c.validated -> options[:validate] =false, reported independently - change_foreign_key issues ALTER TABLE ... MODIFY CONSTRAINT ... ENABLE/DISABLE via foreign_key_for! lookup; raises ArgumentError when :enforced is omitted (mirrors PG, prevents a no-op call from silently disabling the FK). Docstring records the accepted opts and the bare DISABLE/ENABLE side effect on Oracle's VALIDATED state. Schema dumping relies on the abstract SchemaDumper#foreign_keys, which since rails/rails#57377 emits `enforced:` based on ForeignKeyDefinition#enforced? (options.fetch(:enforced, true)); no adapter-level dumper override is needed. References: - pgsql-hackers (SQL:2011 4.17.2 quote; accessible primary reference since the ISO standard itself is paywalled): https://postgrespro.com/list/thread-id/1770540 - Oracle 8i SQL Reference Release 3 (8.1.7) constraint_clause - documents ENABLE/DISABLE and VALIDATE/NOVALIDATE on foreign-key constraints; the DML-blocking semantics of DISABLE VALIDATE (ORA-25128) are part of the same long-standing model: https://docs.oracle.com/cd/A87860_01/doc/server.817/a85397/state14a.htm Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bb446f2 to
6661ac7
Compare
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mirrors rails/rails#57377 (merged commit 6cb88eae), which adds an
enforced:option toadd_foreign_key(and achange_foreign_keyhelper) for PostgreSQL 18.4+NOT ENFORCEDconstraints. The Ruby option nameenforced:follows the[NOT] ENFORCEDconstraint characteristic introduced in SQL:2011 and carried forward into SQL:2016 (see References below); Oracle does not implement the[NOT] ENFORCEDkeyword. Instead, oracle-enhanced exposes Oracle's pre-existing 4-state constraint model via the independentenforced:andvalidate:Ruby options.The 4-state matrix
enforced:validate:true(default)true(default)ENABLE VALIDATEtrue(default)falseENABLE NOVALIDATEfalsetrue(default)DISABLE VALIDATEfalsefalseDISABLE NOVALIDATENOT ENFORCED)The Ruby-API defaults are kept independent (
validate:defaults totrueregardless ofenforced:) so all 4 states are reachable from idiomatic Rails code. The framework does not "protect" the user fromDISABLE VALIDATEbecause Oracle itself allows it — the matrix above is reproduced as a comment abovevisit_ForeignKeyDefinitionso this is discoverable in source.Changes
supports_enforced_foreign_keys?returnstrueunconditionally — Oracle'sDISABLE/ENABLEconstraint state has been available across every Oracle release this gem supports.visit_ForeignKeyDefinitionappendsDISABLEwhenenforced: false. Whenvalidate:istrue(the Rails default) andenforced: false,VALIDATEis also emitted explicitly because Oracle's bareDISABLEclause defaults toNOVALIDATE— without the explicit keyword we couldn't reachDISABLE VALIDATE. The full 4-state matrix is documented in a comment above the method.foreign_keysintrospection selectsc.statusfromall_constraintsand maps'DISABLED'tooptions[:enforced] = false.c.validatedis mapped independently tooptions[:validate] = false— the two flags are not coupled. The dumped output preserves both, so every Oracle state round-trips.change_foreign_keyissuesALTER TABLE ... MODIFY CONSTRAINT ... ENABLE|DISABLEvia the existingforeign_key_for!lookup helper.MODIFY ... DISABLEresets the validated state toNOT VALIDATED, so disabling an enforced FK reports bothenforced: falseandvalidate: false.Schema dumping relies on the abstract
ActiveRecord::ConnectionAdapters::SchemaDumper#foreign_keys, which since rails/rails#57377 emitsenforced: falsewheneverforeign_key.enforced?is false (whereenforced?isoptions.fetch(:enforced, true)onForeignKeyDefinition). No adapter-level dumper override is needed.Tests
schema_statements_spec.rbexercising all 4 states + bothchange_foreign_keydirections. ORA-25128 (DML blocked under DISABLE VALIDATE) is asserted, not avoided.schema_dumper_spec.rbcoveringenforced: falseemission and full dump-and-load round-trips for bothDISABLE VALIDATEandDISABLE NOVALIDATE.269 examples pass against FREEPDB1 (Oracle 23ai).
References
[NOT] ENFORCED(the Rails option name)enforced:option for foreign keys on PostgreSQL 18.4+ rails/rails#57377 — merged commit rails/rails@6cb88ea<table constraint definition>—<constraint characteristics>syntactic slot<referential constraint definition>— accepts<constraint characteristics>(this is the FK path)<check constraint definition>— accepts<constraint characteristics><constraint enforcement> ::= ENFORCED | NOT ENFORCEDsrc/backend/catalog/sql_features.txtreferences F815; the entry was updated for FK support in PostgreSQL commitb663b9436e.ENABLE/DISABLE/VALIDATE/NOVALIDATE(the Oracle-side translation)constraint_clause: https://docs.oracle.com/cd/A87860_01/doc/server.817/a85397/state14a.htm — documents theconstraint_stateclause (ENABLE/DISABLE,VALIDATE/NOVALIDATE) as applicable to foreign-key (referential) constraints. The four-state model and its semantic definitions ("ENABLE VALIDATE … guarantees that all data is and will continue to be valid"; "DISABLE NOVALIDATE signifies that Oracle makes no effort to maintain the constraint") are already present in 8i and have been carried forward to current releases unchanged. The DML-blocking behavior ofDISABLE VALIDATE(ORA-25128 — "No insert/update/delete on table with constraint (…) disabled and validated") is part of the same long-standing semantics.