Skip to content

WIP: Support constraint enable clause on CREATE statements.#1147

Draft
c2main wants to merge 6 commits intoIvorySQL:masterfrom
Data-Bene:support-constraint-enable_clause
Draft

WIP: Support constraint enable clause on CREATE statements.#1147
c2main wants to merge 6 commits intoIvorySQL:masterfrom
Data-Bene:support-constraint-enable_clause

Conversation

@c2main
Copy link
Copy Markdown
Contributor

@c2main c2main commented Jan 6, 2026

Add support for { ENABLE | DISABLE } [ VALIDATE | NOVALIDATE ] on CONSTRAINT.

This merge request supports CREATE and ALTER statements at the moment, though I have not added test to cover transitions like "notvalid->valid".

It maps with existing constraints clause in PostgreSQL. Clauses not supported by PostgreSQL return an error with appropriate HINT message.

TODO:

  • test transitions
  • test duplicates/opposite like ENFORCED DISABLE

Example of syntax:

-- NOT ENFORCED
CREATE TABLE fk_p6 (id NUMBER PRIMARY KEY, name VARCHAR2(100)
, fk_r_id NUMBER REFERENCES fk_r(id) DISABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p6_fk_r_id_fkey';
      conname       | contype | conenforced | convalidated 
--------------------+---------+-------------+--------------
 fk_p6_fk_r_id_fkey | f       | f           | f
(1 row)

Example not supported:

-- Not supported
CREATE TABLE cpk_disable (id NUMBER, name VARCHAR2(100),
    CONSTRAINT cpk_pk_disable PRIMARY KEY (id) DISABLE);
ERROR:  PRIMARY KEY DISABLE is not supported.
LINE 2:     CONSTRAINT cpk_pk_disable PRIMARY KEY (id) DISABLE);
                                                       ^
HINT:  Update your CONSTRAINT definition.

Summary by CodeRabbit

  • New Features

    • Support for Oracle-style constraint attributes: ENABLE, DISABLE, VALIDATE, NOVALIDATE; NOVALIDATE recognized as a keyword.
  • Behavior

    • ENABLE/DISABLE now affect constraint enforcement and validation state; improved error reporting for unsupported or conflicting combinations.
  • Tests

    • Extensive regression tests added to cover ENABLE/DISABLE and VALIDATE/NOVALIDATE permutations across constraint types.
  • Public API

    • Constraint attribute enum expanded to include explicit ENABLE/DISABLE values.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Adds Oracle-style constraint-attribute parsing and handling: new lexer keyword NOVALIDATE, grammar nonterminal OraConstraintAttributeElem, CAS_* attribute bitflags, parser transforms to map ENABLE/DISABLE/VALIDATE/NOVALIDATE into enforcement/validation flags (with error checks), enum additions, and extensive regression tests.

Changes

Cohort / File(s) Summary
Oracle grammar
src/backend/oracle_parser/ora_gram.y
Adds OraConstraintAttributeElem nonterminal; extends %type; introduces CAS token bitflags (CAS_ENABLE, CAS_DISABLE, CAS_VALIDATE, CAS_NOVALIDATE); integrates into ConstraintAttributeSpec/ConstraintAttributeElem; expands processCASbits with mapping and error branches.
Lexer keyword list
src/include/oracle_parser/ora_kwlist.h
Adds PG_KEYWORD("novalidate", NOVALIDATE, UNRESERVED_KEYWORD, BARE_LABEL) to recognize NOVALIDATE.
Parser transforms & node types
src/backend/parser/parse_utilcmd.c, src/backend/commands/typecmds.c, src/include/nodes/parsenodes.h
Adds CONSTR_ATTR_ENABLE / CONSTR_ATTR_DISABLE to ConstrType; transformConstraintAttrs and related flows now handle ENABLE/DISABLE (map to ENFORCED/NOT_ENFORCED, set skip_validation/initially_valid, emit errors for invalid combos); typecmds.c routes new attrs into existing error handling.
Regression tests (SQL + expected)
src/oracle_test/regress/sql/constraints.sql, src/oracle_test/regress/expected/constraints.out, src/oracle_test/regress/sql/foreign_key.sql, src/oracle_test/regress/expected/foreign_key.out
Large new test suites adding schemas (ivo_regress_enable, fkpart14) exercising ENABLE/DISABLE/VALIDATE/NOVALIDATE across PK/UNIQUE/CHECK/NOT NULL/FOREIGN KEY and partition cases; includes expected success and error outputs.
Minor formatting
src/backend/commands/typecmds.c
Whitespace/layout tweaks and added switch cases to handle new enum values (route to existing error paths).

Sequence Diagram(s)

sequenceDiagram
  participant Lexer as Lexer (scanner)
  participant Parser as Parser (ora_gram)
  participant Transform as Transform (parse_utilcmd.c)
  participant Catalog as Catalog (constraint builder)

  Lexer->>Parser: emit tokens (ENABLE / DISABLE / VALIDATE / NOVALIDATE)
  Parser->>Parser: parse OraConstraintAttributeElem -> produce CAS_* bits / CONSTR_ATTR_*
  Parser->>Transform: pass ConstraintAttributeSpec with attribute codes
  Transform->>Transform: processCASbits & transformConstraintAttrs -> map to ENFORCED/NOT_ENFORCED, set skip_validation/initially_valid, or raise errors
  Transform->>Catalog: populate constraint descriptor (enforced/validated flags)
  Transform-->>Parser: report semantic errors (if any)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • jiaoshuntian
  • OreoYang
  • NotHimmel

Poem

🐇 I nibble tokens by moonlit slate,
ENABLE hops in, NOVALIDATE waits,
DISABLE stretches, VALIDATE sings,
Grammar burrows sprout new things,
Hooray — constraints and parsing plates!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'WIP: Support constraint enable clause on CREATE statements' accurately summarizes the main change in this pull request, which adds support for ENABLE/DISABLE constraint attributes on CREATE statements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/oracle_parser/ora_gram.y (1)

22157-22302: CAS_ENABLE handling in processCASbits wipes other constraint attributes (e.g., DEFERRABLE)

In processCASbits, for PRIMARY KEY / UNIQUE / NOT NULL you do:

if (cas_bits & CAS_ENABLE)
{
    cas_bits = 0;
    ereport(LOG, ...);
}

If a user writes something like:

PRIMARY KEY DEFERRABLE ENABLE
UNIQUE DEFERRABLE ENABLE
NOT NULL DEFERRABLE ENABLE   -- via table constraint form

the CAS mask will contain both CAS_DEFERRABLE and CAS_ENABLE. The above block zeroes all bits, silently discarding CAS_DEFERRABLE (and any INITIALLY_* flags). The constraint ends up non‑deferrable, which is a behavioral bug rather than merely treating ENABLE as noise.

You probably want to strip just the CAS_ENABLE bit while preserving others:

if (cas_bits & CAS_ENABLE)
{
    cas_bits &= ~CAS_ENABLE;
    ereport(LOG,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             errmsg("%s ENABLE is the default.", constrType),
             errhint("Update your %s CONSTRAINT definition.", constrType),
             parser_errposition(location)));
}

Same consideration applies if more masking like this is added later; keep the non‑Oracle flags intact.

Also applies to: 141-155

🧹 Nitpick comments (5)
src/oracle_test/regress/sql/constraints.sql (1)

1044-1366: Comprehensive test coverage for constraint ENABLE/DISABLE syntax.

The test suite thoroughly exercises all constraint types (PRIMARY KEY, UNIQUE, CHECK, NOT NULL) with all ENABLE/DISABLE and VALIDATE/NOVALIDATE combinations. The pattern of creating tables and querying pg_constraint to verify enforcement and validation flags is appropriate for regression testing.

Minor suggestion for clarity:

The comments "Not supported" and "Not supported - XXX NOT VALID supported on ALTER TABLE" could be more explicit about expected behavior. Consider clarifying whether these cases should:

  • Produce an error with a HINT message
  • Silently treat NOVALIDATE as unsupported
  • Create the constraint with specific flag values

This would help future maintainers understand the intended behavior.

src/oracle_test/regress/expected/foreign_key.out (1)

3408-3625: fkpart14 FK ENABLE/DISABLE tests look correct and complete

The new fkpart14 block exercises foreign key ENABLE/DISABLE with VALIDATE/NOVALIDATE across inline, column, and named FKs, and the expected pg_constraint flags and error/HINT combinations all align with the documented mapping:

  • ENABLE / ENABLE VALIDATEconenforced = t, convalidated = t.
  • DISABLE / DISABLE NOVALIDATEconenforced = f, convalidated = f.
  • ENABLE NOVALIDATE and DISABLE VALIDATE → explicit “FOREIGN KEY … is not supported” errors with a HINT and no leftover constraints.

