Commit b08df9ca authored by Robert Haas's avatar Robert Haas

Teach predtest.c about CHECK clauses to fix partitioning bugs.

In a CHECK clause, a null result means true, whereas in a WHERE clause
it means false.  predtest.c provided different functions depending on
which set of semantics applied to the predicate being proved, but had
no option to control what a null meant in the clauses provided as
axioms.  Add one.

Use that in the partitioning code when figuring out whether the
validation scan on a new partition can be skipped.  Rip out the
old logic that attempted (not very successfully) to compensate
for the absence of the necessary support in predtest.c.

Ashutosh Bapat and Robert Haas, reviewed by Amit Langote and
incorporating feedback from Tom Lane.

Discussion: http://postgr.es/m/CAFjFpReT_kq_uwU_B8aWDxR7jNGE=P0iELycdq5oupi=xSQTOw@mail.gmail.com
parent a12c09ad
......@@ -13410,7 +13410,6 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
static ObjectAddress
ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
{
PartitionKey key = RelationGetPartitionKey(rel);
Relation attachRel,
catalog;
List *childrels;
......@@ -13596,11 +13595,6 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
{
int num_check = attachRel_constr->num_check;
int i;
Bitmapset *not_null_attrs = NULL;
List *part_constr;
ListCell *lc;
bool partition_accepts_null = true;
int partnatts;
if (attachRel_constr->has_not_null)
{
......@@ -13630,7 +13624,6 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
ntest->argisrow = false;
ntest->location = -1;
existConstraint = lappend(existConstraint, ntest);
not_null_attrs = bms_add_member(not_null_attrs, i);
}
}
}
......@@ -13664,59 +13657,8 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
existConstraint = list_make1(make_ands_explicit(existConstraint));
/* And away we go ... */
if (predicate_implied_by(partConstraint, existConstraint))
if (predicate_implied_by(partConstraint, existConstraint, true))
skip_validate = true;
/*
* We choose to err on the safer side, i.e., give up on skipping the
* validation scan, if the partition key column doesn't have the NOT
* NULL constraint and the table is to become a list partition that
* does not accept nulls. In this case, the partition predicate
* (partConstraint) does include an 'key IS NOT NULL' expression,
* however, because of the way predicate_implied_by_simple_clause() is
* designed to handle IS NOT NULL predicates in the absence of a IS
* NOT NULL clause, we cannot rely on just the above proof.
*
* That is not an issue in case of a range partition, because if there
* were no NOT NULL constraint defined on the key columns, an error
* would be thrown before we get here anyway. That is not true,
* however, if any of the partition keys is an expression, which is
* handled below.
*/
part_constr = linitial(partConstraint);
part_constr = make_ands_implicit((Expr *) part_constr);
/*
* part_constr contains an IS NOT NULL expression, if this is a list
* partition that does not accept nulls (in fact, also if this is a
* range partition and some partition key is an expression, but we
* never skip validation in that case anyway; see below)
*/
foreach(lc, part_constr)
{
Node *expr = lfirst(lc);
if (IsA(expr, NullTest) &&
((NullTest *) expr)->nulltesttype == IS_NOT_NULL)
{
partition_accepts_null = false;
break;
}
}
partnatts = get_partition_natts(key);
for (i = 0; i < partnatts; i++)
{
AttrNumber partattno;
partattno = get_partition_col_attnum(key, i);
/* If partition key is an expression, must not skip validation */
if (!partition_accepts_null &&
(partattno == 0 ||
!bms_is_member(partattno, not_null_attrs)))
skip_validate = false;
}
}
/* It's safe to skip the validation scan after all */
......
......@@ -1210,10 +1210,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
all_clauses = list_concat(list_copy(clauses),
other_clauses);
if (!predicate_implied_by(index->indpred, all_clauses))
if (!predicate_implied_by(index->indpred, all_clauses, false))
continue; /* can't use it at all */
if (!predicate_implied_by(index->indpred, other_clauses))
if (!predicate_implied_by(index->indpred, other_clauses, false))
useful_predicate = true;
}
}
......@@ -1519,7 +1519,7 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths)
{
Node *np = (Node *) lfirst(l);
if (predicate_implied_by(list_make1(np), qualsofar))
if (predicate_implied_by(list_make1(np), qualsofar, false))
{
redundant = true;
break; /* out of inner foreach loop */
......@@ -2871,7 +2871,8 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
continue; /* ignore non-partial indexes here */
if (!index->predOK) /* don't repeat work if already proven OK */
index->predOK = predicate_implied_by(index->indpred, clauselist);
index->predOK = predicate_implied_by(index->indpred, clauselist,
false);
/* If rel is an update target, leave indrestrictinfo as set above */
if (is_target_rel)
......@@ -2886,7 +2887,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
/* predicate_implied_by() assumes first arg is immutable */
if (contain_mutable_functions((Node *) rinfo->clause) ||
!predicate_implied_by(list_make1(rinfo->clause),
index->indpred))
index->indpred, false))
index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
}
}
......
......@@ -2576,7 +2576,7 @@ create_indexscan_plan(PlannerInfo *root,
if (is_redundant_derived_clause(rinfo, indexquals))
continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions((Node *) rinfo->clause) &&
predicate_implied_by(list_make1(rinfo->clause), indexquals))
predicate_implied_by(list_make1(rinfo->clause), indexquals, false))
continue; /* provably implied by indexquals */
qpqual = lappend(qpqual, rinfo);
}
......@@ -2737,7 +2737,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec))
continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions(clause) &&
predicate_implied_by(list_make1(clause), indexquals))
predicate_implied_by(list_make1(clause), indexquals, false))
continue; /* provably implied by indexquals */
qpqual = lappend(qpqual, rinfo);
}
......@@ -2968,7 +2968,8 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
* the conditions that got pushed into the bitmapqual. Avoid
* generating redundant conditions.
*/
if (!predicate_implied_by(list_make1(pred), ipath->indexclauses))
if (!predicate_implied_by(list_make1(pred), ipath->indexclauses,
false))
{
*qual = lappend(*qual, pred);
*indexqual = lappend(*indexqual, pred);
......
......@@ -776,7 +776,7 @@ infer_arbiter_indexes(PlannerInfo *root)
*/
predExprs = RelationGetIndexPredicate(idxRel);
if (!predicate_implied_by(predExprs, (List *) onconflict->arbiterWhere))
if (!predicate_implied_by(predExprs, (List *) onconflict->arbiterWhere, false))
goto next;
results = lappend_oid(results, idxForm->indexrelid);
......@@ -1399,7 +1399,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
safe_restrictions = lappend(safe_restrictions, rinfo->clause);
}
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
if (predicate_refuted_by(safe_restrictions, safe_restrictions, false))
return true;
/* Only plain relations have constraints */
......@@ -1438,7 +1438,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
* have volatile and nonvolatile subclauses, and it's OK to make
* deductions with the nonvolatile parts.
*/
if (predicate_refuted_by(safe_constraints, rel->baserestrictinfo))
if (predicate_refuted_by(safe_constraints, rel->baserestrictinfo, false))
return true;
return false;
......
This diff is collapsed.
......@@ -6671,7 +6671,7 @@ add_predicate_to_quals(IndexOptInfo *index, List *indexQuals)
Node *predQual = (Node *) lfirst(lc);
List *oneQual = list_make1(predQual);
if (!predicate_implied_by(oneQual, indexQuals))
if (!predicate_implied_by(oneQual, indexQuals, false))
predExtraQuals = list_concat(predExtraQuals, oneQual);
}
/* list_concat avoids modifying the passed-in indexQuals list */
......@@ -7556,7 +7556,7 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
Node *predQual = (Node *) lfirst(l);
List *oneQual = list_make1(predQual);
if (!predicate_implied_by(oneQual, indexQuals))
if (!predicate_implied_by(oneQual, indexQuals, false))
predExtraQuals = list_concat(predExtraQuals, oneQual);
}
/* list_concat avoids modifying the passed-in indexQuals list */
......
......@@ -17,9 +17,9 @@
#include "nodes/primnodes.h"
extern bool predicate_implied_by(List *predicate_list,
List *restrictinfo_list);
extern bool predicate_refuted_by(List *predicate_list,
List *restrictinfo_list);
extern bool predicate_implied_by(List *predicate_list, List *clause_list,
bool clause_is_check);
extern bool predicate_refuted_by(List *predicate_list, List *clause_list,
bool clause_is_check);
#endif /* PREDTEST_H */
......@@ -3338,6 +3338,12 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
ERROR: partition constraint is violated by some row
-- delete the faulting row and also add a constraint to skip the scan
DELETE FROM part_5_a WHERE a NOT IN (3);
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5);
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
INFO: partition constraint for table "part_5" is implied by existing constraints
ALTER TABLE list_parted2 DETACH PARTITION part_5;
ALTER TABLE part_5 DROP CONSTRAINT check_a;
-- scan should again be skipped, even though NOT NULL is now a column property
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
INFO: partition constraint for table "part_5" is implied by existing constraints
......
......@@ -2169,9 +2169,14 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-- delete the faulting row and also add a constraint to skip the scan
DELETE FROM part_5_a WHERE a NOT IN (3);
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 5);
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
ALTER TABLE list_parted2 DETACH PARTITION part_5;
ALTER TABLE part_5 DROP CONSTRAINT check_a;
-- scan should again be skipped, even though NOT NULL is now a column property
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-- check that the table being attached is not already a partition
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
......
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