@@ -473,6 +473,11 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
473473static void RemoveInheritance (Relation child_rel , Relation parent_rel );
474474static 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 );
476481static 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