@@ -473,6 +473,11 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
473
473
static void RemoveInheritance (Relation child_rel , Relation parent_rel );
474
474
static ObjectAddress ATExecAttachPartition (List * * wqueue , Relation rel ,
475
475
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 );
476
481
static ObjectAddress ATExecDetachPartition (Relation rel , RangeVar * name );
477
482
478
483
@@ -13424,6 +13429,169 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
13424
13429
}
13425
13430
}
13426
13431
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
+
13427
13595
/*
13428
13596
* ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
13429
13597
*
@@ -13435,15 +13603,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
13435
13603
Relation attachrel ,
13436
13604
catalog ;
13437
13605
List * attachrel_children ;
13438
- TupleConstr * attachrel_constr ;
13439
- List * partConstraint ,
13440
- * existConstraint ;
13606
+ List * partConstraint ;
13441
13607
SysScanDesc scan ;
13442
13608
ScanKeyData skey ;
13443
13609
AttrNumber attno ;
13444
13610
int natts ;
13445
13611
TupleDesc tupleDesc ;
13446
- bool skip_validate = false;
13447
13612
ObjectAddress address ;
13448
13613
const char * trigger_name ;
13449
13614
bool found_whole_row ;
@@ -13637,148 +13802,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
13637
13802
if (found_whole_row )
13638
13803
elog (ERROR , "unexpected whole-row reference found in partition key" );
13639
13804
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 );
13782
13808
13783
13809
ObjectAddressSet (address , RelationRelationId , RelationGetRelid (attachrel ));
13784
13810
0 commit comments