If you want even stronger coverage, you could optionally add a tiny DML snippet to confirm that a DISABLE [NOVALIDATE] FK indeed allows inserting non-matching rows and that ENABLE still enforces, but that’s already well-covered earlier in this file and not strictly necessary here.

src/oracle_test/regress/expected/constraints.out (1)

1692-2311: CREATE TABLE ENABLE/DISABLE constraint tests are thorough and consistent

The ivo_regress_enable block does a good job of:

  • Exercising ENABLE/DISABLE with VALIDATE/NOVALIDATE across PRIMARY KEY, UNIQUE, CHECK, and NOT NULL, in both column and table-constraint forms.
  • Verifying that supported forms map to the intended conenforced/convalidated combinations and that unsupported forms yield clear, type-specific “<TYPE> <STATE> is not supported.” errors with a HINT and no residual constraints in pg_constraint.
  • Including an explicit syntax error case (id NUMBER ENABLE) plus cleanup via DROP SCHEMA … CASCADE and RESET search_path;.

One small optional polish: for cases like PRIMARY KEY DISABLE NOVALIDATE and NOT NULL DISABLE NOVALIDATE, you might consider mentioning DISABLE NOVALIDATE explicitly in the error text (rather than just DISABLE) to more directly reflect the user’s input, but the current behavior is functionally fine.

src/backend/oracle_parser/ora_gram.y (2)

6644-6692: ENABLE/DISABLE grammar now applies to all ConstraintAttributeSpec users (ALTER, DOMAIN, TRIGGER)

By:

  • Extending %type to include OraConstraintAttributeElem (line 615),
  • Adding OraConstraintAttributeElem into ConstraintAttributeElem and as its own alternative in ConstraintAttributeSpec (lines 6644–6683),
  • Introducing the OraConstraintAttributeElem nonterminal and NOVALIDATE keyword (lines 6685–6692, 781–782),

the { ENABLE | DISABLE } [ VALIDATE | NOVALIDATE ] syntax is now accepted anywhere ConstraintAttributeSpec is used, not just in CREATE-table/column constraints. That includes:

  • ALTER TABLE ... ALTER CONSTRAINT ... ConstraintAttributeSpec
  • CREATE DOMAIN / ALTER DOMAIN constraints
  • CREATE CONSTRAINT TRIGGER ... ConstraintAttributeSpec

This is broader than the PR description (“currently applies only to CREATE statements; ALTER and DOMAIN are not supported”), and some of these combinations hit generic processCASbits errors like “%s constraints cannot be marked ENFORCED/NOT ENFORCED”, which may be surprising to users of the new syntax.

If the intent is truly “CREATE only”, consider:

  • Using a separate nonterminal (e.g. OraConstraintAttributeSpec) wired only into the CREATE‑time constraint productions, or
  • Guarding unsupported call sites early (e.g. reject any CAS_ENABLE/CAS_DISABLE bits when constrType is "TRIGGER" or invoked from ALTER/DOMAIN paths) with a clear Oracle‑style error/HINT.

Otherwise, please update the PR docs/tests to explicitly call out that ALTER/DOMAIN also accept the syntax and verify the behavior you want at those sites.

Also applies to: 615-615, 781-782


4278-4482: Column-level ENABLE/DISABLE [NO]VALIDATE behavior looks consistent, but a few edge cases need decisions

The new OraConstraintAttributeElem usages in ColConstraintElem:

  • NOT NULL_P OraConstraintAttributeElem
  • UNIQUE ... OraConstraintAttributeElem
  • PRIMARY KEY ... OraConstraintAttributeElem
  • CHECK (...) opt_no_inherit OraConstraintAttributeElem
  • REFERENCES ... key_actions OraConstraintAttributeElem

plus the updated processCASbits give:

  • REFERENCES ... DISABLE NOVALIDATEis_enforced = false, skip_validation = true, initially_valid = false (as desired for “not enforced, not validated”).
  • CHECK ... DISABLE NOVALIDATE → same pattern with is_enforced = false and initially_valid = false.
  • PRIMARY KEY|UNIQUE|NOT NULL ... DISABLE[*] → hard error (“... DISABLE is not supported”) as per the PR text.
  • PRIMARY KEY|UNIQUE|NOT NULL ... ENABLE[*] → treated as redundant, logged, and ignored.

Two things to lock down:

  1. Plain DISABLE vs DISABLE NOVALIDATE for CHECK/FK
    Right now both map to CAS_DISABLE (i.e., treated as “NOT ENFORCED and NOT VALID”). If you intend to mirror Oracle’s distinction between DISABLE VALIDATE and DISABLE NOVALIDATE, you might at least want to:

    • Continue erroring on DISABLE VALIDATE (as you do now), and
    • Decide whether bare DISABLE should be an alias for DISABLE NOVALIDATE (current behavior) or be rejected as ambiguous.
  2. Column-level NOT NULL with ENABLE syntax
    NOT NULL ENABLE is accepted but only logged at LOG level and treated identically to bare NOT NULL. If you want users to see that ENABLE is redundant/Oracle‑only, consider WARNING/NOTICE instead of LOG, or just silently ignore it and document as accepted Oracle noise.

These are more semantics/UX than correctness issues, but clarifying them now will prevent surprising behavior once people start relying on the Oracle compatibility layer.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e665bff and dd6e09e.

📒 Files selected for processing (6)
  • src/backend/oracle_parser/ora_gram.y
  • src/include/oracle_parser/ora_kwlist.h
  • src/oracle_test/regress/expected/constraints.out
  • src/oracle_test/regress/expected/foreign_key.out
  • src/oracle_test/regress/sql/constraints.sql
  • src/oracle_test/regress/sql/foreign_key.sql
🧰 Additional context used
📓 Path-based instructions (2)
**/sql/*.sql

⚙️ CodeRabbit configuration file

**/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.

Files:

  • src/oracle_test/regress/sql/constraints.sql
  • src/oracle_test/regress/sql/foreign_key.sql
