Commit 091e22b2 authored by Tom Lane's avatar Tom Lane

Clean up treatment of missing default and CHECK-constraint records.

Andrew Gierth reported that it's possible to crash the backend if no
pg_attrdef record is found to match an attribute that has atthasdef set.
AttrDefaultFetch warns about this situation, but then leaves behind
a relation tupdesc that has null "adbin" pointer(s), which most places
don't guard against.

We considered promoting the warning to an error, but throwing errors
during relcache load is pretty drastic: it effectively locks one out
of using the relation at all.  What seems better is to leave the
load-time behavior as a warning, but then throw an error in any code
path that wants to use a default and can't find it.  This confines
the error to a subset of INSERT/UPDATE operations on the table, and
in particular will at least allow a pg_dump to succeed.

Also, we should fix AttrDefaultFetch to not leave any null pointers
in the tupdesc, because that just creates an untested bug hazard.

While at it, apply the same philosophy of "warn at load, throw error
only upon use of the known-missing info" to CHECK constraints.
CheckConstraintFetch is very nearly the same logic as AttrDefaultFetch,
but for reasons lost in the mists of time, it was throwing ERROR for
the same cases that AttrDefaultFetch treats as WARNING.  Make the two
functions more nearly alike.

In passing, get rid of potentially-O(N^2) loops in equalTupleDesc
by making AttrDefaultFetch sort the entries after fetching them,
so that equalTupleDesc can assume that entries in two equal tupdescs
must be in matching order.  (CheckConstraintFetch already was sorting
CHECK constraints, but equalTupleDesc hadn't been told about it.)

There's some argument for back-patching this, but with such a small
number of field reports, I'm content to fix it in HEAD.

Discussion: https://postgr.es/m/87pmzaq4gx.fsf@news-spur.riddles.org.uk
parent 9de9294b
......@@ -174,10 +174,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
cpy->defval = (AttrDefault *) palloc(cpy->num_defval * sizeof(AttrDefault));
memcpy(cpy->defval, constr->defval, cpy->num_defval * sizeof(AttrDefault));
for (i = cpy->num_defval - 1; i >= 0; i--)
{
if (constr->defval[i].adbin)
cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin);
}
cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin);
}
if (constr->missing)
......@@ -203,10 +200,8 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
memcpy(cpy->check, constr->check, cpy->num_check * sizeof(ConstrCheck));
for (i = cpy->num_check - 1; i >= 0; i--)
{
if (constr->check[i].ccname)
cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
if (constr->check[i].ccbin)
cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
cpy->check[i].ccvalid = constr->check[i].ccvalid;
cpy->check[i].ccnoinherit = constr->check[i].ccnoinherit;
}
......@@ -328,10 +323,7 @@ FreeTupleDesc(TupleDesc tupdesc)
AttrDefault *attrdef = tupdesc->constr->defval;
for (i = tupdesc->constr->num_defval - 1; i >= 0; i--)
{
if (attrdef[i].adbin)
pfree(attrdef[i].adbin);
}
pfree(attrdef[i].adbin);
pfree(attrdef);
}
if (tupdesc->constr->missing)
......@@ -352,10 +344,8 @@ FreeTupleDesc(TupleDesc tupdesc)
for (i = tupdesc->constr->num_check - 1; i >= 0; i--)
{
if (check[i].ccname)
pfree(check[i].ccname);
if (check[i].ccbin)
pfree(check[i].ccbin);
pfree(check[i].ccname);
pfree(check[i].ccbin);
}
pfree(check);
}
......@@ -412,7 +402,6 @@ bool
equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
{
int i,
j,
n;
if (tupdesc1->natts != tupdesc2->natts)
......@@ -488,22 +477,13 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
n = constr1->num_defval;
if (n != (int) constr2->num_defval)
return false;
/* We assume here that both AttrDefault arrays are in adnum order */
for (i = 0; i < n; i++)
{
AttrDefault *defval1 = constr1->defval + i;
AttrDefault *defval2 = constr2->defval;
/*
* We can't assume that the items are always read from the system
* catalogs in the same order; so use the adnum field to identify
* the matching item to compare.
*/
for (j = 0; j < n; defval2++, j++)
{
if (defval1->adnum == defval2->adnum)
break;
}
if (j >= n)
AttrDefault *defval2 = constr2->defval + i;
if (defval1->adnum != defval2->adnum)
return false;
if (strcmp(defval1->adbin, defval2->adbin) != 0)
return false;
......@@ -534,25 +514,21 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
n = constr1->num_check;
if (n != (int) constr2->num_check)
return false;
/*
* Similarly, we rely here on the ConstrCheck entries being sorted by
* name. If there are duplicate names, the outcome of the comparison
* is uncertain, but that should not happen.
*/
for (i = 0; i < n; i++)
{
ConstrCheck *check1 = constr1->check + i;
ConstrCheck *check2 = constr2->check;
/*
* Similarly, don't assume that the checks are always read in the
* same order; match them up by name and contents. (The name
* *should* be unique, but...)
*/
for (j = 0; j < n; check2++, j++)
{
if (strcmp(check1->ccname, check2->ccname) == 0 &&
strcmp(check1->ccbin, check2->ccbin) == 0 &&
check1->ccvalid == check2->ccvalid &&
check1->ccnoinherit == check2->ccnoinherit)
break;
}
if (j >= n)
ConstrCheck *check2 = constr2->check + i;
if (!(strcmp(check1->ccname, check2->ccname) == 0 &&
strcmp(check1->ccbin, check2->ccbin) == 0 &&
check1->ccvalid == check2->ccvalid &&
check1->ccnoinherit == check2->ccnoinherit))
return false;
}
}
......
......@@ -2501,21 +2501,24 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
if (attribute->atthasdef)
{
Node *this_default = NULL;
AttrDefault *attrdef;
int i;
/* Find default in constraint structure */
Assert(constr != NULL);
attrdef = constr->defval;
for (i = 0; i < constr->num_defval; i++)
if (constr != NULL)
{
if (attrdef[i].adnum == parent_attno)
AttrDefault *attrdef = constr->defval;
for (int i = 0; i < constr->num_defval; i++)
{
this_default = stringToNode(attrdef[i].adbin);
break;
if (attrdef[i].adnum == parent_attno)
{
this_default = stringToNode(attrdef[i].adbin);
break;
}
}
}
Assert(this_default != NULL);
if (this_default == NULL)
elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
parent_attno, RelationGetRelationName(relation));
/*
* If it's a GENERATED default, it might contain Vars that
......
......@@ -1609,6 +1609,15 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
MemoryContext oldContext;
int i;
/*
* CheckConstraintFetch let this pass with only a warning, but now we
* should fail rather than possibly failing to enforce an important
* constraint.
*/
if (ncheck != rel->rd_rel->relchecks)
elog(ERROR, "%d pg_constraint record(s) missing for relation \"%s\"",
rel->rd_rel->relchecks - ncheck, RelationGetRelationName(rel));
/*
* If first time through for this result relation, build expression
* nodetrees for rel's constraint expressions. Keep them in the per-query
......@@ -1862,7 +1871,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
if (constr->num_check > 0)
if (rel->rd_rel->relchecks > 0)
{
const char *failed;
......
......@@ -1239,8 +1239,6 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
(CREATE_TABLE_LIKE_DEFAULTS | CREATE_TABLE_LIKE_GENERATED)) &&
constr != NULL)
{
AttrDefault *attrdef = constr->defval;
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
parent_attno++)
{
......@@ -1264,6 +1262,7 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
(table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)))
{
Node *this_default = NULL;
AttrDefault *attrdef = constr->defval;
AlterTableCmd *atsubcmd;
bool found_whole_row;
......@@ -1276,7 +1275,9 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
break;
}
}
Assert(this_default != NULL);
if (this_default == NULL)
elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
parent_attno, RelationGetRelationName(relation));
atsubcmd = makeNode(AlterTableCmd);
atsubcmd->subtype = AT_CookedColumnDefault;
......
......@@ -1228,25 +1228,28 @@ build_column_default(Relation rel, int attrno)
}
/*
* Scan to see if relation has a default for this column.
* If relation has a default for this column, fetch that expression.
*/
if (att_tup->atthasdef && rd_att->constr &&
rd_att->constr->num_defval > 0)
if (att_tup->atthasdef)
{
AttrDefault *defval = rd_att->constr->defval;
int ndef = rd_att->constr->num_defval;
while (--ndef >= 0)
if (rd_att->constr && rd_att->constr->num_defval > 0)
{
if (attrno == defval[ndef].adnum)
AttrDefault *defval = rd_att->constr->defval;
int ndef = rd_att->constr->num_defval;
while (--ndef >= 0)
{
/*
* Found it, convert string representation to node tree.
*/
expr = stringToNode(defval[ndef].adbin);
break;
if (attrno == defval[ndef].adnum)
{
/* Found it, convert string representation to node tree. */
expr = stringToNode(defval[ndef].adbin);
break;
}
}
}
if (expr == NULL)
elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
attrno, RelationGetRelationName(rel));
}
/*
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment