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] == '$')
{