**/expected/*.out

⚙️ CodeRabbit configuration file

**/expected/*.out: These are expected test outputs from pg_regress. Review for:

  • Correctness of query results
  • Proper Oracle compatibility behavior
  • Edge case coverage
  • Consistency with corresponding .sql test files

Files:

  • src/oracle_test/regress/expected/constraints.out
  • src/oracle_test/regress/expected/foreign_key.out
🔇 Additional comments (2)
src/include/oracle_parser/ora_kwlist.h (1)

357-357: LGTM! Keyword addition is correct.

The NOVALIDATE keyword is properly added in alphabetical order with appropriate categorization (UNRESERVED_KEYWORD, BARE_LABEL), consistent with similar constraint-related keywords.

src/oracle_test/regress/sql/foreign_key.sql (1)

2390-2516: LGTM! Comprehensive foreign key constraint testing.

The test suite provides thorough coverage of foreign key constraint syntax with ENABLE/DISABLE and VALIDATE/NOVALIDATE attributes across three different declaration forms:

  1. Inline REFERENCES
  2. FOREIGN KEY clause
  3. CONSTRAINT FOREIGN KEY clause

The systematic testing of all six combinations (ENABLE, ENABLE VALIDATE, ENABLE NOVALIDATE, DISABLE, DISABLE VALIDATE, DISABLE NOVALIDATE) for each form ensures consistent behavior. The verification pattern using pg_constraint queries is appropriate for validating the conenforced and convalidated flags.

As per coding guidelines, test SQL files should ensure comprehensive coverage of features—this test block achieves that goal for foreign key constraints.

Comment thread src/oracle_test/regress/sql/constraints.sql
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/include/nodes/parsenodes.h (1)

332-339: OraParamRef node support infrastructure is missing critical functions

Adding ParseLoc location and char *name to OraParamRef requires implementing support in all node infrastructure functions (copy, equal, read, write, and query-jumbling). Currently, there are no references to OraParamRef in any of these support files (copyfuncs.c, readfuncs.c, outfuncs.c, equalfuncs.c, queryjumblefuncs.c), which will cause parse trees containing Oracle parameters to fail serialization, comparison, and cloning operations.

Ensure the code generator (gen_node_support.pl) properly processes OraParamRef and generates the necessary support functions, or add them manually to all node infrastructure files.

src/backend/oracle_parser/ora_gram.y (1)

2920-2942: ALTER CONSTRAINT with ENABLE/DISABLE doesn’t mark alterEnforceability

In:

if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED))
    c->alterEnforceability = true;
...
processCASbits($4, @4, "FOREIGN KEY",
               &c->deferrable,
               &c->initdeferred,
               &c->is_enforced,
               NULL,
               &c->noinherit,
               yyscanner);

$4 can now contain CAS_ENABLE / CAS_DISABLE (from OraConstraintAttributeElem), which are only converted to CAS_ENFORCED / CAS_NOT_ENFORCED inside processCASbits(). As a result:

  • ALTER TABLE .. ALTER CONSTRAINT .. ENABLE [VALIDATE|NOVALIDATE]
  • ALTER TABLE .. ALTER CONSTRAINT .. DISABLE [VALIDATE|NOVALIDATE]

will populate c->is_enforced via processCASbits(), but c->alterEnforceability remains false, so downstream code that keys off alterEnforceability may ignore the enforcement change.

You likely need to treat CAS_ENABLE/CAS_DISABLE as enforcement-changing here, e.g.:

- if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED))
+ if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED | CAS_ENABLE | CAS_DISABLE))
     c->alterEnforceability = true;

Without this, the new Oracle-style ENABLE/DISABLE syntax for ALTER CONSTRAINT is parsed but may be a no-op at execution time.

🤖 Fix all issues with AI agents
In @src/backend/parser/parse_utilcmd.c:
- Around line 4304-4310: The error path that checks lastprimarycon and raises
ERROR for DISABLE on unsupported constraint types (the ereport(...) call
referencing lastprimarycon and con->location) is missing the helpful errhint;
modify that ereport to include an errhint() suggesting the user update the
constraint definition to remove the DISABLE clause (for example: "Update the
constraint definition without the DISABLE clause, e.g. ALTER TABLE ... ADD
CONSTRAINT ... (omit DISABLE)"). Ensure the errhint text matches the style used
in other DISABLE error paths.
- Around line 4241-4274: The ENABLE branch handling CONSTR_ATTR_ENABLE contains
an unreachable inner `if (con->skip_validation)` block because earlier it errors
when `con->skip_validation` is true; remove the dead block (the inner `if
(con->skip_validation)` that sets `lastprimarycon->skip_validation` and
`lastprimarycon->initially_valid`) from the CONSTR_ATTR_ENABLE case in
parse_utilcmd so the code no longer contains unreachable statements, or
alternatively, if ENABLE NOVALIDATE must be supported, restructure the earlier
guard that raises the error on `con->skip_validation` to allow controlled
handling here and adjust `con->contype`/`saw_enforced` logic accordingly.
🧹 Nitpick comments (9)
src/include/nodes/parsenodes.h (3)

1765-1776: MergeWhenClause update_delete linkage

The update_delete self-pointer on MergeWhenClause is a reasonable way to represent Oracle-style UPDATE ... DELETE combinations. Just ensure this new field is included in copy/equal/read/write support for MergeWhenClause so rules and stored queries round-trip correctly.


3250-3260: CreateSeqStmt seq_type semantics should be documented

The new char seq_type flag (“is oracle compatible type or original type”) is fine structurally, but it would benefit from a clearer comment describing the allowed values and their meaning (e.g., which char indicates Oracle-compatible behavior vs native). That will avoid future ambiguity when interpreting this field.


3611-3655: New DO / InlineCodeBlock / CALL fields: check lifecycle and node support

The additions:

  • DoStmt.paramsmode / DoStmt.paramslen
  • InlineCodeBlock.params
  • CallStmt.hostvariable and CallStmt.callinto

all look consistent with extending PL/SQL-style call/DO support. A few points to verify:

  • InlineCodeBlock and CallStmt are used outside parse trees at execution time; ensure ownership/lifetime of ParamListInfo params and OraParamRef *hostvariable is clear so there are no dangling pointers.
  • Confirm all three structs are updated in copy/equal/read/write helpers as appropriate (for DoStmt and CallStmt, which are parse nodes; InlineCodeBlock is tagged nodetag_only but still flows through some infrastructure).
src/backend/commands/typecmds.c (1)

1032-1040: Domain constraint switch correctly rejects ENABLE/DISABLE attributes

Extending the DefineDomain constraint-type switch to include CONSTR_ATTR_ENABLE and CONSTR_ATTR_DISABLE alongside CONSTR_ATTR_ENFORCED / CONSTR_ATTR_NOT_ENFORCED ensures that Oracle-style ENABLE/DISABLE attributes on domains are rejected consistently, which aligns with the stated “not supported for domains” behavior.

If you want to be extra explicit for Oracle users, you might consider slightly broadening the error text (e.g., mention ENABLE/DISABLE in the message or HINT), but the current wording is functionally correct.

src/oracle_test/regress/sql/foreign_key.sql (1)

2390-2558: Consider adding DML operations to verify actual constraint behavior.

The fkpart14 test block comprehensively tests the syntax and metadata storage for FK constraints with ENABLE/DISABLE/VALIDATE/NOVALIDATE clauses. However, it only queries pg_constraint to verify the flags—it doesn't verify that the constraints actually enforce (or don't enforce) the expected behavior through INSERT/UPDATE/DELETE operations.

💡 Suggested enhancement

Consider adding DML verification for at least a few representative cases. For example:

-- After creating ENABLE constraint
INSERT INTO fk_r VALUES (1, 'test');
INSERT INTO fk_p1 VALUES (1, 'valid');    -- should succeed
INSERT INTO fk_p1 VALUES (999, 'invalid'); -- should fail

-- After creating DISABLE constraint  
INSERT INTO fk_p4 VALUES (999, 'invalid'); -- should succeed (not enforced)

-- Clean up
DELETE FROM fk_p1;
DELETE FROM fk_p4;
DELETE FROM fk_r;

This would provide functional validation in addition to the metadata checks.

As per coding guidelines, these are SQL test files that should ensure comprehensive coverage of features.

src/backend/parser/parse_utilcmd.c (1)

4311-4316: Inconsistent error formatting for duplicate clause detection.

The error for multiple ENABLE/DISABLE clauses includes an errhint in the DISABLE case (line 4315-4316) but not in the ENABLE case (line 4264). For consistency, either both should have the hint or neither should.

🔧 Proposed fix: Remove unnecessary hint for consistency
 			if (saw_enforced)
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("multiple ENABLE/DISABLE clauses not allowed"),
-						 errhint("Update your CONSTRAINT definition."),
 						 parser_errposition(cxt->pstate, con->location)));
src/backend/oracle_parser/ora_gram.y (3)

4568-4627: ENABLE/DISABLE [NO]VALIDATE ConstraintAttr semantics need downstream handling

The new ConstraintAttr variants for:

  • ENABLE [VALIDATE|NOVALIDATE]
  • DISABLE [VALIDATE|NOVALIDATE]

populate contype = CONSTR_ATTR_ENABLE/DISABLE plus skip_validation and initially_valid. This is reasonable, but behavior depends on parse/analyze code actually interpreting these fields for CONSTR_ATTR_ENABLE/CONSTR_ATTR_DISABLE (e.g., in parse_utilcmd.c).

If downstream still only looks at the legacy CONSTR_ATTR_[NOT_]DEFERRABLE/... values, these new attributes may be silently ignored.

Consider:

  • Verifying that all Constraint-attribute handling switch statements cover the new CONSTR_ATTR_ENABLE / CONSTR_ATTR_DISABLE variants.
  • Adding a defensive default: in such switches that errors if an unsupported attribute contype is seen, to avoid silent no-ops.

Would you like me to generate a small rg/ast-grep script to scan for CONSTR_ATTR_ usages and confirm all cases handle the new values?


6733-6750: OraConstraintAttributeElem → CAS_ mapping is fine but lacks conflict checks*

Mapping:

  • ENABLECAS_ENABLE
  • DISABLECAS_DISABLE
  • VALIDATECAS_VALIDATE (no-op, default)
  • NOVALIDATECAS_NOVALIDATE

is straightforward and works with later processCASbits() remapping. However, ConstraintAttributeSpec’s conflict checks only look at CAS_NOT_DEFERRABLE/DEFERRABLE, CAS_INITIALLY_*, and CAS_NOT_ENFORCED/ENFORCED, so combinations like ENABLE DISABLE or ENFORCED DISABLE are not detected there and currently rely entirely on processCASbits() behavior.

Given the PR description mentions future tests for “duplicate/opposite clauses”, it may be worth:

  • Either adding explicit conflict checks for CAS_ENABLE/CAS_DISABLE and CAS_VALIDATE/CAS_NOVALIDATE in ConstraintAttributeSpec, or
  • Documenting that these combinations are undefined for now so tests don’t accidentally rely on the current behavior.

22201-22345: processCASbits: ENABLE/DISABLE/[NO]VALIDATE mapping mostly correct, but be careful with unsupported callers

The updated processCASbits():

  • Maps:
    • CAS_ENABLECAS_ENFORCED
    • CAS_DISABLECAS_NOT_ENFORCED
    • CAS_NOVALIDATECAS_NOT_VALID
  • Issues Oracle-style errors/hints when:
    • NOVALIDATE or DISABLE are used on constraint types that don’t support NOT VALID or NOT ENFORCED.

The main semantic points look good, but there are a couple of edge considerations:

  • Callers that pass NULL for is_enforced or not_valid now rely on the new if (cas_bits & CAS_DISABLE) / if (cas_bits & CAS_NOVALIDATE) branches for error reporting. That’s fine, but it means any future caller added with NULL pointers will get Oracle-style error text by default, which might not always be desired.
  • There is no second-stage conflict check after CAS_ENABLE/CAS_DISABLE are mapped to CAS_ENFORCED/CAS_NOT_ENFORCED, so combinations like ENABLE DISABLE or ENFORCED DISABLE end up with both bits set but are not flagged here (only plain ENFORCED vs NOT ENFORCED conflicts are blocked in ConstraintAttributeSpec). If you care about rejecting such opposite combinations, you may want a small consistency check after the CAS_ENABLE/DISABLE remap.

Overall the mapping is consistent, but it would be good to:

  • Audit all processCASbits call sites to ensure that combinations of ENABLE/DISABLE/[NO]VALIDATE either have supported pointer fields or get the desired error behavior.
  • Optionally add a post-remap assertion for (CAS_ENFORCED | CAS_NOT_ENFORCED) being non-conflicting if you want to hard-fail on contradictory specs.

Would you like a quick grep-based script to list all processCASbits( call sites and summarize which boolean pointer arguments are NULL vs non-NULL?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd6e09e and 1516fdb.

📒 Files selected for processing (8)
  • src/backend/commands/typecmds.c
  • src/backend/oracle_parser/ora_gram.y
  • src/backend/parser/parse_utilcmd.c
  • src/include/nodes/parsenodes.h
  • src/oracle_test/regress/expected/constraints.out
  • src/oracle_test/regress/expected/foreign_key.out
  • src/oracle_test/regress/sql/constraints.sql
  • src/oracle_test/regress/sql/foreign_key.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/oracle_test/regress/expected/foreign_key.out
  • src/oracle_test/regress/sql/constraints.sql
🧰 Additional context used
📓 Path-based instructions (2)
**/sql/*.sql

⚙️ CodeRabbit configuration file

**/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.

