mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Allow row comparisons to be used as indexscan qualifications.
This completes the project to upgrade our handling of row comparisons.
This commit is contained in:
@ -9,7 +9,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.196 2005/12/06 16:50:36 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.197 2006/01/25 20:29:23 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -61,6 +61,11 @@ static bool match_clause_to_indexcol(IndexOptInfo *index,
|
||||
SaOpControl saop_control);
|
||||
static bool is_indexable_operator(Oid expr_op, Oid opclass,
|
||||
bool indexkey_on_left);
|
||||
static bool match_rowcompare_to_indexcol(IndexOptInfo *index,
|
||||
int indexcol,
|
||||
Oid opclass,
|
||||
RowCompareExpr *clause,
|
||||
Relids outer_relids);
|
||||
static Relids indexable_outerrelids(RelOptInfo *rel);
|
||||
static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel,
|
||||
Relids outer_relids);
|
||||
@ -82,7 +87,10 @@ static bool match_special_index_operator(Expr *clause, Oid opclass,
|
||||
bool indexkey_on_left);
|
||||
static Expr *expand_boolean_index_clause(Node *clause, int indexcol,
|
||||
IndexOptInfo *index);
|
||||
static List *expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass);
|
||||
static List *expand_indexqual_opclause(RestrictInfo *rinfo, Oid opclass);
|
||||
static RestrictInfo *expand_indexqual_rowcompare(RestrictInfo *rinfo,
|
||||
IndexOptInfo *index,
|
||||
int indexcol);
|
||||
static List *prefix_quals(Node *leftop, Oid opclass,
|
||||
Const *prefix, Pattern_Prefix_Status pstatus);
|
||||
static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass,
|
||||
@ -900,6 +908,14 @@ group_clauses_by_indexkey(IndexOptInfo *index,
|
||||
* We do not actually do the commuting here, but we check whether a
|
||||
* suitable commutator operator is available.
|
||||
*
|
||||
* It is also possible to match RowCompareExpr clauses to indexes (but
|
||||
* currently, only btree indexes handle this). In this routine we will
|
||||
* report a match if the first column of the row comparison matches the
|
||||
* target index column. This is sufficient to guarantee that some index
|
||||
* condition can be constructed from the RowCompareExpr --- whether the
|
||||
* remaining columns match the index too is considered in
|
||||
* expand_indexqual_rowcompare().
|
||||
*
|
||||
* It is also possible to match ScalarArrayOpExpr clauses to indexes, when
|
||||
* the clause is of the form "indexkey op ANY (arrayconst)". Since the
|
||||
* executor can only handle these in the context of bitmap index scans,
|
||||
@ -944,7 +960,8 @@ match_clause_to_indexcol(IndexOptInfo *index,
|
||||
|
||||
/*
|
||||
* Clause must be a binary opclause, or possibly a ScalarArrayOpExpr
|
||||
* (which is always binary, by definition).
|
||||
* (which is always binary, by definition). Or it could be a
|
||||
* RowCompareExpr, which we pass off to match_rowcompare_to_indexcol().
|
||||
*/
|
||||
if (is_opclause(clause))
|
||||
{
|
||||
@ -972,6 +989,12 @@ match_clause_to_indexcol(IndexOptInfo *index,
|
||||
expr_op = saop->opno;
|
||||
plain_op = false;
|
||||
}
|
||||
else if (clause && IsA(clause, RowCompareExpr))
|
||||
{
|
||||
return match_rowcompare_to_indexcol(index, indexcol, opclass,
|
||||
(RowCompareExpr *) clause,
|
||||
outer_relids);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
@ -1039,6 +1062,74 @@ is_indexable_operator(Oid expr_op, Oid opclass, bool indexkey_on_left)
|
||||
return op_in_opclass(expr_op, opclass);
|
||||
}
|
||||
|
||||
/*
|
||||
* match_rowcompare_to_indexcol()
|
||||
* Handles the RowCompareExpr case for match_clause_to_indexcol(),
|
||||
* which see for comments.
|
||||
*/
|
||||
static bool
|
||||
match_rowcompare_to_indexcol(IndexOptInfo *index,
|
||||
int indexcol,
|
||||
Oid opclass,
|
||||
RowCompareExpr *clause,
|
||||
Relids outer_relids)
|
||||
{
|
||||
Node *leftop,
|
||||
*rightop;
|
||||
Oid expr_op;
|
||||
|
||||
/* Forget it if we're not dealing with a btree index */
|
||||
if (index->relam != BTREE_AM_OID)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* We could do the matching on the basis of insisting that the opclass
|
||||
* shown in the RowCompareExpr be the same as the index column's opclass,
|
||||
* but that does not work well for cross-type comparisons (the opclass
|
||||
* could be for the other datatype). Also it would fail to handle indexes
|
||||
* using reverse-sort opclasses. Instead, match if the operator listed in
|
||||
* the RowCompareExpr is the < <= > or >= member of the index opclass
|
||||
* (after commutation, if the indexkey is on the right).
|
||||
*/
|
||||
leftop = (Node *) linitial(clause->largs);
|
||||
rightop = (Node *) linitial(clause->rargs);
|
||||
expr_op = linitial_oid(clause->opnos);
|
||||
|
||||
/*
|
||||
* These syntactic tests are the same as in match_clause_to_indexcol()
|
||||
*/
|
||||
if (match_index_to_operand(leftop, indexcol, index) &&
|
||||
bms_is_subset(pull_varnos(rightop), outer_relids) &&
|
||||
!contain_volatile_functions(rightop))
|
||||
{
|
||||
/* OK, indexkey is on left */
|
||||
}
|
||||
else if (match_index_to_operand(rightop, indexcol, index) &&
|
||||
bms_is_subset(pull_varnos(leftop), outer_relids) &&
|
||||
!contain_volatile_functions(leftop))
|
||||
{
|
||||
/* indexkey is on right, so commute the operator */
|
||||
expr_op = get_commutator(expr_op);
|
||||
if (expr_op == InvalidOid)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
/* We're good if the operator is the right type of opclass member */
|
||||
switch (get_op_opclass_strategy(expr_op, opclass))
|
||||
{
|
||||
case BTLessStrategyNumber:
|
||||
case BTLessEqualStrategyNumber:
|
||||
case BTGreaterEqualStrategyNumber:
|
||||
case BTGreaterStrategyNumber:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ----
|
||||
****************************************************************************/
|
||||
@ -2014,7 +2105,8 @@ match_special_index_operator(Expr *clause, Oid opclass,
|
||||
* of index qual clauses. Standard qual clauses (those in the index's
|
||||
* opclass) are passed through unchanged. Boolean clauses and "special"
|
||||
* index operators are expanded into clauses that the indexscan machinery
|
||||
* will know what to do with.
|
||||
* will know what to do with. RowCompare clauses are simplified if
|
||||
* necessary to create a clause that is fully checkable by the index.
|
||||
*
|
||||
* The input list is ordered by index key, and so the output list is too.
|
||||
* (The latter is not depended on by any part of the core planner, I believe,
|
||||
@ -2041,13 +2133,14 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
|
||||
foreach(l, (List *) lfirst(clausegroup_item))
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
|
||||
Expr *clause = rinfo->clause;
|
||||
|
||||
/* First check for boolean cases */
|
||||
if (IsBooleanOpclass(curClass))
|
||||
{
|
||||
Expr *boolqual;
|
||||
|
||||
boolqual = expand_boolean_index_clause((Node *) rinfo->clause,
|
||||
boolqual = expand_boolean_index_clause((Node *) clause,
|
||||
indexcol,
|
||||
index);
|
||||
if (boolqual)
|
||||
@ -2061,16 +2154,31 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
|
||||
}
|
||||
}
|
||||
|
||||
/* Next check for ScalarArrayOp cases */
|
||||
if (IsA(rinfo->clause, ScalarArrayOpExpr))
|
||||
/*
|
||||
* Else it must be an opclause (usual case), ScalarArrayOp, or
|
||||
* RowCompare
|
||||
*/
|
||||
if (is_opclause(clause))
|
||||
{
|
||||
resultquals = lappend(resultquals, rinfo);
|
||||
continue;
|
||||
resultquals = list_concat(resultquals,
|
||||
expand_indexqual_opclause(rinfo,
|
||||
curClass));
|
||||
}
|
||||
|
||||
resultquals = list_concat(resultquals,
|
||||
expand_indexqual_condition(rinfo,
|
||||
curClass));
|
||||
else if (IsA(clause, ScalarArrayOpExpr))
|
||||
{
|
||||
/* no extra work at this time */
|
||||
resultquals = lappend(resultquals, rinfo);
|
||||
}
|
||||
else if (IsA(clause, RowCompareExpr))
|
||||
{
|
||||
resultquals = lappend(resultquals,
|
||||
expand_indexqual_rowcompare(rinfo,
|
||||
index,
|
||||
indexcol));
|
||||
}
|
||||
else
|
||||
elog(ERROR, "unsupported indexqual type: %d",
|
||||
(int) nodeTag(clause));
|
||||
}
|
||||
|
||||
clausegroup_item = lnext(clausegroup_item);
|
||||
@ -2145,16 +2253,15 @@ expand_boolean_index_clause(Node *clause,
|
||||
}
|
||||
|
||||
/*
|
||||
* expand_indexqual_condition --- expand a single indexqual condition
|
||||
* (other than a boolean-qual or ScalarArrayOp case)
|
||||
* expand_indexqual_opclause --- expand a single indexqual condition
|
||||
* that is an operator clause
|
||||
*
|
||||
* The input is a single RestrictInfo, the output a list of RestrictInfos
|
||||
*/
|
||||
static List *
|
||||
expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass)
|
||||
expand_indexqual_opclause(RestrictInfo *rinfo, Oid opclass)
|
||||
{
|
||||
Expr *clause = rinfo->clause;
|
||||
|
||||
/* we know these will succeed */
|
||||
Node *leftop = get_leftop(clause);
|
||||
Node *rightop = get_rightop(clause);
|
||||
@ -2224,6 +2331,204 @@ expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* expand_indexqual_rowcompare --- expand a single indexqual condition
|
||||
* that is a RowCompareExpr
|
||||
*
|
||||
* It's already known that the first column of the row comparison matches
|
||||
* the specified column of the index. We can use additional columns of the
|
||||
* row comparison as index qualifications, so long as they match the index
|
||||
* in the "same direction", ie, the indexkeys are all on the same side of the
|
||||
* clause and the operators are all the same-type members of the opclasses.
|
||||
* If all the columns of the RowCompareExpr match in this way, we just use it
|
||||
* as-is. Otherwise, we build a shortened RowCompareExpr (if more than one
|
||||
* column matches) or a simple OpExpr (if the first-column match is all
|
||||
* there is). In these cases the modified clause is always "<=" or ">="
|
||||
* even when the original was "<" or ">" --- this is necessary to match all
|
||||
* the rows that could match the original. (We are essentially building a
|
||||
* lossy version of the row comparison when we do this.)
|
||||
*/
|
||||
static RestrictInfo *
|
||||
expand_indexqual_rowcompare(RestrictInfo *rinfo,
|
||||
IndexOptInfo *index,
|
||||
int indexcol)
|
||||
{
|
||||
RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause;
|
||||
bool var_on_left;
|
||||
int op_strategy;
|
||||
Oid op_subtype;
|
||||
bool op_recheck;
|
||||
int matching_cols;
|
||||
Oid expr_op;
|
||||
List *opclasses;
|
||||
List *subtypes;
|
||||
List *new_ops;
|
||||
ListCell *largs_cell;
|
||||
ListCell *rargs_cell;
|
||||
ListCell *opnos_cell;
|
||||
|
||||
/* We have to figure out (again) how the first col matches */
|
||||
var_on_left = match_index_to_operand((Node *) linitial(clause->largs),
|
||||
indexcol, index);
|
||||
Assert(var_on_left ||
|
||||
match_index_to_operand((Node *) linitial(clause->rargs),
|
||||
indexcol, index));
|
||||
expr_op = linitial_oid(clause->opnos);
|
||||
if (!var_on_left)
|
||||
expr_op = get_commutator(expr_op);
|
||||
get_op_opclass_properties(expr_op, index->classlist[indexcol],
|
||||
&op_strategy, &op_subtype, &op_recheck);
|
||||
/* Build lists of the opclasses and operator subtypes in case needed */
|
||||
opclasses = list_make1_oid(index->classlist[indexcol]);
|
||||
subtypes = list_make1_oid(op_subtype);
|
||||
|
||||
/*
|
||||
* See how many of the remaining columns match some index column
|
||||
* in the same way. A note about rel membership tests: we assume
|
||||
* that the clause as a whole is already known to use only Vars from
|
||||
* the indexed relation and possibly some acceptable outer relations.
|
||||
* So the "other" side of any potential index condition is OK as long
|
||||
* as it doesn't use Vars from the indexed relation.
|
||||
*/
|
||||
matching_cols = 1;
|
||||
largs_cell = lnext(list_head(clause->largs));
|
||||
rargs_cell = lnext(list_head(clause->rargs));
|
||||
opnos_cell = lnext(list_head(clause->opnos));
|
||||
|
||||
while (largs_cell != NULL)
|
||||
{
|
||||
Node *varop;
|
||||
Node *constop;
|
||||
int i;
|
||||
|
||||
expr_op = lfirst_oid(opnos_cell);
|
||||
if (var_on_left)
|
||||
{
|
||||
varop = (Node *) lfirst(largs_cell);
|
||||
constop = (Node *) lfirst(rargs_cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
varop = (Node *) lfirst(rargs_cell);
|
||||
constop = (Node *) lfirst(largs_cell);
|
||||
/* indexkey is on right, so commute the operator */
|
||||
expr_op = get_commutator(expr_op);
|
||||
if (expr_op == InvalidOid)
|
||||
break; /* operator is not usable */
|
||||
}
|
||||
if (bms_is_member(index->rel->relid, pull_varnos(constop)))
|
||||
break; /* no good, Var on wrong side */
|
||||
if (contain_volatile_functions(constop))
|
||||
break; /* no good, volatile comparison value */
|
||||
|
||||
/*
|
||||
* The Var side can match any column of the index. If the user
|
||||
* does something weird like having multiple identical index
|
||||
* columns, we insist the match be on the first such column,
|
||||
* to avoid confusing the executor.
|
||||
*/
|
||||
for (i = 0; i < index->ncolumns; i++)
|
||||
{
|
||||
if (match_index_to_operand(varop, i, index))
|
||||
break;
|
||||
}
|
||||
if (i >= index->ncolumns)
|
||||
break; /* no match found */
|
||||
|
||||
/* Now, do we have the right operator for this column? */
|
||||
if (get_op_opclass_strategy(expr_op, index->classlist[i])
|
||||
!= op_strategy)
|
||||
break;
|
||||
|
||||
/* Add opclass and subtype to lists */
|
||||
get_op_opclass_properties(expr_op, index->classlist[i],
|
||||
&op_strategy, &op_subtype, &op_recheck);
|
||||
opclasses = lappend_oid(opclasses, index->classlist[i]);
|
||||
subtypes = lappend_oid(subtypes, op_subtype);
|
||||
|
||||
/* This column matches, keep scanning */
|
||||
matching_cols++;
|
||||
largs_cell = lnext(largs_cell);
|
||||
rargs_cell = lnext(rargs_cell);
|
||||
opnos_cell = lnext(opnos_cell);
|
||||
}
|
||||
|
||||
/* Return clause as-is if it's all usable as index quals */
|
||||
if (matching_cols == list_length(clause->opnos))
|
||||
return rinfo;
|
||||
|
||||
/*
|
||||
* We have to generate a subset rowcompare (possibly just one OpExpr).
|
||||
* The painful part of this is changing < to <= or > to >=, so deal with
|
||||
* that first.
|
||||
*/
|
||||
if (op_strategy == BTLessEqualStrategyNumber ||
|
||||
op_strategy == BTGreaterEqualStrategyNumber)
|
||||
{
|
||||
/* easy, just use the same operators */
|
||||
new_ops = list_truncate(list_copy(clause->opnos), matching_cols);
|
||||
}
|
||||
else
|
||||
{
|
||||
ListCell *opclasses_cell;
|
||||
ListCell *subtypes_cell;
|
||||
|
||||
if (op_strategy == BTLessStrategyNumber)
|
||||
op_strategy = BTLessEqualStrategyNumber;
|
||||
else if (op_strategy == BTGreaterStrategyNumber)
|
||||
op_strategy = BTGreaterEqualStrategyNumber;
|
||||
else
|
||||
elog(ERROR, "unexpected strategy number %d", op_strategy);
|
||||
new_ops = NIL;
|
||||
forboth(opclasses_cell, opclasses, subtypes_cell, subtypes)
|
||||
{
|
||||
expr_op = get_opclass_member(lfirst_oid(opclasses_cell),
|
||||
lfirst_oid(subtypes_cell),
|
||||
op_strategy);
|
||||
if (!OidIsValid(expr_op)) /* should not happen */
|
||||
elog(ERROR, "could not find member %d of opclass %u",
|
||||
op_strategy, lfirst_oid(opclasses_cell));
|
||||
if (!var_on_left)
|
||||
{
|
||||
expr_op = get_commutator(expr_op);
|
||||
if (!OidIsValid(expr_op)) /* should not happen */
|
||||
elog(ERROR, "could not find commutator of member %d of opclass %u",
|
||||
op_strategy, lfirst_oid(opclasses_cell));
|
||||
}
|
||||
new_ops = lappend_oid(new_ops, expr_op);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have more than one matching col, create a subset rowcompare */
|
||||
if (matching_cols > 1)
|
||||
{
|
||||
RowCompareExpr *rc = makeNode(RowCompareExpr);
|
||||
|
||||
if (var_on_left)
|
||||
rc->rctype = (RowCompareType) op_strategy;
|
||||
else
|
||||
rc->rctype = (op_strategy == BTLessEqualStrategyNumber) ?
|
||||
ROWCOMPARE_GE : ROWCOMPARE_LE;
|
||||
rc->opnos = new_ops;
|
||||
rc->opclasses = list_truncate(list_copy(clause->opclasses),
|
||||
matching_cols);
|
||||
rc->largs = list_truncate((List *) copyObject(clause->largs),
|
||||
matching_cols);
|
||||
rc->rargs = list_truncate((List *) copyObject(clause->rargs),
|
||||
matching_cols);
|
||||
return make_restrictinfo((Expr *) rc, true, false, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
Expr *opexpr;
|
||||
|
||||
opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false,
|
||||
copyObject(linitial(clause->largs)),
|
||||
copyObject(linitial(clause->rargs)));
|
||||
return make_restrictinfo(opexpr, true, false, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a fixed prefix that all the "leftop" values must have,
|
||||
* generate suitable indexqual condition(s). opclass is the index
|
||||
|
Reference in New Issue
Block a user