mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-27 08:39:28 +08:00
When adding a "target IS NOT NULL" indexqual to the plan for an index-optimized
MIN or MAX, we must take care to insert the added qual in a legal place among the existing indexquals, if any. The btree index AM requires the quals to appear in index-column order. We didn't have to worry about this before because "target IS NOT NULL" was just treated as a plain scan filter condition; but as of 9.0 it can be an index qual and then it has to follow the rule. Per report from Ian Barwick.
This commit is contained in:
parent
7fdbb8e353
commit
ed83f6e382
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.51 2010/02/14 18:42:15 rhaas Exp $
|
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.52 2010/05/10 16:25:46 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -50,6 +50,7 @@ static bool build_minmax_path(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info,
|
static ScanDirection match_agg_to_index_col(MinMaxAggInfo *info,
|
||||||
IndexOptInfo *index, int indexcol);
|
IndexOptInfo *index, int indexcol);
|
||||||
static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info);
|
static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info);
|
||||||
|
static void attach_notnull_index_qual(MinMaxAggInfo *info, IndexScan *iplan);
|
||||||
static Node *replace_aggs_with_params_mutator(Node *node, List **context);
|
static Node *replace_aggs_with_params_mutator(Node *node, List **context);
|
||||||
static Oid fetch_agg_sort_op(Oid aggfnoid);
|
static Oid fetch_agg_sort_op(Oid aggfnoid);
|
||||||
|
|
||||||
@ -537,9 +538,6 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
|
|||||||
* The NOT NULL qual has to go on the actual indexscan; create_plan might
|
* The NOT NULL qual has to go on the actual indexscan; create_plan might
|
||||||
* have stuck a gating Result atop that, if there were any pseudoconstant
|
* have stuck a gating Result atop that, if there were any pseudoconstant
|
||||||
* quals.
|
* quals.
|
||||||
*
|
|
||||||
* We can skip adding the NOT NULL qual if it duplicates either an
|
|
||||||
* already-given WHERE condition, or a clause of the index predicate.
|
|
||||||
*/
|
*/
|
||||||
plan = create_plan(&subroot, (Path *) info->path);
|
plan = create_plan(&subroot, (Path *) info->path);
|
||||||
|
|
||||||
@ -552,21 +550,7 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
|
|||||||
if (!IsA(iplan, IndexScan))
|
if (!IsA(iplan, IndexScan))
|
||||||
elog(ERROR, "result of create_plan(IndexPath) isn't an IndexScan");
|
elog(ERROR, "result of create_plan(IndexPath) isn't an IndexScan");
|
||||||
|
|
||||||
if (!list_member(iplan->indexqualorig, info->notnulltest) &&
|
attach_notnull_index_qual(info, iplan);
|
||||||
!list_member(info->path->indexinfo->indpred, info->notnulltest))
|
|
||||||
{
|
|
||||||
NullTest *ntest;
|
|
||||||
|
|
||||||
/* Need a "fixed" copy as well as the original */
|
|
||||||
ntest = copyObject(info->notnulltest);
|
|
||||||
ntest->arg = (Expr *) fix_indexqual_operand((Node *) ntest->arg,
|
|
||||||
info->path->indexinfo);
|
|
||||||
|
|
||||||
iplan->indexqual = lappend(iplan->indexqual,
|
|
||||||
ntest);
|
|
||||||
iplan->indexqualorig = lappend(iplan->indexqualorig,
|
|
||||||
info->notnulltest);
|
|
||||||
}
|
|
||||||
|
|
||||||
plan = (Plan *) make_limit(plan,
|
plan = (Plan *) make_limit(plan,
|
||||||
subparse->limitOffset,
|
subparse->limitOffset,
|
||||||
@ -586,6 +570,169 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info)
|
|||||||
root->init_plans = subroot.init_plans;
|
root->init_plans = subroot.init_plans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add "target IS NOT NULL" to the quals of the given indexscan.
|
||||||
|
*
|
||||||
|
* This is trickier than it sounds because the new qual has to be added at an
|
||||||
|
* appropriate place in the qual list, to preserve the list's ordering by
|
||||||
|
* index column position.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
attach_notnull_index_qual(MinMaxAggInfo *info, IndexScan *iplan)
|
||||||
|
{
|
||||||
|
NullTest *ntest;
|
||||||
|
List *newindexqual;
|
||||||
|
List *newindexqualorig;
|
||||||
|
bool done;
|
||||||
|
ListCell *lc1;
|
||||||
|
ListCell *lc2;
|
||||||
|
Expr *leftop;
|
||||||
|
AttrNumber targetattno;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can skip adding the NOT NULL qual if it duplicates either an
|
||||||
|
* already-given WHERE condition, or a clause of the index predicate.
|
||||||
|
*/
|
||||||
|
if (list_member(iplan->indexqualorig, info->notnulltest) ||
|
||||||
|
list_member(info->path->indexinfo->indpred, info->notnulltest))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Need a "fixed" copy as well as the original */
|
||||||
|
ntest = copyObject(info->notnulltest);
|
||||||
|
ntest->arg = (Expr *) fix_indexqual_operand((Node *) ntest->arg,
|
||||||
|
info->path->indexinfo);
|
||||||
|
|
||||||
|
/* Identify the target index column from the "fixed" copy */
|
||||||
|
leftop = ntest->arg;
|
||||||
|
|
||||||
|
if (leftop && IsA(leftop, RelabelType))
|
||||||
|
leftop = ((RelabelType *) leftop)->arg;
|
||||||
|
|
||||||
|
Assert(leftop != NULL);
|
||||||
|
|
||||||
|
if (!IsA(leftop, Var))
|
||||||
|
elog(ERROR, "NullTest indexqual has wrong key");
|
||||||
|
|
||||||
|
targetattno = ((Var *) leftop)->varattno;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* list.c doesn't expose a primitive to insert a list cell at an arbitrary
|
||||||
|
* position, so our strategy is to copy the lists and insert the null test
|
||||||
|
* when we reach an appropriate spot.
|
||||||
|
*/
|
||||||
|
newindexqual = newindexqualorig = NIL;
|
||||||
|
done = false;
|
||||||
|
|
||||||
|
forboth(lc1, iplan->indexqual, lc2, iplan->indexqualorig)
|
||||||
|
{
|
||||||
|
Expr *qual = (Expr *) lfirst(lc1);
|
||||||
|
Expr *qualorig = (Expr *) lfirst(lc2);
|
||||||
|
AttrNumber varattno;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Identify which index column this qual is for. This code should
|
||||||
|
* match the qual disassembly code in ExecIndexBuildScanKeys.
|
||||||
|
*/
|
||||||
|
if (IsA(qual, OpExpr))
|
||||||
|
{
|
||||||
|
/* indexkey op expression */
|
||||||
|
leftop = (Expr *) get_leftop(qual);
|
||||||
|
|
||||||
|
if (leftop && IsA(leftop, RelabelType))
|
||||||
|
leftop = ((RelabelType *) leftop)->arg;
|
||||||
|
|
||||||
|
Assert(leftop != NULL);
|
||||||
|
|
||||||
|
if (!IsA(leftop, Var))
|
||||||
|
elog(ERROR, "indexqual doesn't have key on left side");
|
||||||
|
|
||||||
|
varattno = ((Var *) leftop)->varattno;
|
||||||
|
}
|
||||||
|
else if (IsA(qual, RowCompareExpr))
|
||||||
|
{
|
||||||
|
/* (indexkey, indexkey, ...) op (expression, expression, ...) */
|
||||||
|
RowCompareExpr *rc = (RowCompareExpr *) qual;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Examine just the first column of the rowcompare, which is
|
||||||
|
* what determines its placement in the overall qual list.
|
||||||
|
*/
|
||||||
|
leftop = (Expr *) linitial(rc->largs);
|
||||||
|
|
||||||
|
if (leftop && IsA(leftop, RelabelType))
|
||||||
|
leftop = ((RelabelType *) leftop)->arg;
|
||||||
|
|
||||||
|
Assert(leftop != NULL);
|
||||||
|
|
||||||
|
if (!IsA(leftop, Var))
|
||||||
|
elog(ERROR, "indexqual doesn't have key on left side");
|
||||||
|
|
||||||
|
varattno = ((Var *) leftop)->varattno;
|
||||||
|
}
|
||||||
|
else if (IsA(qual, ScalarArrayOpExpr))
|
||||||
|
{
|
||||||
|
/* indexkey op ANY (array-expression) */
|
||||||
|
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual;
|
||||||
|
|
||||||
|
leftop = (Expr *) linitial(saop->args);
|
||||||
|
|
||||||
|
if (leftop && IsA(leftop, RelabelType))
|
||||||
|
leftop = ((RelabelType *) leftop)->arg;
|
||||||
|
|
||||||
|
Assert(leftop != NULL);
|
||||||
|
|
||||||
|
if (!IsA(leftop, Var))
|
||||||
|
elog(ERROR, "indexqual doesn't have key on left side");
|
||||||
|
|
||||||
|
varattno = ((Var *) leftop)->varattno;
|
||||||
|
}
|
||||||
|
else if (IsA(qual, NullTest))
|
||||||
|
{
|
||||||
|
/* indexkey IS NULL or indexkey IS NOT NULL */
|
||||||
|
NullTest *ntest = (NullTest *) qual;
|
||||||
|
|
||||||
|
leftop = ntest->arg;
|
||||||
|
|
||||||
|
if (leftop && IsA(leftop, RelabelType))
|
||||||
|
leftop = ((RelabelType *) leftop)->arg;
|
||||||
|
|
||||||
|
Assert(leftop != NULL);
|
||||||
|
|
||||||
|
if (!IsA(leftop, Var))
|
||||||
|
elog(ERROR, "NullTest indexqual has wrong key");
|
||||||
|
|
||||||
|
varattno = ((Var *) leftop)->varattno;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elog(ERROR, "unsupported indexqual type: %d",
|
||||||
|
(int) nodeTag(qual));
|
||||||
|
varattno = 0; /* keep compiler quiet */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Insert the null test at the first place it can legally go */
|
||||||
|
if (!done && targetattno <= varattno)
|
||||||
|
{
|
||||||
|
newindexqual = lappend(newindexqual, ntest);
|
||||||
|
newindexqualorig = lappend(newindexqualorig, info->notnulltest);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newindexqual = lappend(newindexqual, qual);
|
||||||
|
newindexqualorig = lappend(newindexqualorig, qualorig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add the null test at the end if it must follow all existing quals */
|
||||||
|
if (!done)
|
||||||
|
{
|
||||||
|
newindexqual = lappend(newindexqual, ntest);
|
||||||
|
newindexqualorig = lappend(newindexqualorig, info->notnulltest);
|
||||||
|
}
|
||||||
|
|
||||||
|
iplan->indexqual = newindexqual;
|
||||||
|
iplan->indexqualorig = newindexqualorig;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Replace original aggregate calls with subplan output Params
|
* Replace original aggregate calls with subplan output Params
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user