Files:

  • src/oracle_test/regress/sql/foreign_key.sql
**/expected/*.out

⚙️ CodeRabbit configuration file

**/expected/*.out: These are expected test outputs from pg_regress. Review for:

  • Correctness of query results
  • Proper Oracle compatibility behavior
  • Edge case coverage
  • Consistency with corresponding .sql test files

Files:

  • src/oracle_test/regress/expected/constraints.out
🔇 Additional comments (12)
src/include/nodes/parsenodes.h (5)

765-790: ColumnDef is_invisible field placement looks fine

Appending bool is_invisible after location is consistent with the rest of the struct and matches the comment semantics for invisible columns. Given makeNode(ColumnDef) zero-initializes memory, the default false behavior for existing callers is preserved.


2201-2229: SelectStmt valuesIsrow flag

The new bool valuesIsrow flag is a clean way to distinguish row-valued VALUES forms. Since nodes are zeroed on allocation, existing code will see false by default. As with other new fields, please confirm it’s wired into outfuncs/readfuncs/equalfuncs for SelectStmt.


2512-2527: AlterTableCmd is_rowid extension

Introducing bool is_rowid on AlterTableCmd is structurally safe (zero-initialized, placed at the end). Behavior-wise, just confirm all construction sites that need this set it explicitly, and that node serialization/deserialization for AlterTableCmd was updated where necessary.


2823-2844: ConstrType enum extension for ENABLE/DISABLE is consistent

Adding CONSTR_ATTR_ENABLE and CONSTR_ATTR_DISABLE at the end of ConstrType preserves existing numeric values and is an appropriate place for new constraint-attribute kinds. The corresponding handling in DefineDomain (in typecmds.c) routes these to the same error path as other enforceability attributes, which matches the stated behavior of rejecting ENABLE/DISABLE on domains.

Please also ensure all other switch (ConstrType ...) sites (e.g., in parser/transform and utility code, plus outfuncs/readfuncs) are updated to either handle or explicitly reject these new enum values.


4381-4474: Fix ACCESSOR_UNKNOW typo to ACCESSOR_UNKNOWN

The enum constant ACCESSOR_UNKNOW at line 4388 is misspelled and should be ACCESSOR_UNKNOWN before this becomes part of the stable API. If backward compatibility with the misspelling is needed, keep the misspelled name as a deprecated alias.

Verify that the new node types (AccessorItem, AccessibleByClause, CompileFunctionStmt, ColumnRefOrFuncCall, CreatePackageStmt, CreatePackageBodyStmt, AlterPackageStmt) are fully integrated into node infrastructure functions including copy, equal, read/write, and query-jumble support to ensure packages and accessibility clauses serialize correctly through views and stored rules.

src/oracle_test/regress/expected/constraints.out (2)

2449-2458: LGTM: Correct NOT VALID behavior for ALTER TABLE ADD.

The test correctly shows that ALTER TABLE ADD CHECK ... ENABLE NOVALIDATE creates an enforced but not-validated constraint (conenforced = t, convalidated = f). This aligns with PostgreSQL's support for adding NOT VALID constraints via ALTER TABLE, which allows existing data to violate the constraint while preventing new violations.

This differs from the CREATE TABLE behavior (line 2021) where ENABLE NOVALIDATE creates a fully validated constraint, which is appropriate since there's no existing data at table creation time.

Based on coding guidelines for test expected outputs, this correctly demonstrates proper Oracle compatibility behavior.


1991-2121: The inconsistency between CONSTRAINT CHECK and inline CHECK behavior is real but appears intentional.

The test output correctly reflects the current implementation where:

  • CONSTRAINT CHECK accepts ENABLE NOVALIDATE and DISABLE VALIDATE (succeeds, creates constraint)
  • Inline CHECK rejects both with "not supported" errors

Per the commit message for this WIP (1516fdb), this is acknowledged as a "hack" while reworking ENABLE/DISABLE handling. The constraint attribute handling differs between named and inline constraints by design during this refactoring phase. The test expectations are accurate for the current behavior, and the error messages properly guide users.

Consider whether this inconsistency should be unified in the final implementation or documented as a temporary limitation while the rework is in progress.

src/backend/parser/parse_utilcmd.c (2)

1105-1108: LGTM!

The fallthrough cases for CONSTR_ATTR_ENABLE and CONSTR_ATTR_DISABLE correctly follow the established pattern for other constraint attributes that are preprocessed by transformConstraintAttrs.


1248-1252: LGTM!

Correctly errors out when CONSTR_ATTR_ENABLE or CONSTR_ATTR_DISABLE appears as a standalone table constraint, consistent with handling of other constraint attributes.

src/backend/oracle_parser/ora_gram.y (3)

141-156: CAS_ bitmask extension looks structurally sound*

The new CAS_ENABLE/CAS_DISABLE/CAS_VALIDATE/CAS_NOVALIDATE bits extend the existing flag space cleanly (no overlap, still within an int), and the comment separation (“extra syntax”) makes the intent clear. No issues here.


614-615: OraConstraintAttributeElem correctly wired into ConstraintAttributeSpec

Adding OraConstraintAttributeElem to the %type and to ConstraintAttributeSpec keeps all attribute forms flowing through the same bitmask path, which is what processCASbits() expects. This is the right integration point.


780-781: NOVALIDATE keyword categorization is consistent

Registering NOVALIDATE as an unreserved_keyword and bare_label_keyword allows it to be used in constraint-attribute positions while remaining available as an identifier where needed, consistent with how similar attribute words are treated. No parser-category conflicts are apparent.

Also applies to: 20463-20465, 21163-21165

Comment on lines +4241 to +4274
case CONSTR_ATTR_ENABLE:
if (con->skip_validation)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ENABLE NOVALIDATE clause is not supported"),
errhint("Update your CONSTRAINT definition."),
parser_errposition(cxt->pstate, con->location)));

if (lastprimarycon == NULL ||
(lastprimarycon->contype != CONSTR_PRIMARY &&
lastprimarycon->contype != CONSTR_CHECK &&
lastprimarycon->contype != CONSTR_UNIQUE &&
lastprimarycon->contype != CONSTR_NOTNULL &&
lastprimarycon->contype != CONSTR_FOREIGN))
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("misplaced ENABLE clause"),
parser_errposition(cxt->pstate, con->location)));
}
if (saw_enforced)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple ENABLE/DISABLE clauses not allowed"),
parser_errposition(cxt->pstate, con->location)));
con->contype = CONSTR_ATTR_ENFORCED;
saw_enforced = true;
lastprimarycon->is_enforced = true;
if (con->skip_validation)
{
lastprimarycon->skip_validation = true;
lastprimarycon->initially_valid = false;
}
break;
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Dead code: the skip_validation block at lines 4269-4273 is unreachable.

The check at line 4242 already errors out if con->skip_validation is true (for "ENABLE NOVALIDATE"). Therefore, when execution reaches line 4269, con->skip_validation is guaranteed to be false, making the entire block dead code.

Either:

  1. Remove the dead code block (lines 4269-4273), or
  2. If ENABLE NOVALIDATE should be supported in certain cases, the logic needs restructuring
🔧 Proposed fix: Remove dead code
 			con->contype = CONSTR_ATTR_ENFORCED;
 			saw_enforced = true;
 			lastprimarycon->is_enforced = true;
-			if (con->skip_validation)
-			{
-				lastprimarycon->skip_validation = true;
-				lastprimarycon->initially_valid = false;
-			}
 			break;
🤖 Prompt for AI Agents
In @src/backend/parser/parse_utilcmd.c around lines 4241 - 4274, The ENABLE
branch handling CONSTR_ATTR_ENABLE contains an unreachable inner `if
(con->skip_validation)` block because earlier it errors when
`con->skip_validation` is true; remove the dead block (the inner `if
(con->skip_validation)` that sets `lastprimarycon->skip_validation` and
`lastprimarycon->initially_valid`) from the CONSTR_ATTR_ENABLE case in
parse_utilcmd so the code no longer contains unreachable statements, or
alternatively, if ENABLE NOVALIDATE must be supported, restructure the earlier
guard that raises the error on `con->skip_validation` to allow controlled
handling here and adjust `con->contype`/`saw_enforced` logic accordingly.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@c2main
Hi, Cederic, I agree with codeRabbit about the dead code here. They should be removed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be revisited, depends on how strong we want to allow NOVALIDATE on CREATE statements.

Comment on lines +4304 to +4310
if (lastprimarycon == NULL ||
(lastprimarycon->contype != CONSTR_CHECK &&
lastprimarycon->contype != CONSTR_FOREIGN))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DISABLE clause is not supported"),
parser_errposition(cxt->pstate, con->location)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing HINT for unsupported constraint types with DISABLE.

Per the PR objectives, PRIMARY KEY ... DISABLE should "return an error and HINT to update the constraint definition." However, the current error message for DISABLE on unsupported constraint types (PRIMARY, UNIQUE, NOTNULL) lacks the errhint() that other error paths include.

🔧 Proposed fix: Add helpful hint
 			if (lastprimarycon == NULL ||
 				(lastprimarycon->contype != CONSTR_CHECK &&
 				 lastprimarycon->contype != CONSTR_FOREIGN))
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("DISABLE clause is not supported"),
+						 errmsg("DISABLE clause is not supported for this constraint type"),
+						 errhint("Update your CONSTRAINT definition to remove the DISABLE clause."),
 						 parser_errposition(cxt->pstate, con->location)));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (lastprimarycon == NULL ||
(lastprimarycon->contype != CONSTR_CHECK &&
lastprimarycon->contype != CONSTR_FOREIGN))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DISABLE clause is not supported"),
parser_errposition(cxt->pstate, con->location)));
if (lastprimarycon == NULL ||
(lastprimarycon->contype != CONSTR_CHECK &&
lastprimarycon->contype != CONSTR_FOREIGN))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DISABLE clause is not supported for this constraint type"),
errhint("Update your CONSTRAINT definition to remove the DISABLE clause."),
parser_errposition(cxt->pstate, con->location)));
🤖 Prompt for AI Agents
In @src/backend/parser/parse_utilcmd.c around lines 4304 - 4310, The error path
that checks lastprimarycon and raises ERROR for DISABLE on unsupported
constraint types (the ereport(...) call referencing lastprimarycon and
con->location) is missing the helpful errhint; modify that ereport to include an
errhint() suggesting the user update the constraint definition to remove the
DISABLE clause (for example: "Update the constraint definition without the
DISABLE clause, e.g. ALTER TABLE ... ADD CONSTRAINT ... (omit DISABLE)"). Ensure
the errhint text matches the style used in other DISABLE error paths.

@bigplaice
Copy link
Copy Markdown
Collaborator

Hi, Cedric, I'm looking at this PR and will provide feedback. Thanks!

Comment thread src/oracle_test/regress/expected/constraints.out
Comment thread src/oracle_test/regress/expected/constraints.out
Comment thread src/backend/oracle_parser/ora_gram.y
if (saw_enforced)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple ENABLE/DISABLE clauses not allowed"),
Copy link
Copy Markdown
Collaborator

@bigplaice bigplaice Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of CONSTR_ATTR_ENFORCED, the saw_enforced is set as true.
So, if both ENABLE and ENFORCED appear in statement, the errmsg here "multiple ENABLE/DISABLE clauses not allowed" become misleading. Should we use a new flag to replace saw_enforced?

(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple ENABLE/DISABLE clauses not allowed"),
parser_errposition(cxt->pstate, con->location)));
con->contype = CONSTR_ATTR_ENFORCED;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is con->contype here not CONSTR_ATTR_ENABLE?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is when entering this case but it then looks easier to assume this CONST_ATTR_ENABLE is transient, only the time to assign CONSTR_ATTR_ENFORCED. It allowed me not to have to inspect other logic related to contype value.

@c2main c2main force-pushed the support-constraint-enable_clause branch from 1516fdb to 90691f2 Compare February 5, 2026 14:29
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/backend/oracle_parser/ora_gram.y (2)

2928-2933: ⚠️ Potential issue | 🟠 Major

ALTER CONSTRAINT ENABLE/DISABLE won’t flip enforceability.

Line 2928 only checks CAS_NOT_ENFORCED | CAS_ENFORCED, so ENABLE/DISABLE won’t set alterEnforceability and can be ignored downstream.

🐛 Proposed fix
-					if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED))
+					if ($4 & (CAS_NOT_ENFORCED | CAS_ENFORCED | CAS_ENABLE | CAS_DISABLE))
                         c->alterEnforceability = true;

6706-6725: ⚠️ Potential issue | 🟠 Major

Add conflict checks for ENABLE/DISABLE and VALIDATE/NOVALIDATE.

ConstraintAttributeSpec only detects deferrable/enforced conflicts. With OraConstraintAttributeElem, contradictory pairs (e.g., ENABLE + DISABLE, ENABLE + NOT ENFORCED, VALIDATE + NOVALIDATE) can slip through and become “last wins.”

🐛 Proposed fix
-					int		newspec = $1 | $2;
+					int		newspec = $1 | $2;
+					int		normspec = newspec;
+
+					if (normspec & CAS_ENABLE)
+						normspec |= CAS_ENFORCED;
+					if (normspec & CAS_DISABLE)
+						normspec |= CAS_NOT_ENFORCED;
+					if (normspec & CAS_NOVALIDATE)
+						normspec |= CAS_NOT_VALID;

 					/* special message for this case */
