Skip to content

Commit ecfe59e

Browse files
committed
Refactor validation of new partitions a little bit.
Move some logic that is currently in ATExecAttachPartition to separate functions to facilitate future code reuse. Ashutosh Bapat and Jeevan Ladhe Discussion: http://postgr.es/m/CA+Tgmobbnamyvii0pRdg9pp_jLHSUvq7u5SiRrVV0tEFFU58Tg@mail.gmail.com
1 parent 1e56883 commit ecfe59e

File tree

1 file changed

+172
-146
lines changed

1 file changed

+172
-146
lines changed

src/backend/commands/tablecmds.c

+172-146
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,11 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
473473
static void RemoveInheritance(Relation child_rel, Relation parent_rel);
474474
static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
475475
PartitionCmd *cmd);
476+
static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
477+
List *partConstraint);
478+
static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
479+
List *scanrel_children,
480+
List *partConstraint);
476481
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
477482

478483

@@ -13424,6 +13429,169 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
1342413429
}
1342513430
}
1342613431

13432+
/*
13433+
* PartConstraintImpliedByRelConstraint
13434+
* Does scanrel's existing constraints imply the partition constraint?
13435+
*
13436+
* Existing constraints includes its check constraints and column-level
13437+
* NOT NULL constraints and partConstraint describes the partition constraint.
13438+
*/
13439+
static bool
13440+
PartConstraintImpliedByRelConstraint(Relation scanrel,
13441+
List *partConstraint)
13442+
{
13443+
List *existConstraint = NIL;
13444+
TupleConstr *constr = RelationGetDescr(scanrel)->constr;
13445+
int num_check,
13446+
i;
13447+
13448+
if (constr && constr->has_not_null)
13449+
{
13450+
int natts = scanrel->rd_att->natts;
13451+
13452+
for (i = 1; i <= natts; i++)
13453+
{
13454+
Form_pg_attribute att = scanrel->rd_att->attrs[i - 1];
13455+
13456+
if (att->attnotnull && !att->attisdropped)
13457+
{
13458+
NullTest *ntest = makeNode(NullTest);
13459+
13460+
ntest->arg = (Expr *) makeVar(1,
13461+
i,
13462+
att->atttypid,
13463+
att->atttypmod,
13464+
att->attcollation,
13465+
0);
13466+
ntest->nulltesttype = IS_NOT_NULL;
13467+
13468+
/*
13469+
* argisrow=false is correct even for a composite column,
13470+
* because attnotnull does not represent a SQL-spec IS NOT
13471+
* NULL test in such a case, just IS DISTINCT FROM NULL.
13472+
*/
13473+
ntest->argisrow = false;
13474+
ntest->location = -1;
13475+
existConstraint = lappend(existConstraint, ntest);
13476+
}
13477+
}
13478+
}
13479+
13480+
num_check = (constr != NULL) ? constr->num_check : 0;
13481+
for (i = 0; i < num_check; i++)
13482+
{
13483+
Node *cexpr;
13484+
13485+
/*
13486+
* If this constraint hasn't been fully validated yet, we must ignore
13487+
* it here.
13488+
*/
13489+
if (!constr->check[i].ccvalid)
13490+
continue;
13491+
13492+
cexpr = stringToNode(constr->check[i].ccbin);
13493+
13494+
/*
13495+
* Run each expression through const-simplification and
13496+
* canonicalization. It is necessary, because we will be comparing it
13497+
* to similarly-processed partition constraint expressions, and may
13498+
* fail to detect valid matches without this.
13499+
*/
13500+
cexpr = eval_const_expressions(NULL, cexpr);
13501+
cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
13502+
13503+
existConstraint = list_concat(existConstraint,
13504+
make_ands_implicit((Expr *) cexpr));
13505+
}
13506+
13507+
if (existConstraint != NIL)
13508+
existConstraint = list_make1(make_ands_explicit(existConstraint));
13509+
13510+
/* And away we go ... */
13511+
return predicate_implied_by(partConstraint, existConstraint, true);
13512+
}
13513+
13514+
/*
13515+
* ValidatePartitionConstraints
13516+
*
13517+
* Check whether all rows in the given table obey the given partition
13518+
* constraint; if so, it can be attached as a partition.  We do this by
13519+
* scanning the table (or all of its leaf partitions) row by row, except when
13520+
* the existing constraints are sufficient to prove that the new partitioning
13521+
* constraint must already hold.
13522+
*/
13523+
static void
13524+
ValidatePartitionConstraints(List **wqueue, Relation scanrel,
13525+
List *scanrel_children,
13526+
List *partConstraint)
13527+
{
13528+
bool found_whole_row;
13529+
ListCell *lc;
13530+
13531+
if (partConstraint == NIL)
13532+
return;
13533+
13534+
/*
13535+
* Based on the table's existing constraints, determine if we can skip
13536+
* scanning the table to validate the partition constraint.
13537+
*/
13538+
if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
13539+
{
13540+
ereport(INFO,
13541+
(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
13542+
RelationGetRelationName(scanrel))));
13543+
return;
13544+
}
13545+
13546+
/* Constraints proved insufficient, so we need to scan the table. */
13547+
foreach(lc, scanrel_children)
13548+
{
13549+
AlteredTableInfo *tab;
13550+
Oid part_relid = lfirst_oid(lc);
13551+
Relation part_rel;
13552+
List *my_partconstr = partConstraint;
13553+
13554+
/* Lock already taken */
13555+
if (part_relid != RelationGetRelid(scanrel))
13556+
part_rel = heap_open(part_relid, NoLock);
13557+
else
13558+
part_rel = scanrel;
13559+
13560+
/*
13561+
* Skip if the partition is itself a partitioned table. We can only
13562+
* ever scan RELKIND_RELATION relations.
13563+
*/
13564+
if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
13565+
{
13566+
if (part_rel != scanrel)
13567+
heap_close(part_rel, NoLock);
13568+
continue;
13569+
}
13570+
13571+
if (part_rel != scanrel)
13572+
{
13573+
/*
13574+
* Adjust the constraint for scanrel so that it matches this
13575+
* partition's attribute numbers.
13576+
*/
13577+
my_partconstr = map_partition_varattnos(my_partconstr, 1,
13578+
part_rel, scanrel,
13579+
&found_whole_row);
13580+
/* There can never be a whole-row reference here */
13581+
if (found_whole_row)
13582+
elog(ERROR, "unexpected whole-row reference found in partition key");
13583+
}
13584+
13585+
/* Grab a work queue entry. */
13586+
tab = ATGetQueueEntry(wqueue, part_rel);
13587+
tab->partition_constraint = (Expr *) linitial(my_partconstr);
13588+
13589+
/* keep our lock until commit */
13590+
if (part_rel != scanrel)
13591+
heap_close(part_rel, NoLock);
13592+
}
13593+
}
13594+
1342713595
/*
1342813596
* ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
1342913597
*
@@ -13435,15 +13603,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
1343513603
Relation attachrel,
1343613604
catalog;
1343713605
List *attachrel_children;
13438-
TupleConstr *attachrel_constr;
13439-
List *partConstraint,
13440-
*existConstraint;
13606+
List *partConstraint;
1344113607
SysScanDesc scan;
1344213608
ScanKeyData skey;
1344313609
AttrNumber attno;
1344413610
int natts;
1344513611
TupleDesc tupleDesc;
13446-
bool skip_validate = false;
1344713612
ObjectAddress address;
1344813613
const char *trigger_name;
1344913614
bool found_whole_row;
@@ -13637,148 +13802,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
1363713802
if (found_whole_row)
1363813803
elog(ERROR, "unexpected whole-row reference found in partition key");
1363913804

13640-
/*
13641-
* Check if we can do away with having to scan the table being attached to
13642-
* validate the partition constraint, by *proving* that the existing
13643-
* constraints of the table *imply* the partition predicate. We include
13644-
* the table's check constraints and NOT NULL constraints in the list of
13645-
* clauses passed to predicate_implied_by().
13646-
*
13647-
* There is a case in which we cannot rely on just the result of the
13648-
* proof.
13649-
*/
13650-
attachrel_constr = tupleDesc->constr;
13651-
existConstraint = NIL;
13652-
if (attachrel_constr != NULL)
13653-
{
13654-
int num_check = attachrel_constr->num_check;
13655-
int i;
13656-
13657-
if (attachrel_constr->has_not_null)
13658-
{
13659-
int natts = attachrel->rd_att->natts;
13660-
13661-
for (i = 1; i <= natts; i++)
13662-
{
13663-
Form_pg_attribute att = attachrel->rd_att->attrs[i - 1];
13664-
13665-
if (att->attnotnull && !att->attisdropped)
13666-
{
13667-
NullTest *ntest = makeNode(NullTest);
13668-
13669-
ntest->arg = (Expr *) makeVar(1,
13670-
i,
13671-
att->atttypid,
13672-
att->atttypmod,
13673-
att->attcollation,
13674-
0);
13675-
ntest->nulltesttype = IS_NOT_NULL;
13676-
13677-
/*
13678-
* argisrow=false is correct even for a composite column,
13679-
* because attnotnull does not represent a SQL-spec IS NOT
13680-
* NULL test in such a case, just IS DISTINCT FROM NULL.
13681-
*/
13682-
ntest->argisrow = false;
13683-
ntest->location = -1;
13684-
existConstraint = lappend(existConstraint, ntest);
13685-
}
13686-
}
13687-
}
13688-
13689-
for (i = 0; i < num_check; i++)
13690-
{
13691-
Node *cexpr;
13692-
13693-
/*
13694-
* If this constraint hasn't been fully validated yet, we must
13695-
* ignore it here.
13696-
*/
13697-
if (!attachrel_constr->check[i].ccvalid)
13698-
continue;
13699-
13700-
cexpr = stringToNode(attachrel_constr->check[i].ccbin);
13701-
13702-
/*
13703-
* Run each expression through const-simplification and
13704-
* canonicalization. It is necessary, because we will be
13705-
* comparing it to similarly-processed qual clauses, and may fail
13706-
* to detect valid matches without this.
13707-
*/
13708-
cexpr = eval_const_expressions(NULL, cexpr);
13709-
cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
13710-
13711-
existConstraint = list_concat(existConstraint,
13712-
make_ands_implicit((Expr *) cexpr));
13713-
}
13714-
13715-
existConstraint = list_make1(make_ands_explicit(existConstraint));
13716-
13717-
/* And away we go ... */
13718-
if (predicate_implied_by(partConstraint, existConstraint, true))
13719-
skip_validate = true;
13720-
}
13721-
13722-
if (skip_validate)
13723-
{
13724-
/* No need to scan the table after all. */
13725-
ereport(INFO,
13726-
(errmsg("partition constraint for table \"%s\" is implied by existing constraints",
13727-
RelationGetRelationName(attachrel))));
13728-
}
13729-
else
13730-
{
13731-
/* Constraints proved insufficient, so we need to scan the table. */
13732-
ListCell *lc;
13733-
13734-
foreach(lc, attachrel_children)
13735-
{
13736-
AlteredTableInfo *tab;
13737-
Oid part_relid = lfirst_oid(lc);
13738-
Relation part_rel;
13739-
List *my_partconstr = partConstraint;
13740-
13741-
/* Lock already taken */
13742-
if (part_relid != RelationGetRelid(attachrel))
13743-
part_rel = heap_open(part_relid, NoLock);
13744-
else
13745-
part_rel = attachrel;
13746-
13747-
/*
13748-
* Skip if the partition is itself a partitioned table. We can
13749-
* only ever scan RELKIND_RELATION relations.
13750-
*/
13751-
if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
13752-
{
13753-
if (part_rel != attachrel)
13754-
heap_close(part_rel, NoLock);
13755-
continue;
13756-
}
13757-
13758-
if (part_rel != attachrel)
13759-
{
13760-
/*
13761-
* Adjust the constraint that we constructed above for
13762-
* attachRel so that it matches this partition's attribute
13763-
* numbers.
13764-
*/
13765-
my_partconstr = map_partition_varattnos(my_partconstr, 1,
13766-
part_rel, attachrel,
13767-
&found_whole_row);
13768-
/* There can never be a whole-row reference here */
13769-
if (found_whole_row)
13770-
elog(ERROR, "unexpected whole-row reference found in partition key");
13771-
}
13772-
13773-
/* Grab a work queue entry. */
13774-
tab = ATGetQueueEntry(wqueue, part_rel);
13775-
tab->partition_constraint = (Expr *) linitial(my_partconstr);
13776-
13777-
/* keep our lock until commit */
13778-
if (part_rel != attachrel)
13779-
heap_close(part_rel, NoLock);
13780-
}
13781-
}
13805+
/* Validate partition constraints against the table being attached. */
13806+
ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
13807+
partConstraint);
1378213808

1378313809
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
1378413810

0 commit comments

Comments
 (0)