From: Tom Lane Date: Sun, 13 Aug 2000 02:50:35 +0000 (+0000) Subject: Clean up handling of variable-free qual clauses. System now does the X-Git-Url: http://git.postgresql.org/gitweb/-?a=commitdiff_plain;h=37168b8da43d9a6f7471cf757119ece6c96fb2b9;p=users%2Frhaas%2Fpostgres.git 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. --- diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 07b894843d..8c9970a6fa 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.18 2000/01/26 05:56:21 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.19 2000/08/13 02:50:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -277,7 +277,7 @@ ExecProcNode(Plan *node, Plan *parent) * ---------------- */ case T_NestLoop: - result = ExecNestLoop((NestLoop *) node, parent); + result = ExecNestLoop((NestLoop *) node); break; case T_MergeJoin: diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index 0eb3b9f5ae..57770d2405 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.52 2000/07/12 02:37:03 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.53 2000/08/13 02:50:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -281,6 +281,16 @@ IndexNext(IndexScan *node) TupleTableSlot * ExecIndexScan(IndexScan *node) { + IndexScanState *indexstate = node->indxstate; + + /* ---------------- + * If we have runtime keys and they've not already been set up, + * do it now. + * ---------------- + */ + if (indexstate->iss_RuntimeKeyInfo && !indexstate->iss_RuntimeKeysReady) + ExecReScan((Plan *) node, NULL, NULL); + /* ---------------- * use IndexNext as access method * ---------------- @@ -335,9 +345,10 @@ ExecIndexReScan(IndexScan *node, ExprContext *exprCtxt, Plan *parent) scanKeys = indexstate->iss_ScanKeys; runtimeKeyInfo = indexstate->iss_RuntimeKeyInfo; numScanKeys = indexstate->iss_NumScanKeys; - indexstate->iss_IndexPtr = -1; if (ScanDirectionIsBackward(node->indxorderdir)) indexstate->iss_IndexPtr = numIndices; + else + indexstate->iss_IndexPtr = -1; if (econtext) { @@ -420,6 +431,9 @@ ExecIndexReScan(IndexScan *node, ExprContext *exprCtxt, Plan *parent) skey = scanKeys[i]; index_rescan(scan, direction, skey); } + + if (runtimeKeyInfo) + indexstate->iss_RuntimeKeysReady = true; } /* ---------------------------------------------------------------- @@ -603,7 +617,6 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) Relation currentRelation; HeapScanDesc currentScanDesc; ScanDirection direction; - List *execParam = NIL; /* ---------------- * assign execution state to node @@ -656,6 +669,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) indexstate->iss_NumScanKeys = NULL; indexstate->iss_RuntimeKeyInfo = NULL; indexstate->iss_RuntimeContext = NULL; + indexstate->iss_RuntimeKeysReady = false; indexstate->iss_RelationDescs = NULL; indexstate->iss_ScanDescs = NULL; @@ -787,6 +801,9 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) */ leftop = (Node *) get_leftop(clause); + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + Assert(leftop != NULL); if (IsA(leftop, Var) && var_is_rel((Var *) leftop)) @@ -827,7 +844,6 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) /* treat Param as runtime key */ have_runtime_keys = true; run_keys[j] = LEFT_OP; - execParam = lappendi(execParam, ((Param *) leftop)->paramid); } else { @@ -857,6 +873,9 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) */ rightop = (Node *) get_rightop(clause); + if (rightop && IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + Assert(rightop != NULL); if (IsA(rightop, Var) && var_is_rel((Var *) rightop)) @@ -906,7 +925,6 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) /* treat Param as runtime key */ have_runtime_keys = true; run_keys[j] = RIGHT_OP; - execParam = lappendi(execParam, ((Param *) rightop)->paramid); } else { @@ -1068,12 +1086,6 @@ ExecInitIndexScan(IndexScan *node, EState *estate, Plan *parent) indexstate->iss_RelationDescs = relationDescs; indexstate->iss_ScanDescs = scanDescs; - /* - * if there are some PARAM_EXEC in scankeys then force index rescan on - * first scan. - */ - ((Plan *) node)->chgParam = execParam; - /* ---------------- * all done. * ---------------- diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index 70b98a97e3..f59c1b0f60 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.18 2000/07/17 03:04:53 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.19 2000/08/13 02:50:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -57,7 +57,7 @@ * ---------------------------------------------------------------- */ TupleTableSlot * -ExecNestLoop(NestLoop *node, Plan *parent) +ExecNestLoop(NestLoop *node) { NestLoopState *nlstate; Plan *innerPlan; @@ -187,7 +187,7 @@ ExecNestLoop(NestLoop *node, Plan *parent) * outer tuple (e.g. in index scans), that's why we pass our * expr context. */ - ExecReScan(innerPlan, econtext, parent); + ExecReScan(innerPlan, econtext, (Plan *) node); ENL1_printf("getting new inner tuple"); diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index ba2e7e67b3..7d80e28819 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -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; } diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 13bf65e699..27ae4056d9 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -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)) - { - char *opname = get_opname(expr_op); - Operator newop; + 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; - if (opname == NULL) - return InvalidOid; /* probably shouldn't happen */ + /* + * 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); - /* 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)) + { + Oid new_expr_op = oprid(newop); - 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; - - if (op_class(commuted_op, opclass, relam)) - return new_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; + + 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. */ diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index bf91b8d1a6..c049f5d86b 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -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 diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index d2bbff7e60..8ffd35c9bb 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -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 diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 6b6bc15bda..abb468aa8d 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -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. diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index adda68b636..65e7145636 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -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); @@ -601,40 +602,135 @@ check_subplans_for_ungrouped_vars_walker(Node *node, /***************************************************************************** - * * - * General clause-manipulating routines * - * * + * Check clauses for noncachable functions *****************************************************************************/ - /* - * pull_constant_clauses - * Scans through a list of qualifications and find those that - * contain no variables (of the current query level). + * contain_noncachable_functions + * Recursively search for noncachable functions within a clause. * - * Returns a list of the constant clauses in constantQual and the remaining - * quals as the return value. + * 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 **constantQual) +pull_constant_clauses(List *quals, + List **noncachableQual, + List **constantQual) { List *q; + List *normqual = NIL; + List *noncachequal = NIL; List *constqual = NIL; - List *restqual = NIL; foreach(q, quals) { - if (!contain_var_clause(lfirst(q))) - constqual = lcons(lfirst(q), constqual); + 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 - restqual = lcons(lfirst(q), restqual); + constqual = lappend(constqual, qual); } + + *noncachableQual = noncachequal; *constantQual = constqual; - return restqual; + return normqual; } +/***************************************************************************** + * * + * General clause-manipulating routines * + * * + *****************************************************************************/ + /* * 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); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 5c80a5179a..28910dae89 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.44 2000/07/23 03:50:26 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/cache/lsyscache.c,v 1.45 2000/08/13 02:50:16 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -26,15 +26,15 @@ /* * op_class * - * Return t iff operator 'opid' is in operator class 'opclass' for + * Return t iff operator 'opno' is in operator class 'opclass' for * access method 'amopid'. */ bool -op_class(Oid opid, Oid opclass, Oid amopid) +op_class(Oid opno, Oid opclass, Oid amopid) { if (HeapTupleIsValid(SearchSysCacheTuple(AMOPOPID, ObjectIdGetDatum(opclass), - ObjectIdGetDatum(opid), + ObjectIdGetDatum(opno), ObjectIdGetDatum(amopid), 0))) return true; @@ -287,7 +287,7 @@ get_opcode(Oid opno) return optup->oprcode; } else - return (RegProcedure) NULL; + return (RegProcedure) InvalidOid; } /* @@ -371,17 +371,28 @@ op_hashjoinable(Oid opno, Oid ltype, Oid rtype) return InvalidOid; } +/* + * op_iscachable + * + * Get the proiscachable flag for the operator's underlying function. + */ +bool +op_iscachable(Oid opno) +{ + RegProcedure funcid = get_opcode(opno); + + if (funcid == (RegProcedure) InvalidOid) + elog(ERROR, "Operator OID %u does not exist", opno); + + return func_iscachable((Oid) funcid); +} + HeapTuple get_operator_tuple(Oid opno) { - HeapTuple optup; - - if ((optup = SearchSysCacheTuple(OPEROID, - ObjectIdGetDatum(opno), - 0, 0, 0))) - return optup; - else - return (HeapTuple) NULL; + return SearchSysCacheTuple(OPEROID, + ObjectIdGetDatum(opno), + 0, 0, 0); } /* @@ -450,7 +461,7 @@ get_oprrest(Oid opno) return optup->oprrest; } else - return (RegProcedure) NULL; + return (RegProcedure) InvalidOid; } /* @@ -473,7 +484,7 @@ get_oprjoin(Oid opno) return optup->oprjoin; } else - return (RegProcedure) NULL; + return (RegProcedure) InvalidOid; } /* ---------- FUNCTION CACHE ---------- */ @@ -496,6 +507,24 @@ get_func_rettype(Oid funcid) return ((Form_pg_proc) GETSTRUCT(func_tuple))->prorettype; } +/* + * func_iscachable + * Given procedure id, return the function's proiscachable flag. + */ +bool +func_iscachable(Oid funcid) +{ + HeapTuple func_tuple; + + func_tuple = SearchSysCacheTuple(PROCOID, + ObjectIdGetDatum(funcid), + 0, 0, 0); + if (!HeapTupleIsValid(func_tuple)) + elog(ERROR, "Function OID %u does not exist", funcid); + + return ((Form_pg_proc) GETSTRUCT(func_tuple))->proiscachable; +} + /* ---------- RELATION CACHE ---------- */ #ifdef NOT_USED diff --git a/src/include/executor/nodeNestloop.h b/src/include/executor/nodeNestloop.h index b1242b0da0..42e3fae833 100644 --- a/src/include/executor/nodeNestloop.h +++ b/src/include/executor/nodeNestloop.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: nodeNestloop.h,v 1.12 2000/01/26 05:58:05 momjian Exp $ + * $Id: nodeNestloop.h,v 1.13 2000/08/13 02:50:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,7 +16,7 @@ #include "nodes/plannodes.h" -extern TupleTableSlot *ExecNestLoop(NestLoop *node, Plan *parent); +extern TupleTableSlot *ExecNestLoop(NestLoop *node); extern bool ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent); extern int ExecCountSlotsNestLoop(NestLoop *node); extern void ExecEndNestLoop(NestLoop *node); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index a9ef6e78ae..6e691c1d78 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: execnodes.h,v 1.45 2000/08/06 04:26:29 tgl Exp $ + * $Id: execnodes.h,v 1.46 2000/08/13 02:50:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -380,6 +380,7 @@ typedef struct CommonScanState * NumScanKeys array of no of keys in each Skey struct * RuntimeKeyInfo array of array of flags for Skeys evaled at runtime * RuntimeContext expr context for evaling runtime Skeys + * RuntimeKeysReady true if runtime Skeys have been computed * RelationDescs ptr to array of relation descriptors * ScanDescs ptr to array of scan descriptors * ---------------- @@ -394,6 +395,7 @@ typedef struct IndexScanState int *iss_NumScanKeys; int **iss_RuntimeKeyInfo; ExprContext *iss_RuntimeContext; + bool iss_RuntimeKeysReady; RelationPtr iss_RelationDescs; IndexScanDescPtr iss_ScanDescs; HeapTupleData iss_htup; diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 946a9b0586..1b2bcd9205 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: clauses.h,v 1.37 2000/05/30 04:24:57 tgl Exp $ + * $Id: clauses.h,v 1.38 2000/08/13 02:50:26 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,7 +53,13 @@ extern bool contain_subplans(Node *clause); extern List *pull_subplans(Node *clause); extern void check_subplans_for_ungrouped_vars(Node *clause, Query *query); -extern List *pull_constant_clauses(List *quals, List **constantQual); +extern bool contain_noncachable_functions(Node *clause); + +extern bool is_pseudo_constant_clause(Node *clause); + +extern List *pull_constant_clauses(List *quals, + List **noncachableQual, + List **constantQual); extern void clause_get_relids_vars(Node *clause, Relids *relids, List **vars); extern int NumRelids(Node *clause); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 5c6c09ccbc..9c105576ae 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: lsyscache.h,v 1.24 2000/06/08 22:37:58 momjian Exp $ + * $Id: lsyscache.h,v 1.25 2000/08/13 02:50:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,7 +15,7 @@ #include "access/htup.h" -extern bool op_class(Oid opid, Oid opclass, Oid amopid); +extern bool op_class(Oid opno, Oid opclass, Oid amopid); extern char *get_attname(Oid relid, AttrNumber attnum); extern AttrNumber get_attnum(Oid relid, char *attname); extern Oid get_atttype(Oid relid, AttrNumber attnum); @@ -23,21 +23,20 @@ extern bool get_attisset(Oid relid, char *attname); extern int32 get_atttypmod(Oid relid, AttrNumber attnum); extern double get_attdisbursion(Oid relid, AttrNumber attnum, double min_estimate); -extern RegProcedure get_opcode(Oid opid); -extern char *get_opname(Oid opid); -extern bool op_mergejoinable(Oid opid, Oid ltype, Oid rtype, +extern RegProcedure get_opcode(Oid opno); +extern char *get_opname(Oid opno); +extern bool op_mergejoinable(Oid opno, Oid ltype, Oid rtype, Oid *leftOp, Oid *rightOp); -extern Oid op_hashjoinable(Oid opid, Oid ltype, Oid rtype); -extern Oid get_commutator(Oid opid); +extern Oid op_hashjoinable(Oid opno, Oid ltype, Oid rtype); +extern bool op_iscachable(Oid opno); extern HeapTuple get_operator_tuple(Oid opno); -extern Oid get_negator(Oid opid); -extern RegProcedure get_oprrest(Oid opid); -extern RegProcedure get_oprjoin(Oid opid); +extern Oid get_commutator(Oid opno); +extern Oid get_negator(Oid opno); +extern RegProcedure get_oprrest(Oid opno); +extern RegProcedure get_oprjoin(Oid opno); extern Oid get_func_rettype(Oid funcid); +extern bool func_iscachable(Oid funcid); extern char *get_rel_name(Oid relid); -extern struct varlena *get_relstub(Oid relid, int no, bool *islast); -extern Oid get_ruleid(char *rulename); -extern Oid get_eventrelid(Oid ruleid); extern int16 get_typlen(Oid typid); extern bool get_typbyval(Oid typid); extern Datum get_typdefault(Oid typid); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 1f54890e5c..0096b3825c 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.26 2000/08/03 16:34:57 tgl Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.27 2000/08/13 02:50:35 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -54,6 +54,7 @@ #include "executor/spi.h" #include "executor/spi_priv.h" #include "fmgr.h" +#include "parser/parse_expr.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -2487,6 +2488,9 @@ exec_simple_check_node(Node *node) case T_Const: return TRUE; + case T_RelabelType: + return exec_simple_check_node(((RelabelType *) node)->arg); + default: return FALSE; } @@ -2563,26 +2567,7 @@ exec_simple_check_plan(PLpgSQL_expr * expr) * ---------- */ expr->plan_simple_expr = tle->expr; - - switch (nodeTag(tle->expr)) - { - case T_Expr: - expr->plan_simple_type = - ((Expr *) (tle->expr))->typeOid; - break; - - case T_Param: - expr->plan_simple_type = - ((Param *) (tle->expr))->paramtype; - break; - - case T_Const: - expr->plan_simple_type = ((Const *) (tle->expr))->consttype; - break; - - default: - expr->plan_simple_type = InvalidOid; - } + expr->plan_simple_type = exprType(tle->expr); }