-					if ((newspec & (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED)) == (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED))
+					if ((normspec & (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED)) == (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED))
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
 								 errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"),
 								 parser_errposition(`@2`)));
 					/* generic message for other conflicts */
-					if ((newspec & (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE)) == (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE) ||
-						(newspec & (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED)) == (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED) ||
-						(newspec & (CAS_NOT_ENFORCED | CAS_ENFORCED)) == (CAS_NOT_ENFORCED | CAS_ENFORCED))
+					if ((normspec & (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE)) == (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE) ||
+						(normspec & (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED)) == (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED) ||
+						(normspec & (CAS_NOT_ENFORCED | CAS_ENFORCED)) == (CAS_NOT_ENFORCED | CAS_ENFORCED) ||
+						(newspec & (CAS_VALIDATE | CAS_NOVALIDATE)) == (CAS_VALIDATE | CAS_NOVALIDATE))
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
 								 errmsg("conflicting constraint properties"),
 								 parser_errposition(`@2`)));

Also applies to: 6733-6749

🧹 Nitpick comments (1)
src/backend/parser/parse_utilcmd.c (1)

4255-4260: Inconsistent error code for "misplaced" error.

The error uses ERRCODE_FEATURE_NOT_SUPPORTED with message "misplaced ENABLE clause", but all other "misplaced" errors in this function (e.g., lines 4152, 4163, 4188, 4230) use ERRCODE_SYNTAX_ERROR.

For consistency, consider using ERRCODE_SYNTAX_ERROR here as well.

🛠️ Proposed fix for consistency
 			{
 				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						(errcode(ERRCODE_SYNTAX_ERROR),
 						 errmsg("misplaced ENABLE clause"),
 						 parser_errposition(cxt->pstate, con->location)));
 			}

