1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-14 08:21:07 +03:00

Clean up handling of variable-free qual clauses. System now does the

right thing with variable-free clauses that contain noncachable functions,
such as 'WHERE random() < 0.5' --- these are evaluated once per
potential output tuple.  Expressions that contain only Params are
now candidates to be indexscan quals --- for example, 'var = ($1 + 1)'
can now be indexed.  Cope with RelabelType nodes atop potential indexscan
variables --- this oversight prevents 7.0.* from recognizing some
potentially indexscanable situations.
This commit is contained in:
Tom Lane
2000-08-13 02:50:35 +00:00
parent 766fb7f707
commit 37168b8da4
15 changed files with 410 additions and 177 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.38 2000/06/08 22:37:09 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/clausesel.c,v 1.39 2000/08/13 02:50:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -123,7 +123,7 @@ clauselist_selectivity(Query *root,
Selectivity s2;
/*
* See if it looks like a restriction clause with a Const or Param
* See if it looks like a restriction clause with a pseudoconstant
* on one side. (Anything more complicated than that might not
* behave in the simple way we are expecting.)
*
@ -146,7 +146,7 @@ clauselist_selectivity(Query *root,
other = (flag & SEL_RIGHT) ? get_rightop((Expr *) clause) :
get_leftop((Expr *) clause);
if (IsA(other, Const) || IsA(other, Param))
if (is_pseudo_constant_clause((Node *) other))
{
Oid opno = ((Oper *) ((Expr *) clause)->oper)->opno;
RegProcedure oprrest = get_oprrest(opno);
@ -533,6 +533,13 @@ clause_selectivity(Query *root,
*/
s1 = 1.0;
}
else if (IsA(clause, RelabelType))
{
/* Not sure this case is needed, but it can't hurt */
s1 = clause_selectivity(root,
((RelabelType *) clause)->arg,
varRelid);
}
return s1;
}

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.92 2000/08/08 15:41:30 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.93 2000/08/13 02:50:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -649,7 +649,7 @@ group_clauses_by_ikey_for_joins(RelOptInfo *rel,
* a key of an index.
*
* To match, the clause:
*
* (1a) for a restriction clause: must be in the form (indexkey op const)
* or (const op indexkey), or
* (1b) for a join clause: must be in the form (indexkey op others)
@ -708,11 +708,11 @@ match_clause_to_indexkey(RelOptInfo *rel,
/*
* Not considering joins, so check for clauses of the form:
* (indexkey operator constant) or (constant operator indexkey).
* We will accept a Param as being constant.
* Anything that is a "pseudo constant" expression will do.
*/
if ((IsA(rightop, Const) ||IsA(rightop, Param)) &&
match_index_to_operand(indexkey, leftop, rel, index))
if (match_index_to_operand(indexkey, leftop, rel, index) &&
is_pseudo_constant_clause((Node *) rightop))
{
if (is_indexable_operator(clause, opclass, index->relam, true))
return true;
@ -726,8 +726,8 @@ match_clause_to_indexkey(RelOptInfo *rel,
return true;
return false;
}
if ((IsA(leftop, Const) ||IsA(leftop, Param)) &&
match_index_to_operand(indexkey, rightop, rel, index))
if (match_index_to_operand(indexkey, rightop, rel, index) &&
is_pseudo_constant_clause((Node *) leftop))
{
if (is_indexable_operator(clause, opclass, index->relam, false))
return true;
@ -748,29 +748,32 @@ match_clause_to_indexkey(RelOptInfo *rel,
/*
* Check for an indexqual that could be handled by a nestloop
* join. We need the index key to be compared against an
* expression that uses none of the indexed relation's vars.
* expression that uses none of the indexed relation's vars
* and contains no non-cachable functions.
*/
if (match_index_to_operand(indexkey, leftop, rel, index))
{
List *othervarnos = pull_varnos((Node *) rightop);
bool isIndexable;
isIndexable = !intMember(lfirsti(rel->relids), othervarnos);
isIndexable =
!intMember(lfirsti(rel->relids), othervarnos) &&
!contain_noncachable_functions((Node *) rightop) &&
is_indexable_operator(clause, opclass, index->relam, true);
freeList(othervarnos);
if (isIndexable &&
is_indexable_operator(clause, opclass, index->relam, true))
return true;
return isIndexable;
}
else if (match_index_to_operand(indexkey, rightop, rel, index))
{
List *othervarnos = pull_varnos((Node *) leftop);
bool isIndexable;
isIndexable = !intMember(lfirsti(rel->relids), othervarnos);
isIndexable =
!intMember(lfirsti(rel->relids), othervarnos) &&
!contain_noncachable_functions((Node *) leftop) &&
is_indexable_operator(clause, opclass, index->relam, false);
freeList(othervarnos);
if (isIndexable &&
is_indexable_operator(clause, opclass, index->relam, false))
return true;
return isIndexable;
}
}
@ -790,7 +793,9 @@ match_clause_to_indexkey(RelOptInfo *rel,
* recognizing binary-compatible datatypes. For example, if we have
* an expression like "oid = 123", the operator will be oideqint4,
* which we need to replace with oideq in order to recognize it as
* matching an oid_ops index on the oid field.
* matching an oid_ops index on the oid field. A variant case is where
* the expression is like "oid::int4 = 123", where the given operator
* will be int4eq and again we need to intuit that we want to use oideq.
*
* Returns the OID of the matching operator, or InvalidOid if no match.
* Note that the returned OID will be different from the one in the given
@ -804,8 +809,13 @@ indexable_operator(Expr *clause, Oid opclass, Oid relam,
{
Oid expr_op = ((Oper *) clause->oper)->opno;
Oid commuted_op;
Operator oldop,
newop;
Form_pg_operator oldopform;
char *opname;
Oid ltype,
rtype;
rtype,
indexkeytype;
/* Get the commuted operator if necessary */
if (indexkey_on_left)
@ -821,48 +831,72 @@ indexable_operator(Expr *clause, Oid opclass, Oid relam,
/*
* Maybe the index uses a binary-compatible operator set.
*
* Get the nominal input types of the given operator and the actual
* type (before binary-compatible relabeling) of the index key.
*/
ltype = exprType((Node *) get_leftop(clause));
rtype = exprType((Node *) get_rightop(clause));
oldop = get_operator_tuple(expr_op);
if (! HeapTupleIsValid(oldop))
return InvalidOid; /* probably can't happen */
oldopform = (Form_pg_operator) GETSTRUCT(oldop);
opname = NameStr(oldopform->oprname);
ltype = oldopform->oprleft;
rtype = oldopform->oprright;
if (indexkey_on_left)
{
Node *leftop = (Node *) get_leftop(clause);
if (leftop && IsA(leftop, RelabelType))
leftop = ((RelabelType *) leftop)->arg;
indexkeytype = exprType(leftop);
}
else
{
Node *rightop = (Node *) get_rightop(clause);
if (rightop && IsA(rightop, RelabelType))
rightop = ((RelabelType *) rightop)->arg;
indexkeytype = exprType(rightop);
}
/*
* make sure we have two different binary-compatible types...
* Make sure we have different but binary-compatible types.
*/
if (ltype != rtype && IS_BINARY_COMPATIBLE(ltype, rtype))
if (ltype == indexkeytype && rtype == indexkeytype)
return InvalidOid; /* no chance for a different operator */
if (ltype != indexkeytype && !IS_BINARY_COMPATIBLE(ltype, indexkeytype))
return InvalidOid;
if (rtype != indexkeytype && !IS_BINARY_COMPATIBLE(rtype, indexkeytype))
return InvalidOid;
/*
* OK, look for operator of the same name with the indexkey's data type.
* (In theory this might find a non-semantically-comparable operator,
* but in practice that seems pretty unlikely for binary-compatible types.)
*/
newop = oper(opname, indexkeytype, indexkeytype, TRUE);
if (HeapTupleIsValid(newop))
{
char *opname = get_opname(expr_op);
Operator newop;
Oid new_expr_op = oprid(newop);
if (opname == NULL)
return InvalidOid; /* probably shouldn't happen */
/* Use the datatype of the index key */
if (indexkey_on_left)
newop = oper(opname, ltype, ltype, TRUE);
else
newop = oper(opname, rtype, rtype, TRUE);
if (HeapTupleIsValid(newop))
if (new_expr_op != expr_op)
{
Oid new_expr_op = oprid(newop);
if (new_expr_op != expr_op)
{
/*
* OK, we found a binary-compatible operator of the same
* name; now does it match the index?
*/
if (indexkey_on_left)
commuted_op = new_expr_op;
else
commuted_op = get_commutator(new_expr_op);
if (commuted_op == InvalidOid)
return InvalidOid;
/*
* OK, we found a binary-compatible operator of the same
* name; now does it match the index?
*/
if (indexkey_on_left)
commuted_op = new_expr_op;
else
commuted_op = get_commutator(new_expr_op);
if (commuted_op == InvalidOid)
return InvalidOid;
if (op_class(commuted_op, opclass, relam))
return new_expr_op;
}
if (op_class(commuted_op, opclass, relam))
return new_expr_op;
}
}
@ -1526,13 +1560,22 @@ match_index_to_operand(int indexkey,
RelOptInfo *rel,
IndexOptInfo *index)
{
/*
* Ignore any RelabelType node above the indexkey. This is needed to
* be able to apply indexscanning in binary-compatible-operator cases.
* Note: we can assume there is at most one RelabelType node;
* eval_const_expressions() will have simplified if more than one.
*/
if (operand && IsA(operand, RelabelType))
operand = (Var *) ((RelabelType *) operand)->arg;
if (index->indproc == InvalidOid)
{
/*
* Normal index.
* Simple index.
*/
if (IsA(operand, Var) &&
if (operand && IsA(operand, Var) &&
lfirsti(rel->relids) == operand->varno &&
indexkey == operand->varattno)
return true;
@ -1541,7 +1584,7 @@ match_index_to_operand(int indexkey,
}
/*
* functional index check
* Functional index.
*/
return function_index_operand((Expr *) operand, rel, index);
}
@ -1570,18 +1613,23 @@ function_index_operand(Expr *funcOpnd, RelOptInfo *rel, IndexOptInfo *index)
if (function->funcid != index->indproc)
return false;
/*
/*----------
* Check that the arguments correspond to the same arguments used to
* create the functional index. To do this we must check that 1.
* refer to the right relation. 2. the args have the right attr.
* numbers in the right order.
* create the functional index. To do this we must check that
* 1. they refer to the right relation.
* 2. the args have the right attr. numbers in the right order.
* We must ignore RelabelType nodes above the argument Vars in order
* to recognize binary-compatible-function cases correctly.
*----------
*/
i = 0;
foreach(arg, funcargs)
{
Var *var = (Var *) lfirst(arg);
if (!IsA(var, Var))
if (var && IsA(var, RelabelType))
var = (Var *) ((RelabelType *) var)->arg;
if (var == NULL || !IsA(var, Var))
return false;
if (indexKeys[i] == 0)
return false;
@ -1643,7 +1691,7 @@ function_index_operand(Expr *funcOpnd, RelOptInfo *rel, IndexOptInfo *index)
* additional indexscanable qualifications.
*
* The given clause is already known to be a binary opclause having
* the form (indexkey OP const/param) or (const/param OP indexkey),
* the form (indexkey OP pseudoconst) or (pseudoconst OP indexkey),
* but the OP proved not to be one of the index's opclass operators.
* Return 'true' if we can do something with it anyway.
*/

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.94 2000/07/12 02:37:08 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.95 2000/08/13 02:50:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -865,6 +865,12 @@ static Node *
fix_indxqual_operand(Node *node, int baserelid, Form_pg_index index,
Oid *opclass)
{
/*
* Remove any binary-compatible relabeling of the indexkey
*/
if (IsA(node, RelabelType))
node = ((RelabelType *) node)->arg;
/*
* We represent index keys by Var nodes having the varno of the base
* table but varattno equal to the index's attribute number (index

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.48 2000/08/08 15:41:38 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.49 2000/08/13 02:50:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -184,7 +184,7 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
/*
* There is only one relation participating in 'clause', so
* 'clause' must be a restriction clause for that relation.
* 'clause' is a restriction clause for that relation.
*/
RelOptInfo *rel = get_base_rel(root, lfirsti(relids));
@ -201,11 +201,11 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
*/
check_mergejoinable(restrictinfo);
}
else
else if (relids != NIL)
{
/*
* 'clause' is a join clause, since there is more than one atom in
* 'clause' is a join clause, since there is more than one rel in
* the relid list. Set additional RestrictInfo fields for
* joining.
*
@ -219,8 +219,6 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
/*
* Add clause to the join lists of all the relevant relations.
* (If, perchance, 'clause' contains NO vars, then nothing will
* happen...)
*/
add_join_info_to_rels(root, restrictinfo, relids);
@ -232,6 +230,15 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
*/
add_vars_to_targetlist(root, vars);
}
else
{
/*
* 'clause' references no rels, and therefore we have no place to
* attach it. This means query_planner() screwed up --- it should
* treat variable-less clauses separately.
*/
elog(ERROR, "add_restrict_and_join_to_rel: can't cope with variable-free clause");
}
/*
* If the clause has a mergejoinable operator, then the two sides

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.57 2000/07/27 04:51:04 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.58 2000/08/13 02:50:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -78,7 +78,8 @@ query_planner(Query *root,
List *qual,
double tuple_fraction)
{
List *constant_qual = NIL;
List *noncachable_qual;
List *constant_qual;
List *var_only_tlist;
Plan *subplan;
@ -106,9 +107,14 @@ query_planner(Query *root,
* have been optimized away by eval_const_expressions(). What we're
* mostly interested in here is quals that depend only on outer-level
* vars, although if the qual reduces to "WHERE FALSE" this path will
* also be taken.
* also be taken. We also need a special case for quals that contain
* noncachable functions but no vars, such as "WHERE random() < 0.5".
* These cannot be treated as normal restriction or join quals, but
* they're not constants either. Instead, attach them to the qpqual
* of the top-level plan, so that they get evaluated once per potential
* output tuple.
*/
qual = pull_constant_clauses(qual, &constant_qual);
qual = pull_constant_clauses(qual, &noncachable_qual, &constant_qual);
/*
* Create a target list that consists solely of (resdom var) target
@ -128,6 +134,12 @@ query_planner(Query *root,
*/
subplan = subplanner(root, var_only_tlist, qual, tuple_fraction);
/*
* Handle the noncachable quals.
*/
if (noncachable_qual)
subplan->qual = nconc(subplan->qual, noncachable_qual);
/*
* Build a result node to control the plan if we have constant quals.
*/
@ -163,7 +175,7 @@ query_planner(Query *root,
* for processing a single level of attributes.
*
* flat_tlist is the flattened target list
* qual is the qualification to be satisfied
* qual is the qualification to be satisfied (restrict and join quals only)
* tuple_fraction is the fraction of tuples we expect will be retrieved
*
* See query_planner() comments about the interpretation of tuple_fraction.

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.70 2000/08/08 15:41:53 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.71 2000/08/13 02:50:10 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -46,6 +46,7 @@ static bool contain_subplans_walker(Node *node, void *context);
static bool pull_subplans_walker(Node *node, List **listptr);
static bool check_subplans_for_ungrouped_vars_walker(Node *node,
Query *context);
static bool contain_noncachable_functions_walker(Node *node, void *context);
static int is_single_func(Node *node);
static Node *eval_const_expressions_mutator(Node *node, void *context);
static Expr *simplify_op_or_func(Expr *expr, List *args);
@ -600,41 +601,136 @@ check_subplans_for_ungrouped_vars_walker(Node *node,
}
/*****************************************************************************
* Check clauses for noncachable functions
*****************************************************************************/
/*
* contain_noncachable_functions
* Recursively search for noncachable functions within a clause.
*
* Returns true if any noncachable function (or operator implemented by a
* noncachable function) is found. This test is needed so that we don't
* mistakenly think that something like "WHERE random() < 0.5" can be treated
* as a constant qualification.
*
* XXX we do not examine sublinks/subplans to see if they contain uses of
* noncachable functions. It's not real clear if that is correct or not...
*/
bool
contain_noncachable_functions(Node *clause)
{
return contain_noncachable_functions_walker(clause, NULL);
}
static bool
contain_noncachable_functions_walker(Node *node, void *context)
{
if (node == NULL)
return false;
if (IsA(node, Expr))
{
Expr *expr = (Expr *) node;
switch (expr->opType)
{
case OP_EXPR:
if (! op_iscachable(((Oper *) expr->oper)->opno))
return true;
break;
case FUNC_EXPR:
if (! func_iscachable(((Func *) expr->oper)->funcid))
return true;
break;
default:
break;
}
}
return expression_tree_walker(node, contain_noncachable_functions_walker,
context);
}
/*****************************************************************************
* Check for "pseudo-constant" clauses
*****************************************************************************/
/*
* is_pseudo_constant_clause
* Detect whether a clause is "constant", ie, it contains no variables
* of the current query level and no uses of noncachable functions.
* Such a clause is not necessarily a true constant: it can still contain
* Params and outer-level Vars. However, its value will be constant over
* any one scan of the current query, so it can be used as an indexscan
* key or (if a top-level qual) can be pushed up to become a gating qual.
*/
bool
is_pseudo_constant_clause(Node *clause)
{
/*
* We could implement this check in one recursive scan. But since the
* check for noncachable functions is both moderately expensive and
* unlikely to fail, it seems better to look for Vars first and only
* check for noncachable functions if we find no Vars.
*/
if (!contain_var_clause(clause) &&
!contain_noncachable_functions(clause))
return true;
return false;
}
/*----------
* pull_constant_clauses
* Scan through a list of qualifications and separate "constant" quals
* from those that are not.
*
* The input qual list is divided into three parts:
* * The function's return value is a list of all those quals that contain
* variable(s) of the current query level. (These quals will become
* restrict and join quals.)
* * *noncachableQual receives a list of quals that have no Vars, yet
* cannot be treated as constants because they contain noncachable
* function calls. (Example: WHERE random() < 0.5)
* * *constantQual receives a list of the remaining quals, which can be
* treated as constants for any one scan of the current query level.
* (They are really only pseudo-constant, since they may contain
* Params or outer-level Vars.)
*----------
*/
List *
pull_constant_clauses(List *quals,
List **noncachableQual,
List **constantQual)
{
List *q;
List *normqual = NIL;
List *noncachequal = NIL;
List *constqual = NIL;
foreach(q, quals)
{
Node *qual = (Node *) lfirst(q);
if (contain_var_clause(qual))
normqual = lappend(normqual, qual);
else if (contain_noncachable_functions(qual))
noncachequal = lappend(noncachequal, qual);
else
constqual = lappend(constqual, qual);
}
*noncachableQual = noncachequal;
*constantQual = constqual;
return normqual;
}
/*****************************************************************************
* *
* General clause-manipulating routines *
* *
*****************************************************************************/
/*
* pull_constant_clauses
* Scans through a list of qualifications and find those that
* contain no variables (of the current query level).
*
* Returns a list of the constant clauses in constantQual and the remaining
* quals as the return value.
*
*/
List *
pull_constant_clauses(List *quals, List **constantQual)
{
List *q;
List *constqual = NIL;
List *restqual = NIL;
foreach(q, quals)
{
if (!contain_var_clause(lfirst(q)))
constqual = lcons(lfirst(q), constqual);
else
restqual = lcons(lfirst(q), restqual);
}
*constantQual = constqual;
return restqual;
}
/*
* clause_relids_vars
* Retrieves distinct relids and vars appearing within a clause.
@ -744,6 +840,13 @@ get_relattval(Node *clause,
if (!right)
goto default_results;
/* Ignore any binary-compatible relabeling */
if (IsA(left, RelabelType))
left = (Var *) ((RelabelType *) left)->arg;
if (IsA(right, RelabelType))
right = (Var *) ((RelabelType *) right)->arg;
/* First look for the var or func */
if (IsA(left, Var) &&
@ -856,6 +959,12 @@ get_rels_atts(Node *clause,
{
int funcvarno;
/* Ignore any binary-compatible relabeling */
if (IsA(left, RelabelType))
left = (Var *) ((RelabelType *) left)->arg;
if (IsA(right, RelabelType))
right = (Var *) ((RelabelType *) right)->arg;
if (IsA(left, Var))
{
*relid1 = left->varno;
@ -1147,12 +1256,20 @@ eval_const_expressions_mutator(Node *node, void *context)
/*
* If we can simplify the input to a constant, then we don't need
* the RelabelType node anymore: just change the type field of the
* Const node. Otherwise, copy the RelabelType node.
* Const node. Otherwise, must copy the RelabelType node.
*/
RelabelType *relabel = (RelabelType *) node;
Node *arg;
arg = eval_const_expressions_mutator(relabel->arg, context);
/*
* If we find stacked RelabelTypes (eg, from foo :: int :: oid)
* we can discard all but the top one.
*/
while (arg && IsA(arg, RelabelType))
arg = ((RelabelType *) arg)->arg;
if (arg && IsA(arg, Const))
{
Const *con = (Const *) arg;
@ -1369,7 +1486,10 @@ simplify_op_or_func(Expr *expr, List *args)
funcid = func->funcid;
result_typeid = func->functype;
}
/* Someday lsyscache.c might provide a function for this */
/*
* we could use func_iscachable() here, but we need several fields
* out of the func tuple, so might as well just look it up once.
*/
func_tuple = SearchSysCacheTuple(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);