1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-02 04:21:28 +03:00

Implement SQL-compliant treatment of row comparisons for < <= > >= cases

(previously we only did = and <> correctly).  Also, allow row comparisons
with any operators that are in btree opclasses, not only those with these
specific names.  This gets rid of a whole lot of indefensible assumptions
about the behavior of particular operators based on their names ... though
it's still true that IN and NOT IN expand to "= ANY".  The patch adds a
RowCompareExpr expression node type, and makes some changes in the
representation of ANY/ALL/ROWCOMPARE SubLinks so that they can share code
with RowCompareExpr.

I have not yet done anything about making RowCompareExpr an indexable
operator, but will look at that soon.

initdb forced due to changes in stored rules.
This commit is contained in:
Tom Lane
2005-12-28 01:30:02 +00:00
parent a37422e042
commit 6e07709760
26 changed files with 1452 additions and 659 deletions

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.102 2005/11/26 22:14:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.103 2005/12/28 01:29:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -19,14 +19,12 @@
#include "nodes/makefuncs.h"
#include "nodes/params.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/subselect.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
@@ -74,6 +72,12 @@ typedef struct PlannerParamItem
} PlannerParamItem;
typedef struct convert_testexpr_context
{
int rtindex; /* RT index for Vars, or 0 for Params */
List *righthandIds; /* accumulated list of Vars or Param IDs */
} convert_testexpr_context;
typedef struct finalize_primnode_context
{
Bitmapset *paramids; /* Set of PARAM_EXEC paramids found */
@@ -81,10 +85,13 @@ typedef struct finalize_primnode_context
} finalize_primnode_context;
static List *convert_sublink_opers(List *lefthand, List *operOids,
List *targetlist, int rtindex,
List **righthandIds);
static Node *convert_testexpr(Node *testexpr,
int rtindex,
List **righthandIds);
static Node *convert_testexpr_mutator(Node *node,
convert_testexpr_context *context);
static bool subplan_is_hashable(SubLink *slink, SubPlan *node);
static bool hash_ok_operator(OpExpr *expr);
static Node *replace_correlation_vars_mutator(Node *node, void *context);
static Node *process_sublinks_mutator(Node *node, bool *isTopQual);
static Bitmapset *finalize_plan(Plan *plan, List *rtable,
@@ -228,20 +235,20 @@ generate_new_param(Oid paramtype, int32 paramtypmod)
}
/*
* Convert a bare SubLink (as created by the parser) into a SubPlan.
* Convert a SubLink (as created by the parser) into a SubPlan.
*
* We are given the raw SubLink and the already-processed lefthand argument
* list (use this instead of the SubLink's own field). We are also told if
* We are given the original SubLink and the already-processed testexpr
* (use this instead of the SubLink's own field). We are also told if
* this expression appears at top level of a WHERE/HAVING qual.
*
* The result is whatever we need to substitute in place of the SubLink
* node in the executable expression. This will be either the SubPlan
* node (if we have to do the subplan as a subplan), or a Param node
* representing the result of an InitPlan, or possibly an AND or OR tree
* containing InitPlan Param nodes.
* representing the result of an InitPlan, or a row comparison expression
* tree containing InitPlan Param nodes.
*/
static Node *
make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
make_subplan(SubLink *slink, Node *testexpr, bool isTopQual)
{
SubPlan *node = makeNode(SubPlan);
Query *subquery = (Query *) (slink->subselect);
@@ -264,7 +271,7 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
* first tuple will be retrieved. For ALL and ANY subplans, we will be
* able to stop evaluating if the test condition fails, so very often not
* all the tuples will be retrieved; for lack of a better idea, specify
* 50% retrieval. For EXPR and MULTIEXPR subplans, use default behavior
* 50% retrieval. For EXPR and ROWCOMPARE subplans, use default behavior
* (we're only expecting one row out, anyway).
*
* NOTE: if you change these numbers, also change cost_qual_eval_walker()
@@ -300,8 +307,7 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
* Initialize other fields of the SubPlan node.
*/
node->subLinkType = slink->subLinkType;
node->useOr = slink->useOr;
node->exprs = NIL;
node->testexpr = NULL;
node->paramIds = NIL;
node->useHashTable = false;
/* At top level of a qual, can treat UNKNOWN the same as FALSE */
@@ -326,11 +332,11 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
/*
* Un-correlated or undirect correlated plans of EXISTS, EXPR, ARRAY, or
* MULTIEXPR types can be used as initPlans. For EXISTS, EXPR, or ARRAY,
* ROWCOMPARE types can be used as initPlans. For EXISTS, EXPR, or ARRAY,
* we just produce a Param referring to the result of evaluating the
* initPlan. For MULTIEXPR, we must build an AND or OR-clause of the
* individual comparison operators, using the appropriate lefthand side
* expressions and Params for the initPlan's target items.
* initPlan. For ROWCOMPARE, we must modify the testexpr tree to contain
* PARAM_EXEC Params instead of the PARAM_SUBLINK Params emitted by the
* parser.
*/
if (node->parParam == NIL && slink->subLinkType == EXISTS_SUBLINK)
{
@@ -369,34 +375,30 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
PlannerInitPlan = lappend(PlannerInitPlan, node);
result = (Node *) prm;
}
else if (node->parParam == NIL && slink->subLinkType == MULTIEXPR_SUBLINK)
else if (node->parParam == NIL && slink->subLinkType == ROWCOMPARE_SUBLINK)
{
List *exprs;
/* Convert the lefthand exprs and oper OIDs into executable exprs */
exprs = convert_sublink_opers(lefthand,
slink->operOids,
plan->targetlist,
0,
&node->paramIds);
/* Adjust the Params */
result = convert_testexpr(testexpr,
0,
&node->paramIds);
node->setParam = list_copy(node->paramIds);
PlannerInitPlan = lappend(PlannerInitPlan, node);
/*
* The executable expressions are returned to become part of the outer
* plan's expression tree; they are not kept in the initplan node.
* The executable expression is returned to become part of the outer
* plan's expression tree; it is not kept in the initplan node.
*/
if (list_length(exprs) > 1)
result = (Node *) (node->useOr ? make_orclause(exprs) :
make_andclause(exprs));
else
result = (Node *) linitial(exprs);
}
else
{
List *args;
ListCell *l;
/* Adjust the Params */
node->testexpr = convert_testexpr(testexpr,
0,
&node->paramIds);
/*
* We can't convert subplans of ALL_SUBLINK or ANY_SUBLINK types to
* initPlans, even when they are uncorrelated or undirect correlated,
@@ -434,13 +436,6 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
node->plan = plan = materialize_finished_plan(plan);
}
/* Convert the lefthand exprs and oper OIDs into executable exprs */
node->exprs = convert_sublink_opers(lefthand,
slink->operOids,
plan->targetlist,
0,
&node->paramIds);
/*
* Make node->args from parParam.
*/
@@ -465,10 +460,9 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
}
/*
* convert_sublink_opers: given a lefthand-expressions list and a list of
* operator OIDs, build a list of actually executable expressions. The
* righthand sides of the expressions are Params or Vars representing the
* results of the sub-select.
* convert_testexpr: convert the testexpr given by the parser into
* actually executable form. This entails replacing PARAM_SUBLINK Params
* with Params or Vars representing the results of the sub-select:
*
* If rtindex is 0, we build Params to represent the sub-select outputs.
* The paramids of the Params created are returned in the *righthandIds list.
@@ -476,90 +470,84 @@ make_subplan(SubLink *slink, List *lefthand, bool isTopQual)
* If rtindex is not 0, we build Vars using that rtindex as varno. Copies
* of the Var nodes are returned in *righthandIds (this is a bit of a type
* cheat, but we can get away with it).
*
* The given testexpr has already been recursively processed by
* process_sublinks_mutator. Hence it can no longer contain any
* PARAM_SUBLINK Params for lower SubLink nodes; we can safely assume that
* any we find are for our own level of SubLink.
*/
static List *
convert_sublink_opers(List *lefthand, List *operOids,
List *targetlist, int rtindex,
List **righthandIds)
static Node *
convert_testexpr(Node *testexpr,
int rtindex,
List **righthandIds)
{
List *result = NIL;
ListCell *l,
*lefthand_item,
*tlist_item;
*righthandIds = NIL;
lefthand_item = list_head(lefthand);
tlist_item = list_head(targetlist);
foreach(l, operOids)
{
Oid opid = lfirst_oid(l);
Node *leftop = (Node *) lfirst(lefthand_item);
TargetEntry *te = (TargetEntry *) lfirst(tlist_item);
Node *rightop;
Operator tup;
Assert(!te->resjunk);
if (rtindex)
{
/* Make the Var node representing the subplan's result */
rightop = (Node *) makeVar(rtindex,
te->resno,
exprType((Node *) te->expr),
exprTypmod((Node *) te->expr),
0);
/*
* Copy it for caller. NB: we need a copy to avoid having
* doubly-linked substructure in the modified parse tree.
*/
*righthandIds = lappend(*righthandIds, copyObject(rightop));
}
else
{
/* Make the Param node representing the subplan's result */
Param *prm;
prm = generate_new_param(exprType((Node *) te->expr),
exprTypmod((Node *) te->expr));
/* Record its ID */
*righthandIds = lappend_int(*righthandIds, prm->paramid);
rightop = (Node *) prm;
}
/* Look up the operator to pass to make_op_expr */
tup = SearchSysCache(OPEROID,
ObjectIdGetDatum(opid),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for operator %u", opid);
/*
* Make the expression node.
*
* Note: we use make_op_expr in case runtime type conversion function
* calls must be inserted for this operator! (But we are not
* expecting to have to resolve unknown Params, so it's okay to pass a
* null pstate.)
*/
result = lappend(result,
make_op_expr(NULL,
tup,
leftop,
rightop,
exprType(leftop),
exprType((Node *) te->expr)));
ReleaseSysCache(tup);
lefthand_item = lnext(lefthand_item);
tlist_item = lnext(tlist_item);
}
Node *result;
convert_testexpr_context context;
context.rtindex = rtindex;
context.righthandIds = NIL;
result = convert_testexpr_mutator(testexpr, &context);
*righthandIds = context.righthandIds;
return result;
}
static Node *
convert_testexpr_mutator(Node *node,
convert_testexpr_context *context)
{
if (node == NULL)
return NULL;
if (IsA(node, Param))
{
Param *param = (Param *) node;
if (param->paramkind == PARAM_SUBLINK)
{
/*
* We expect to encounter the Params in column-number sequence.
* We could handle non-sequential order if necessary, but for now
* there's no need. (This is also a useful cross-check that we
* aren't finding any unexpected Params.)
*/
if (param->paramid != list_length(context->righthandIds) + 1)
elog(ERROR, "unexpected PARAM_SUBLINK ID: %d", param->paramid);
if (context->rtindex)
{
/* Make the Var node representing the subplan's result */
Var *newvar;
newvar = makeVar(context->rtindex,
param->paramid,
param->paramtype,
-1,
0);
/*
* Copy it for caller. NB: we need a copy to avoid having
* doubly-linked substructure in the modified parse tree.
*/
context->righthandIds = lappend(context->righthandIds,
copyObject(newvar));
return (Node *) newvar;
}
else
{
/* Make the Param node representing the subplan's result */
Param *newparam;
newparam = generate_new_param(param->paramtype, -1);
/* Record its ID */
context->righthandIds = lappend_int(context->righthandIds,
newparam->paramid);
return (Node *) newparam;
}
}
}
return expression_tree_mutator(node,
convert_testexpr_mutator,
(void *) context);
}
/*
* subplan_is_hashable: decide whether we can implement a subplan by hashing
*
@@ -573,15 +561,19 @@ subplan_is_hashable(SubLink *slink, SubPlan *node)
ListCell *l;
/*
* The sublink type must be "= ANY" --- that is, an IN operator. (We
* require the operator name to be unqualified, which may be overly
* paranoid, or may not be.) XXX since we also check that the operators
* are hashable, the test on operator name may be redundant?
* The sublink type must be "= ANY" --- that is, an IN operator. We
* expect that the test expression will be either a single OpExpr, or an
* AND-clause containing OpExprs. (If it's anything else then the parser
* must have determined that the operators have non-equality-like
* semantics. In the OpExpr case we can't be sure what the operator's
* semantics are like, but the test below for hashability will reject
* anything that's not equality.)
*/
if (slink->subLinkType != ANY_SUBLINK)
return false;
if (list_length(slink->operName) != 1 ||
strcmp(strVal(linitial(slink->operName)), "=") != 0)
if (slink->testexpr == NULL ||
(!IsA(slink->testexpr, OpExpr) &&
!and_clause(slink->testexpr)))
return false;
/*
@@ -614,26 +606,47 @@ subplan_is_hashable(SubLink *slink, SubPlan *node)
* could be relaxed by using two different sets of operators with the hash
* table, but there is no obvious usefulness to that at present.)
*/
foreach(l, slink->operOids)
if (IsA(slink->testexpr, OpExpr))
{
Oid opid = lfirst_oid(l);
HeapTuple tup;
Form_pg_operator optup;
tup = SearchSysCache(OPEROID,
ObjectIdGetDatum(opid),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for operator %u", opid);
optup = (Form_pg_operator) GETSTRUCT(tup);
if (!optup->oprcanhash || optup->oprcom != opid ||
!func_strict(optup->oprcode))
{
ReleaseSysCache(tup);
if (!hash_ok_operator((OpExpr *) slink->testexpr))
return false;
}
ReleaseSysCache(tup);
}
else
{
foreach(l, ((BoolExpr *) slink->testexpr)->args)
{
Node *andarg = (Node *) lfirst(l);
if (!IsA(andarg, OpExpr))
return false; /* probably can't happen */
if (!hash_ok_operator((OpExpr *) andarg))
return false;
}
}
return true;
}
static bool
hash_ok_operator(OpExpr *expr)
{
Oid opid = expr->opno;
HeapTuple tup;
Form_pg_operator optup;
tup = SearchSysCache(OPEROID,
ObjectIdGetDatum(opid),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for operator %u", opid);
optup = (Form_pg_operator) GETSTRUCT(tup);
if (!optup->oprcanhash || optup->oprcom != opid ||
!func_strict(optup->oprcode))
{
ReleaseSysCache(tup);
return false;
}
ReleaseSysCache(tup);
return true;
}
@@ -659,17 +672,28 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
RangeTblEntry *rte;
RangeTblRef *rtr;
InClauseInfo *ininfo;
List *exprs;
/*
* The sublink type must be "= ANY" --- that is, an IN operator. (We
* require the operator name to be unqualified, which may be overly
* paranoid, or may not be.)
* The sublink type must be "= ANY" --- that is, an IN operator. We
* expect that the test expression will be either a single OpExpr, or an
* AND-clause containing OpExprs. (If it's anything else then the parser
* must have determined that the operators have non-equality-like
* semantics. In the OpExpr case we can't be sure what the operator's
* semantics are like, and must check for ourselves.)
*/
if (sublink->subLinkType != ANY_SUBLINK)
return NULL;
if (list_length(sublink->operName) != 1 ||
strcmp(strVal(linitial(sublink->operName)), "=") != 0)
if (sublink->testexpr && IsA(sublink->testexpr, OpExpr))
{
List *opclasses;
List *opstrats;
get_op_btree_interpretation(((OpExpr *) sublink->testexpr)->opno,
&opclasses, &opstrats);
if (!list_member_int(opstrats, ROWCOMPARE_EQ))
return NULL;
}
else if (!and_clause(sublink->testexpr))
return NULL;
/*
@@ -683,16 +707,14 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
* The left-hand expressions must contain some Vars of the current query,
* else it's not gonna be a join.
*/
left_varnos = pull_varnos((Node *) sublink->lefthand);
left_varnos = pull_varnos(sublink->testexpr);
if (bms_is_empty(left_varnos))
return NULL;
/*
* The left-hand expressions mustn't be volatile. (Perhaps we should test
* the combining operators, too? We'd only need to point the function
* directly at the sublink ...)
* The combining operators and left-hand expressions mustn't be volatile.
*/
if (contain_volatile_functions((Node *) sublink->lefthand))
if (contain_volatile_functions(sublink->testexpr))
return NULL;
/*
@@ -722,16 +744,13 @@ convert_IN_to_join(PlannerInfo *root, SubLink *sublink)
root->in_info_list = lappend(root->in_info_list, ininfo);
/*
* Build the result qual expressions. As a side effect,
* Build the result qual expression. As a side effect,
* ininfo->sub_targetlist is filled with a list of Vars representing the
* subselect outputs.
*/
exprs = convert_sublink_opers(sublink->lefthand,
sublink->operOids,
subselect->targetList,
rtindex,
&ininfo->sub_targetlist);
return (Node *) make_ands_explicit(exprs);
return convert_testexpr(sublink->testexpr,
rtindex,
&ininfo->sub_targetlist);
}
/*
@@ -802,19 +821,18 @@ process_sublinks_mutator(Node *node, bool *isTopQual)
if (IsA(node, SubLink))
{
SubLink *sublink = (SubLink *) node;
List *lefthand;
Node *testexpr;
/*
* First, recursively process the lefthand-side expressions, if any.
*/
locTopQual = false;
lefthand = (List *)
process_sublinks_mutator((Node *) sublink->lefthand, &locTopQual);
testexpr = process_sublinks_mutator(sublink->testexpr, &locTopQual);
/*
* Now build the SubPlan node and make the expr to return.
*/
return make_subplan(sublink, lefthand, *isTopQual);
return make_subplan(sublink, testexpr, *isTopQual);
}
/*