c2main and others added 6 commits February 21, 2026 13:22
* Not covering ALTER and DOMAIN.

* Add unit testing with catalog lookup to check VALID and ENFORCED status in pg_constraints.

* Valid only starting with PostgreSQL 18 (which has been enhanced
  regarding supported options on CONSTRAINT).
Support for syntax and mapping to PostgreSQL policies (see CAS
constants)

HINTs to suggest updating the syntax when relevant.

In `ora_gram.y`, extended the bitmask to cover more cases.
The previous patchset is a working PoC with few bugs.

In this one I have updated parsenode for constrType, adding
CONS_ATTR_ENABLE/DISABLE, which allows to manage the clause like it's
done with ENFORCE/NOT ENFORCED.

Only real "hack" is to accept ENABLE in CREATE TABLE, see bottom of
processCASbits()
Adding checks in `ora_gram.y` and related unit test.
@c2main c2main force-pushed the support-constraint-enable_clause branch from df98ef0 to ca7c95f Compare February 21, 2026 12:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/oracle_test/regress/expected/constraints.out (1)

2585-2631: Consider ordering multi-row pg_constraint outputs to avoid nondeterminism.

The multi-row queries (Lines 2585–2593 and 2625–2631) don’t specify an ORDER BY. This relies on heap/insert order and can be fragile if catalog changes reorder rows. Recommend adding ORDER BY conname in the SQL and updating the expected output accordingly.
As per coding guidelines, “These are expected test outputs from pg_regress. Review for: Correctness of query results; Proper Oracle compatibility behavior; Edge case coverage; Consistency with corresponding .sql test files”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle_test/regress/expected/constraints.out` around lines 2585 - 2631,
The SELECT queries against pg_constraint that return multiple rows (the
statements starting "SELECT conname, contype, conenforced, convalidated FROM
pg_constraint WHERE conrelid = 'enable_noconflict'::regclass;" and the similar
ones for 'disable_noconflict' and 'novalidate_noconflict') are nondeterministic;
add "ORDER BY conname" to each of those SELECTs to produce a stable ordering and
then update the expected output blocks to match the new sorted ordering. Ensure
you modify each multi-row SELECT that queries pg_constraint (referenced above)
and regenerate the expected output so the test comparisons are deterministic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/backend/oracle_parser/ora_gram.y`:
- Around line 4568-4627: The grammar actions create Constraint nodes for
ENABLE_P / DISABLE_P alternatives but always set initially_valid = true; change
each alternative (the actions for ENABLE_P, ENABLE_P VALIDATE, ENABLE_P
NOVALIDATE, DISABLE_P, DISABLE_P NOVALIDATE, DISABLE_P VALIDATE in ora_gram.y)
so that after setting n->skip_validation you set n->initially_valid based on
that flag (e.g., n->initially_valid = !n->skip_validation) instead of hardcoding
true, keeping n->contype and n->location assignments intact.

In `@src/oracle_test/regress/expected/constraints.out`:
- Around line 1806-1836: The error output for unsupported DISABLE variants is
inconsistent: add the missing HINT lines to the failing cases so they match the
pk_disable_validate case; update the expected file entries for pk_disable and
pk_disable_novalidate in constraints.out to include the same "HINT:  Update your
CONSTRAINT definition." line immediately after their ERROR blocks (keeping the
existing ERROR text, caret line and spacing) so pk_disable, pk_disable_validate
and pk_disable_novalidate all show the HINT consistently.
- Around line 2013-2088: The test shows inconsistent handling of "ENABLE
NOVALIDATE" between table-level CONSTRAINT (e.g.,
ccheck_check_enable_novalidate) which is accepted and recorded as convalidated =
t, and column-level CHECK (check_enable_novalidate) which currently errors; make
them consistent by changing the column-level parser/validator that handles CHECK
... ENABLE NOVALIDATE to either accept and map to the same internal state as
table-level constraints (so convalidated becomes true) or to explicitly reject
with the same error/HINT as the table-level path—update the code path that
processes column-level CHECK clauses (look for the function that builds column
constraints/flags for CHECK and the handler for ENABLE NOVALIDATE) and then
update the expected output in constraints.out to match the chosen behavior
(adjust the entry for check_enable_novalidate_id_check or the error text to
mirror the table-level case).

In `@src/oracle_test/regress/sql/constraints.sql`:
- Around line 1044-1058: Add migration tests that exercise ALTER TABLE ... ALTER
CONSTRAINT state transitions for the existing schema/tables: use
ivo_regress_enable and the constraints cpk_pk_enable and cpk_pk_enable_validate
to toggle through DISABLE NOVALIDATE → ENABLE VALIDATE and NOT VALID → VALIDATE
sequences; specifically, add ALTER TABLE ... ALTER CONSTRAINT statements (e.g.,
ALTER TABLE cpk_enable ALTER CONSTRAINT cpk_pk_enable DISABLE NOVALIDATE, then
ENABLE VALIDATE) followed by queries against pg_constraint (conname, contype,
conenforced, convalidated) to assert the expected state at each step so
transitions are covered for both cpk_pk_enable and cpk_pk_enable_validate.

In `@src/oracle_test/regress/sql/foreign_key.sql`:
- Around line 2391-2558: Add SQL regression cases that exercise ALTER-time
transitions and conflicting/duplicate clause combinations: add ALTER TABLE ...
ENABLE / DISABLE / VALIDATE / NOVALIDATE transitions for representative tables
(e.g., fk_p36, fk_p33, fk_p13) to test moves like NOT ENFORCED NOT VALID →
ENFORCED VALIDATE and vice versa, and add creation/alter statements with
conflicting clauses (e.g., CREATE/ALTER with both ENABLE and DISABLE, ENABLE
NOVALIDATE vs ENABLE VALIDATE, or duplicate ENABLE/ENABLE) for tables such as
fk_p1/fk_p4/fk_p16 and constraint names like fk_p13_fk_r_id_fkey or fk_fk_p23 to
validate parser/state handling and ensure SELECT checks against pg_constraint
(conenforced, convalidated) follow each transition.

---

Duplicate comments:
In `@src/backend/parser/parse_utilcmd.c`:
- Around line 4297-4310: The second error path for CONSTR_ATTR_DISABLE (the
check that verifies lastprimarycon and its contype against
CONSTR_CHECK/CONSTR_FOREIGN) is missing an errhint; modify the ereport(ERROR,
...) that reports "DISABLE clause is not supported" to include the same errhint
used above (e.g. errhint("Update your CONSTRAINT definition.")) and keep the
existing errcode and parser_errposition calls (reference symbols:
CONSTR_ATTR_DISABLE, lastprimarycon, lastprimarycon->contype, CONSTR_CHECK,
CONSTR_FOREIGN, parser_errposition(cxt->pstate, con->location)).
- Around line 4241-4274: The ENABLE NOVALIDATE branch inside the
CONSTR_ATTR_ENABLE case is unreachable because con->skip_validation is already
rejected earlier; either remove the dead block that sets
lastprimarycon->skip_validation and lastprimarycon->initially_valid, or if
ENABLE NOVALIDATE should be supported, relax/remove the earlier ereport that
errors on con->skip_validation and ensure proper validation by keeping the
assignments to lastprimarycon->skip_validation and
lastprimarycon->initially_valid in the CONSTR_ATTR_ENABLE handling (referencing
con->skip_validation, lastprimarycon, and lastprimarycon->is_enforced to locate
the code).

---

Nitpick comments:
In `@src/oracle_test/regress/expected/constraints.out`:
- Around line 2585-2631: The SELECT queries against pg_constraint that return
multiple rows (the statements starting "SELECT conname, contype, conenforced,
convalidated FROM pg_constraint WHERE conrelid = 'enable_noconflict'::regclass;"
and the similar ones for 'disable_noconflict' and 'novalidate_noconflict') are
nondeterministic; add "ORDER BY conname" to each of those SELECTs to produce a
stable ordering and then update the expected output blocks to match the new
sorted ordering. Ensure you modify each multi-row SELECT that queries
pg_constraint (referenced above) and regenerate the expected output so the test
comparisons are deterministic.

