diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 8504555bac..24d7d98722 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1898,7 +1898,8 @@ convalidated bool - Has the constraint been validated? Can only be false for foreign keys + Has the constraint been validated? + Currently, can only be false for foreign keys diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 4e02438483..01d3105bf6 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -42,9 +42,8 @@ ALTER TABLE name ALTER [ COLUMN ] column SET ( attribute_option = value [, ... ] ) ALTER [ COLUMN ] column RESET ( attribute_option [, ... ] ) ALTER [ COLUMN ] column SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } - ADD table_constraint - ADD table_constraint_using_index ADD table_constraint [ NOT VALID ] + ADD table_constraint_using_index VALIDATE CONSTRAINT constraint_name DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ] DISABLE TRIGGER [ trigger_name | ALL | USER ] @@ -235,27 +234,21 @@ ALTER TABLE name - ADD table_constraint - [ NOT VALID ] + ADD table_constraint [ NOT VALID ] This form adds a new constraint to a table using the same syntax as - . Newly added foreign key constraints can - also be defined as NOT VALID to avoid the - potentially lengthy initial check that must otherwise be performed. - Constraint checks are skipped at create table time, so - does not contain this option. - - - - - - VALIDATE CONSTRAINT - - - This form validates a foreign key constraint that was previously created - as NOT VALID. Constraints already marked valid do not - cause an error response. + , plus the option NOT + VALID, which is currently only allowed for foreign key + constraints. + If the constraint is marked NOT VALID, the + potentially-lengthy initial check to verify that all rows in the table + satisfy the constraint is skipped. The constraint will still be + enforced against subsequent inserts or updates (that is, they'll fail + unless there is a matching row in the referenced table). But the + database will not assume that the constraint holds for all rows in + the table, until it is validated by using the VALIDATE + CONSTRAINT option. @@ -311,6 +304,21 @@ ALTER TABLE name + + VALIDATE CONSTRAINT + + + This form validates a foreign key constraint that was previously created + as NOT VALID, by scanning the table to ensure there + are no unmatched rows. Nothing happens if the constraint is + already marked valid. + The value of separating validation from initial creation of the + constraint is that validation requires a lesser lock on the table + than constraint creation does. + + + + DROP CONSTRAINT [ IF EXISTS ] diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2c9f855f53..912f45c052 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -258,7 +258,7 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel, static void AlterSeqNamespaces(Relation classRel, Relation rel, Oid oldNspOid, Oid newNspOid, const char *newNspName, LOCKMODE lockmode); -static void ATExecValidateConstraint(Relation rel, const char *constrName); +static void ATExecValidateConstraint(Relation rel, char *constrName); static int transformColumnNameList(Oid relId, List *colList, int16 *attnums, Oid *atttypids); static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid, @@ -5726,9 +5726,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, createForeignKeyTriggers(rel, fkconstraint, constrOid, indexOid); /* - * Tell Phase 3 to check that the constraint is satisfied by existing rows - * We can skip this during table creation or if requested explicitly by - * specifying NOT VALID on an alter table statement. + * Tell Phase 3 to check that the constraint is satisfied by existing rows. + * We can skip this during table creation, or if requested explicitly by + * specifying NOT VALID in an ADD FOREIGN KEY command. */ if (!fkconstraint->skip_validation) { @@ -5755,7 +5755,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * ALTER TABLE VALIDATE CONSTRAINT */ static void -ATExecValidateConstraint(Relation rel, const char *constrName) +ATExecValidateConstraint(Relation rel, char *constrName) { Relation conrel; SysScanDesc scan; @@ -5810,7 +5810,7 @@ ATExecValidateConstraint(Relation rel, const char *constrName) */ refrel = heap_open(con->confrelid, RowShareLock); - validateForeignKeyConstraint((char *) constrName, rel, refrel, + validateForeignKeyConstraint(constrName, rel, refrel, con->conindid, conid); @@ -5830,6 +5830,7 @@ ATExecValidateConstraint(Relation rel, const char *constrName) heap_close(conrel, RowExclusiveLock); } + /* * transformColumnNameList - transform list of column names * diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ce36ea8be4..798d8a85a4 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -1006,6 +1006,8 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid) } fkcon->deferrable = stmt->deferrable; fkcon->initdeferred = stmt->initdeferred; + fkcon->skip_validation = false; + fkcon->initially_valid = true; /* ... and execute it */ ProcessUtility((Node *) atstmt, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 1d39674de4..62cff8a7de 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -94,6 +94,13 @@ typedef struct PrivTarget List *objs; } PrivTarget; +/* ConstraintAttributeSpec yields an integer bitmask of these flags: */ +#define CAS_NOT_DEFERRABLE 0x01 +#define CAS_DEFERRABLE 0x02 +#define CAS_INITIALLY_IMMEDIATE 0x04 +#define CAS_INITIALLY_DEFERRED 0x08 +#define CAS_NOT_VALID 0x10 + #define parser_yyerror(msg) scanner_yyerror(msg, yyscanner) #define parser_errposition(pos) scanner_errposition(pos, yyscanner) @@ -135,6 +142,9 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ static void SplitColQualList(List *qualList, List **constraintList, CollateClause **collClause, core_yyscan_t yyscanner); +static void processCASbits(int cas_bits, int location, const char *constrType, + bool *deferrable, bool *initdeferred, bool *not_valid, + core_yyscan_t yyscanner); %} @@ -429,8 +439,7 @@ static void SplitColQualList(List *qualList, %type ColQualList %type ColConstraint ColConstraintElem ConstraintAttr %type key_actions key_delete key_match key_update key_action -%type ConstraintAttributeSpec ConstraintDeferrabilitySpec - ConstraintTimeSpec +%type ConstraintAttributeSpec ConstraintAttributeElem %type ExistingIndex %type constraints_set_list @@ -2638,7 +2647,7 @@ ColConstraintElem: n->fk_matchtype = $4; n->fk_upd_action = (char) ($5 >> 8); n->fk_del_action = (char) ($5 & 0xFF); - n->skip_validation = FALSE; + n->skip_validation = false; n->initially_valid = true; $$ = (Node *)n; } @@ -2654,7 +2663,10 @@ ColConstraintElem: * combinations. * * See also ConstraintAttributeSpec, which can be used in places where - * there is no parsing conflict. + * there is no parsing conflict. (Note: currently, NOT VALID is an allowed + * clause in ConstraintAttributeSpec, but not here. Someday we might need + * to allow it here too, but for the moment it doesn't seem useful in the + * statements that use ConstraintAttr.) */ ConstraintAttr: DEFERRABLE @@ -2746,11 +2758,9 @@ ConstraintElem: n->location = @1; n->raw_expr = $3; n->cooked_expr = NULL; - if ($5 != 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("CHECK constraints cannot be deferred"), - parser_errposition(@5))); + processCASbits($5, @5, "CHECK", + NULL, NULL, NULL, + yyscanner); $$ = (Node *)n; } | UNIQUE '(' columnList ')' opt_definition OptConsTableSpace @@ -2763,8 +2773,9 @@ ConstraintElem: n->options = $5; n->indexname = NULL; n->indexspace = $6; - n->deferrable = ($7 & 1) != 0; - n->initdeferred = ($7 & 2) != 0; + processCASbits($7, @7, "UNIQUE", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); $$ = (Node *)n; } | UNIQUE ExistingIndex ConstraintAttributeSpec @@ -2776,8 +2787,9 @@ ConstraintElem: n->options = NIL; n->indexname = $2; n->indexspace = NULL; - n->deferrable = ($3 & 1) != 0; - n->initdeferred = ($3 & 2) != 0; + processCASbits($3, @3, "UNIQUE", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); $$ = (Node *)n; } | PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace @@ -2790,8 +2802,9 @@ ConstraintElem: n->options = $6; n->indexname = NULL; n->indexspace = $7; - n->deferrable = ($8 & 1) != 0; - n->initdeferred = ($8 & 2) != 0; + processCASbits($8, @8, "PRIMARY KEY", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); $$ = (Node *)n; } | PRIMARY KEY ExistingIndex ConstraintAttributeSpec @@ -2803,8 +2816,9 @@ ConstraintElem: n->options = NIL; n->indexname = $3; n->indexspace = NULL; - n->deferrable = ($4 & 1) != 0; - n->initdeferred = ($4 & 2) != 0; + processCASbits($4, @4, "PRIMARY KEY", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); $$ = (Node *)n; } | EXCLUDE access_method_clause '(' ExclusionConstraintList ')' @@ -2820,8 +2834,9 @@ ConstraintElem: n->indexname = NULL; n->indexspace = $7; n->where_clause = $8; - n->deferrable = ($9 & 1) != 0; - n->initdeferred = ($9 & 2) != 0; + processCASbits($9, @9, "EXCLUDE", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name @@ -2836,27 +2851,11 @@ ConstraintElem: n->fk_matchtype = $9; n->fk_upd_action = (char) ($10 >> 8); n->fk_del_action = (char) ($10 & 0xFF); - n->deferrable = ($11 & 1) != 0; - n->initdeferred = ($11 & 2) != 0; - n->skip_validation = false; - n->initially_valid = true; - $$ = (Node *)n; - } - | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name - opt_column_list key_match key_actions - NOT VALID - { - Constraint *n = makeNode(Constraint); - n->contype = CONSTR_FOREIGN; - n->location = @1; - n->pktable = $7; - n->fk_attrs = $4; - n->pk_attrs = $8; - n->fk_matchtype = $9; - n->fk_upd_action = (char) ($10 >> 8); - n->fk_del_action = (char) ($10 & 0xFF); - n->skip_validation = true; - n->initially_valid = false; + processCASbits($11, @11, "FOREIGN KEY", + &n->deferrable, &n->initdeferred, + &n->skip_validation, + yyscanner); + n->initially_valid = !n->skip_validation; $$ = (Node *)n; } ; @@ -4031,8 +4030,9 @@ CreateTrigStmt: n->columns = (List *) lsecond($6); n->whenClause = $14; n->isconstraint = TRUE; - n->deferrable = ($10 & 1) != 0; - n->initdeferred = ($10 & 2) != 0; + processCASbits($10, @10, "TRIGGER", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); n->constrrel = $9; $$ = (Node *)n; } @@ -4135,45 +4135,40 @@ OptConstrFromTable: ; ConstraintAttributeSpec: - ConstraintDeferrabilitySpec - { $$ = $1; } - | ConstraintDeferrabilitySpec ConstraintTimeSpec - { - if ($1 == 0 && $2 != 0) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), - parser_errposition(@1))); - $$ = $1 | $2; - } - | ConstraintTimeSpec - { - if ($1 != 0) - $$ = 3; - else - $$ = 0; - } - | ConstraintTimeSpec ConstraintDeferrabilitySpec - { - if ($2 == 0 && $1 != 0) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), - parser_errposition(@1))); - $$ = $1 | $2; - } - | /*EMPTY*/ + /*EMPTY*/ { $$ = 0; } + | ConstraintAttributeSpec ConstraintAttributeElem + { + /* + * We must complain about conflicting options. + * We could, but choose not to, complain about redundant + * options (ie, where $2's bit is already set in $1). + */ + int newspec = $1 | $2; + + /* special message for this case */ + if ((newspec & (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)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting constraint properties"), + parser_errposition(@2))); + $$ = newspec; + } ; -ConstraintDeferrabilitySpec: - NOT DEFERRABLE { $$ = 0; } - | DEFERRABLE { $$ = 1; } - ; - -ConstraintTimeSpec: - INITIALLY IMMEDIATE { $$ = 0; } - | INITIALLY DEFERRED { $$ = 2; } +ConstraintAttributeElem: + NOT DEFERRABLE { $$ = CAS_NOT_DEFERRABLE; } + | DEFERRABLE { $$ = CAS_DEFERRABLE; } + | INITIALLY IMMEDIATE { $$ = CAS_INITIALLY_IMMEDIATE; } + | INITIALLY DEFERRED { $$ = CAS_INITIALLY_DEFERRED; } + | NOT VALID { $$ = CAS_NOT_VALID; } ; @@ -4217,8 +4212,9 @@ CreateAssertStmt: n->trigname = $3; n->args = list_make1($6); n->isconstraint = TRUE; - n->deferrable = ($8 & 1) != 0; - n->initdeferred = ($8 & 2) != 0; + processCASbits($8, @8, "ASSERTION", + &n->deferrable, &n->initdeferred, NULL, + yyscanner); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -12865,6 +12861,64 @@ SplitColQualList(List *qualList, *constraintList = qualList; } +/* + * Process result of ConstraintAttributeSpec, and set appropriate bool flags + * in the output command node. Pass NULL for any flags the particular + * command doesn't support. + */ +static void +processCASbits(int cas_bits, int location, const char *constrType, + bool *deferrable, bool *initdeferred, bool *not_valid, + core_yyscan_t yyscanner) +{ + /* defaults */ + if (deferrable) + *deferrable = false; + if (initdeferred) + *initdeferred = false; + if (not_valid) + *not_valid = false; + + if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED)) + { + if (deferrable) + *deferrable = true; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is CHECK, UNIQUE, or similar */ + errmsg("%s constraints cannot be marked DEFERRABLE", + constrType), + parser_errposition(location))); + } + + if (cas_bits & CAS_INITIALLY_DEFERRED) + { + if (initdeferred) + *initdeferred = true; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is CHECK, UNIQUE, or similar */ + errmsg("%s constraints cannot be marked DEFERRABLE", + constrType), + parser_errposition(location))); + } + + if (cas_bits & CAS_NOT_VALID) + { + if (not_valid) + *not_valid = true; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is CHECK, UNIQUE, or similar */ + errmsg("%s constraints cannot be marked NOT VALID", + constrType), + parser_errposition(location))); + } +} + /* parser_init() * Initialize to parse one query string */ diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 485bf4b8e7..51899c6b5b 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1751,7 +1751,8 @@ transformFKConstraints(CreateStmtContext *cxt, /* * If CREATE TABLE or adding a column with NULL default, we can safely - * skip validation of the constraint. + * skip validation of FK constraints, and nonetheless mark them valid. + * (This will override any user-supplied NOT VALID flag.) */ if (skipValidation) { diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 06cf6fa4f7..00920f9eda 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1372,7 +1372,6 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, appendStringInfo(&buf, " DEFERRABLE"); if (conForm->condeferred) appendStringInfo(&buf, " INITIALLY DEFERRED"); - if (!conForm->convalidated) appendStringInfoString(&buf, " NOT VALID"); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 14937d4363..c65e3cd6e8 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1221,7 +1221,7 @@ typedef enum AlterTableType AT_DropInherit, /* NO INHERIT parent */ AT_AddOf, /* OF */ AT_DropOf, /* NOT OF */ - AT_GenericOptions, /* OPTIONS (...) */ + AT_GenericOptions /* OPTIONS (...) */ } AlterTableType; typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ @@ -1234,7 +1234,6 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ * constraint, or parent table */ DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */ bool missing_ok; /* skip error if missing? */ - bool validated; } AlterTableCmd; @@ -1469,8 +1468,9 @@ typedef struct CreateStmt * * If skip_validation is true then we skip checking that the existing rows * in the table satisfy the constraint, and just install the catalog entries - * for the constraint. This is currently used only during CREATE TABLE - * (when we know the table must be empty). + * for the constraint. A new FK constraint is marked as valid iff + * initially_valid is true. (Usually skip_validation and initially_valid + * are inverses, but we can set both true if the table is known empty.) * * Constraint attributes (DEFERRABLE etc) are initially represented as * separate Constraint nodes for simplicity of parsing. parse_utilcmd.c makes @@ -1544,7 +1544,7 @@ typedef struct Constraint char fk_upd_action; /* ON UPDATE action */ char fk_del_action; /* ON DELETE action */ bool skip_validation; /* skip validation of existing rows? */ - bool initially_valid; /* start the new constraint as valid */ + bool initially_valid; /* mark the new constraint as valid? */ } Constraint; /* ---------------------- @@ -2417,7 +2417,7 @@ typedef enum VacuumOption VACOPT_VERBOSE = 1 << 2, /* print progress info */ VACOPT_FREEZE = 1 << 3, /* FREEZE option */ VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */ - VACOPT_NOWAIT = 1 << 5 + VACOPT_NOWAIT = 1 << 5 /* don't wait to get lock (autovacuum only) */ } VacuumOption; typedef struct VacuumStmt diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons index a689dbc70e..f6f9ff4313 100644 --- a/src/interfaces/ecpg/preproc/ecpg.addons +++ b/src/interfaces/ecpg/preproc/ecpg.addons @@ -207,12 +207,6 @@ ECPG: CopyStmtCOPYopt_binaryqualified_nameopt_column_listopt_oidscopy_fromcopy_f ECPG: CopyStmtCOPYselect_with_parensTOcopy_file_nameopt_withcopy_options addon if (strcmp($4, "stdin") == 0) mmerror(PARSE_ERROR, ET_ERROR, "COPY TO STDIN is not possible"); -ECPG: ConstraintAttributeSpecConstraintDeferrabilitySpecConstraintTimeSpec addon - if (strcmp($1, "deferrable") != 0 && strcmp($2, "initially deferrable") == 0 ) - mmerror(PARSE_ERROR, ET_ERROR, "constraint declared INITIALLY DEFERRED must be DEFERRABLE"); -ECPG: ConstraintAttributeSpecConstraintTimeSpecConstraintDeferrabilitySpec addon - if (strcmp($2, "deferrable") != 0 && strcmp($1, "initially deferrable") == 0 ) - mmerror(PARSE_ERROR, ET_ERROR, "constraint declared INITIALLY DEFERRED must be DEFERRABLE"); ECPG: var_valueNumericOnly addon if ($1[0] == '$') {