Comment on lines +4568 to +4627
| ENABLE_P
{
Constraint *n = makeNode(Constraint);

n->contype = CONSTR_ATTR_ENABLE;
n->location = @1;
n->skip_validation = false;
n->initially_valid = true;
$$ = (Node *) n;
}
| ENABLE_P VALIDATE
{
Constraint *n = makeNode(Constraint);

n->contype = CONSTR_ATTR_ENABLE;
n->location = @1;
n->skip_validation = false;
n->initially_valid = true;
$$ = (Node *) n;
}
| ENABLE_P NOVALIDATE
{
Constraint *n = makeNode(Constraint);

n->contype = CONSTR_ATTR_ENABLE;
n->location = @1;
n->skip_validation = true;
n->initially_valid = true;
$$ = (Node *) n;
}
| DISABLE_P
{
Constraint *n = makeNode(Constraint);

n->contype = CONSTR_ATTR_DISABLE;
n->location = @1;
n->skip_validation = true;
n->initially_valid = true;
$$ = (Node *) n;
}
| DISABLE_P NOVALIDATE
{
Constraint *n = makeNode(Constraint);

n->contype = CONSTR_ATTR_DISABLE;
n->location = @1;
n->skip_validation = true;
n->initially_valid = true;
$$ = (Node *) n;
}
| DISABLE_P VALIDATE
{
Constraint *n = makeNode(Constraint);

n->contype = CONSTR_ATTR_DISABLE;
n->location = @1;
n->skip_validation = false;
n->initially_valid = true;
$$ = (Node *) n;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

NOVALIDATE is marked as initially valid.

skip_validation is set to true for NOVALIDATE/ DISABLE, but initially_valid is hardcoded to true, which makes NOVALIDATE constraints appear validated. This can break later VALIDATE transitions or status reporting. Set initially_valid based on skip_validation for all ENABLE/DISABLE branches.

✅ Proposed fix
-					n->skip_validation = false;
-					n->initially_valid = true;
+					n->skip_validation = false;
+					n->initially_valid = !n->skip_validation;
...
-					n->skip_validation = true;
-					n->initially_valid = true;
+					n->skip_validation = true;
+					n->initially_valid = !n->skip_validation;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/oracle_parser/ora_gram.y` around lines 4568 - 4627, The grammar
actions create Constraint nodes for ENABLE_P / DISABLE_P alternatives but always
set initially_valid = true; change each alternative (the actions for ENABLE_P,
ENABLE_P VALIDATE, ENABLE_P NOVALIDATE, DISABLE_P, DISABLE_P NOVALIDATE,
DISABLE_P VALIDATE in ora_gram.y) so that after setting n->skip_validation you
set n->initially_valid based on that flag (e.g., n->initially_valid =
!n->skip_validation) instead of hardcoding true, keeping n->contype and
n->location assignments intact.

Comment on lines +1806 to +1836
-- Not supported: NOT ENFORCED
CREATE TABLE pk_disable (id NUMBER PRIMARY KEY DISABLE, name VARCHAR2(100));
ERROR: DISABLE clause is not supported
LINE 1: CREATE TABLE pk_disable (id NUMBER PRIMARY KEY DISABLE, name...
^
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'pk_disable_pkey';
conname | contype | conenforced | convalidated
---------+---------+-------------+--------------
(0 rows)

-- Not supported: VALIDATE .. NOT ENFORCED
CREATE TABLE pk_disable_validate (id NUMBER PRIMARY KEY DISABLE VALIDATE, name VARCHAR2(100));
ERROR: DISABLE VALIDATE clause is not supported
LINE 1: ... TABLE pk_disable_validate (id NUMBER PRIMARY KEY DISABLE VA...
^
HINT: Update your CONSTRAINT definition.
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'pk_disable_validate_pkey';
conname | contype | conenforced | convalidated
---------+---------+-------------+--------------
(0 rows)

-- Not supported: NOT ENFORCED NOT VALID
CREATE TABLE pk_disable_novalidate (id NUMBER PRIMARY KEY DISABLE NOVALIDATE, name VARCHAR2(100));
ERROR: DISABLE clause is not supported
LINE 1: ...ABLE pk_disable_novalidate (id NUMBER PRIMARY KEY DISABLE NO...
^
SELECT conname, contype, conenforced, convalidated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Standardize HINT presence for unsupported DISABLE variants.

Some unsupported cases (e.g., Line 1807 and Line 1832) emit errors without a HINT, while similar cases earlier (e.g., Line 1734) include a HINT. If the implementation is meant to always provide guidance for unsupported combinations, the expected output should consistently include the HINT; otherwise, the error messaging should be normalized in code/tests.
As per coding guidelines, “These are expected test outputs from pg_regress. Review for: Correctness of query results; Proper Oracle compatibility behavior; Edge case coverage; Consistency with corresponding .sql test files”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle_test/regress/expected/constraints.out` around lines 1806 - 1836,
The error output for unsupported DISABLE variants is inconsistent: add the
missing HINT lines to the failing cases so they match the pk_disable_validate
case; update the expected file entries for pk_disable and pk_disable_novalidate
in constraints.out to include the same "HINT:  Update your CONSTRAINT
definition." line immediately after their ERROR blocks (keeping the existing
ERROR text, caret line and spacing) so pk_disable, pk_disable_validate and
pk_disable_novalidate all show the HINT consistently.

Comment on lines +2013 to +2088
-- ENFORCED NOT VALID - saved as ENFORCED VALID
CREATE TABLE ccheck_enable_novalidate (id NUMBER, name VARCHAR2(100),
CONSTRAINT ccheck_check_enable_novalidate CHECK (id > 0) ENABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'ccheck_check_enable_novalidate';
conname | contype | conenforced | convalidated
--------------------------------+---------+-------------+--------------
ccheck_check_enable_novalidate | c | t | t
(1 row)

-- NOT ENFORCED
CREATE TABLE ccheck_disable (id NUMBER, name VARCHAR2(100),
CONSTRAINT ccheck_check_disable CHECK (id > 0) DISABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'ccheck_check_disable';
conname | contype | conenforced | convalidated
----------------------+---------+-------------+--------------
ccheck_check_disable | c | f | f
(1 row)

-- VALIDATE .. NOT ENFORCED - saved as NOT ENFORCED NOT VALID
CREATE TABLE ccheck_disable_validate (id NUMBER, name VARCHAR2(100),
CONSTRAINT ccheck_check_disable_validate CHECK (id > 0) DISABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'ccheck_check_disable_validate';
conname | contype | conenforced | convalidated
-------------------------------+---------+-------------+--------------
ccheck_check_disable_validate | c | f | f
(1 row)

-- NOT ENFORCED NOT VALID
CREATE TABLE ccheck_disable_novalidate (id NUMBER, name VARCHAR2(100),
CONSTRAINT ccheck_check_disable_novalidate CHECK (id > 0) DISABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'ccheck_check_disable_novalidate';
conname | contype | conenforced | convalidated
---------------------------------+---------+-------------+--------------
ccheck_check_disable_novalidate | c | f | f
(1 row)

-- CHECK
CREATE TABLE check_enable (id NUMBER CHECK (id>0) ENABLE, name VARCHAR2(100));
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'check_enable_id_check';
conname | contype | conenforced | convalidated
-----------------------+---------+-------------+--------------
check_enable_id_check | c | t | t
(1 row)

-- VALIDATE .. ENFORCED
CREATE TABLE check_enable_validate (id NUMBER CHECK (id>0) ENABLE VALIDATE, name VARCHAR2(100));
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'check_enable_validate_id_check';
conname | contype | conenforced | convalidated
--------------------------------+---------+-------------+--------------
check_enable_validate_id_check | c | t | t
(1 row)

-- Not supported: ENFORCED NOT VALID
CREATE TABLE check_enable_novalidate (id NUMBER CHECK (id>0) ENABLE NOVALIDATE, name VARCHAR2(100));
ERROR: ENABLE NOVALIDATE clause is not supported
LINE 1: ...E check_enable_novalidate (id NUMBER CHECK (id>0) ENABLE NOV...
^
HINT: Update your CONSTRAINT definition.
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'check_enable_novalidate_id_check';
conname | contype | conenforced | convalidated
---------+---------+-------------+--------------
(0 rows)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align CREATE TABLE CHECK NOVALIDATE behavior across table- and column-level forms.

Line 2013 accepts CONSTRAINT ... CHECK ... ENABLE NOVALIDATE and records convalidated = t, but Line 2077 expects an error for CHECK ... ENABLE NOVALIDATE at column level. This inconsistency will make Oracle compatibility and parser behavior diverge depending on constraint form. Please make both forms consistent (either both accept and map to the same convalidated behavior, or both reject with the same error/HINT) and update the expected output accordingly.
As per coding guidelines, “These are expected test outputs from pg_regress. Review for: Correctness of query results; Proper Oracle compatibility behavior; Edge case coverage; Consistency with corresponding .sql test files”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle_test/regress/expected/constraints.out` around lines 2013 - 2088,
The test shows inconsistent handling of "ENABLE NOVALIDATE" between table-level
CONSTRAINT (e.g., ccheck_check_enable_novalidate) which is accepted and recorded
as convalidated = t, and column-level CHECK (check_enable_novalidate) which
currently errors; make them consistent by changing the column-level
parser/validator that handles CHECK ... ENABLE NOVALIDATE to either accept and
map to the same internal state as table-level constraints (so convalidated
becomes true) or to explicitly reject with the same error/HINT as the
table-level path—update the code path that processes column-level CHECK clauses
(look for the function that builds column constraints/flags for CHECK and the
handler for ENABLE NOVALIDATE) and then update the expected output in
constraints.out to match the chosen behavior (adjust the entry for
check_enable_novalidate_id_check or the error text to mirror the table-level
case).

Comment on lines +1044 to +1058

--- tests for CREATE TABLE (<column> .. ENABLE|DISABLE ..)
CREATE SCHEMA ivo_regress_enable;
SET search_path to ivo_regress_enable;

-- CONSTRAINT PRIMARY KEY
CREATE TABLE cpk_enable (id NUMBER, name VARCHAR2(100),
CONSTRAINT cpk_pk_enable PRIMARY KEY (id) ENABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'cpk_pk_enable';

-- VALIDATE .. ENFORCED
CREATE TABLE cpk_enable_validate (id NUMBER, name VARCHAR2(100),
CONSTRAINT cpk_pk_enable_validate PRIMARY KEY (id) ENABLE VALIDATE);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add ALTER CONSTRAINT state‑transition tests (ENABLE/DISABLE/VALIDATE/NOVALIDATE).

This block covers CREATE TABLE and ALTER TABLE ADD CONSTRAINT, but it doesn’t exercise toggling existing constraints (e.g., DISABLE NOVALIDATE → ENABLE VALIDATE, NOT VALID → VALIDATE CONSTRAINT). Those transitions are part of the ALTER feature surface and need regression coverage to catch state‑change regressions. As per coding guidelines: **/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle_test/regress/sql/constraints.sql` around lines 1044 - 1058, Add
migration tests that exercise ALTER TABLE ... ALTER CONSTRAINT state transitions
for the existing schema/tables: use ivo_regress_enable and the constraints
cpk_pk_enable and cpk_pk_enable_validate to toggle through DISABLE NOVALIDATE →
ENABLE VALIDATE and NOT VALID → VALIDATE sequences; specifically, add ALTER
TABLE ... ALTER CONSTRAINT statements (e.g., ALTER TABLE cpk_enable ALTER
CONSTRAINT cpk_pk_enable DISABLE NOVALIDATE, then ENABLE VALIDATE) followed by
queries against pg_constraint (conname, contype, conenforced, convalidated) to
assert the expected state at each step so transitions are covered for both
cpk_pk_enable and cpk_pk_enable_validate.

Comment on lines +2391 to +2558
CREATE SCHEMA fkpart14;
SET search_path to fkpart14;

CREATE TABLE fk_r (id NUMBER PRIMARY KEY, name VARCHAR2(100));

-- ENFORCED
CREATE TABLE fk_p1 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER REFERENCES fk_r(id) ENABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p1_fk_r_id_fkey';

-- VALIDATE .. ENFORCED
CREATE TABLE fk_p2 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER REFERENCES fk_r(id) ENABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p2_fk_r_id_fkey';

-- Not supported: ENFORCED NOT VALID
CREATE TABLE fk_p3 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER REFERENCES fk_r(id) ENABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p3_fk_r_id_fkey';

-- NOT ENFORCED
CREATE TABLE fk_p4 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER REFERENCES fk_r(id) DISABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p4_fk_r_id_fkey';

-- Not supported: VALIDATE .. NOT ENFORCED
CREATE TABLE fk_p5 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER REFERENCES fk_r(id) DISABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p5_fk_r_id_fkey';

-- NOT ENFORCED NOT VALID
CREATE TABLE fk_p6 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER REFERENCES fk_r(id) DISABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p6_fk_r_id_fkey';

-- ENFORCED
CREATE TABLE fk_p11 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p11_fk_r_id_fkey';

-- VALIDATE .. ENFORCED
CREATE TABLE fk_p12 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p12_fk_r_id_fkey';

-- ENFORCED NOT VALID - saved as ENFORCED VALID
CREATE TABLE fk_p13 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p13_fk_r_id_fkey';

-- NOT ENFORCED
CREATE TABLE fk_p14 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p14_fk_r_id_fkey';

-- VALIDATE .. NOT ENFORCED - saved as NOT ENFORCED NOT VALID
CREATE TABLE fk_p15 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p15_fk_r_id_fkey';

-- NOT ENFORCED NOT VALID
CREATE TABLE fk_p16 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p16_fk_r_id_fkey';

-- ENFORCED
CREATE TABLE fk_p21 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
CONSTRAINT fk_fk_p21 FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_fk_p21';

-- VALIDATE .. ENFORCED
CREATE TABLE fk_p22 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
CONSTRAINT fk_fk_p22 FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_fk_p22';

-- ENFORCED NOT VALID - saved as ENFORCED VALID
CREATE TABLE fk_p23 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
CONSTRAINT fk_fk_p23 FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_fk_p23';

-- NOT ENFORCED
CREATE TABLE fk_p24 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
CONSTRAINT fk_fk_p24 FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_fk_p24';

-- VALIDATE .. NOT ENFORCED - saved as NOT ENFORCED NOT VALID
CREATE TABLE fk_p25 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
CONSTRAINT fk_fk_p25 FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE VALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_fk_p25';

-- NOT ENFORCED NOT VALID
CREATE TABLE fk_p26 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER,
CONSTRAINT fk_fk_p26 FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE NOVALIDATE);
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_fk_p26';

-- ENFORCED
CREATE TABLE fk_p31 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER);
ALTER TABLE fk_p31 ADD FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE;
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p31_fk_r_id_fkey';

-- VALIDATE .. ENFORCED
CREATE TABLE fk_p32 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER);
ALTER TABLE fk_p32 ADD FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE VALIDATE;
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p32_fk_r_id_fkey';

-- ENFORCED NOT VALID
CREATE TABLE fk_p33 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER);
ALTER TABLE fk_p33 ADD FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) ENABLE NOVALIDATE;
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p33_fk_r_id_fkey';

-- NOT ENFORCED
CREATE TABLE fk_p34 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER);
ALTER TABLE fk_p34 ADD FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE;
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p34_fk_r_id_fkey';

-- VALIDATE .. NOT ENFORCED - saved as NOT ENFORCED NOT VALID
CREATE TABLE fk_p35 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER);
ALTER TABLE fk_p35 ADD FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE VALIDATE;
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p35_fk_r_id_fkey';

-- NOT ENFORCED NOT VALID
CREATE TABLE fk_p36 (id NUMBER PRIMARY KEY, name VARCHAR2(100), fk_r_id NUMBER);
ALTER TABLE fk_p36 ADD FOREIGN KEY (fk_r_id) REFERENCES fk_r(id) DISABLE NOVALIDATE;
SELECT conname, contype, conenforced, convalidated
FROM pg_constraint
WHERE conname = 'fk_p36_fk_r_id_fkey';

DROP SCHEMA fkpart14 CASCADE;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add regression coverage for ENABLE/DISABLE transitions and conflicting clauses

This block exercises creation-time permutations, but it doesn’t cover ALTER transitions (e.g., NOT ENFORCED/NOT VALID → ENFORCED VALIDATE) or conflicting/duplicate clause combinations (e.g., ENABLE DISABLE / ENFORCED DISABLE). These are explicitly called out as TODOs in the PR objectives; adding them here will complete coverage and protect the parser/state logic from regressions.

As per coding guidelines, **/sql/*.sql: Test SQL files. Ensure comprehensive coverage of features.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle_test/regress/sql/foreign_key.sql` around lines 2391 - 2558, Add
SQL regression cases that exercise ALTER-time transitions and
conflicting/duplicate clause combinations: add ALTER TABLE ... ENABLE / DISABLE
/ VALIDATE / NOVALIDATE transitions for representative tables (e.g., fk_p36,
fk_p33, fk_p13) to test moves like NOT ENFORCED NOT VALID → ENFORCED VALIDATE
and vice versa, and add creation/alter statements with conflicting clauses
(e.g., CREATE/ALTER with both ENABLE and DISABLE, ENABLE NOVALIDATE vs ENABLE
VALIDATE, or duplicate ENABLE/ENABLE) for tables such as fk_p1/fk_p4/fk_p16 and
constraint names like fk_p13_fk_r_id_fkey or fk_fk_p23 to validate parser/state
handling and ensure SELECT checks against pg_constraint (conenforced,
convalidated) follow each transition.

@c2main c2main marked this pull request as draft March 10, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants