From 6e07709760a29d8dbfb93b9846c905bd40689082 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 28 Dec 2005 01:30:02 +0000 Subject: [PATCH] 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. --- doc/src/sgml/func.sgml | 158 +++++---- src/backend/catalog/dependency.c | 31 +- src/backend/executor/execQual.c | 148 +++++++- src/backend/executor/nodeSubplan.c | 126 +++---- src/backend/nodes/copyfuncs.c | 29 +- src/backend/nodes/equalfuncs.c | 25 +- src/backend/nodes/outfuncs.c | 24 +- src/backend/nodes/readfuncs.c | 25 +- src/backend/optimizer/path/costsize.c | 12 +- src/backend/optimizer/plan/subselect.c | 356 +++++++++---------- src/backend/optimizer/util/clauses.c | 64 ++-- src/backend/parser/gram.y | 23 +- src/backend/parser/parse_expr.c | 450 ++++++++++++++++--------- src/backend/parser/parse_oper.c | 7 +- src/backend/utils/adt/ruleutils.c | 160 ++++++--- src/backend/utils/cache/lsyscache.c | 144 +++++++- src/include/catalog/catversion.h | 4 +- src/include/nodes/execnodes.h | 16 +- src/include/nodes/nodes.h | 4 +- src/include/nodes/params.h | 8 +- src/include/nodes/primnodes.h | 118 ++++--- src/include/parser/parse_oper.h | 5 +- src/include/utils/lsyscache.h | 6 +- src/pl/plpgsql/src/pl_exec.c | 14 +- src/test/regress/expected/rowtypes.out | 122 +++++++ src/test/regress/sql/rowtypes.sql | 32 ++ 26 files changed, 1452 insertions(+), 659 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 86e01ff113..d90bc15d41 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ @@ -350,18 +350,18 @@ PostgreSQL documentation The ordinary comparison operators yield null (signifying unknown) when either input is null. Another way to do comparisons is with the - IS DISTINCT FROM construct: + IS NOT DISTINCT FROM construct: expression IS DISTINCT FROM expression expression IS NOT DISTINCT FROM expression - For non-null inputs, IS DISTINCT FROM this is + For non-null inputs, IS DISTINCT FROM is the same as the <> operator. However, when both inputs are null it will return false, and when just one input is null it will return true. Similarly, IS NOT DISTINCT FROM is identical to = for non-null - inputs, returns true when both inputs are null, and false - otherwise. Thus, these constructs effectively act as though null + inputs, but it returns true when both inputs are null, and false when only + one input is null. Thus, these constructs effectively act as though null were a normal data value, rather than unknown. @@ -7999,8 +7999,8 @@ SELECT col1 FROM tab1 equal if all their corresponding members are non-null and equal; the rows are unequal if any corresponding members are non-null and unequal; otherwise the result of that row comparison is unknown (null). - If all the row results are either unequal or null, with at least one null, - then the result of IN is null. + If all the per-row results are either unequal or null, with at least one + null, then the result of IN is null. @@ -8055,8 +8055,8 @@ SELECT col1 FROM tab1 equal if all their corresponding members are non-null and equal; the rows are unequal if any corresponding members are non-null and unequal; otherwise the result of that row comparison is unknown (null). - If all the row results are either unequal or null, with at least one null, - then the result of NOT IN is null. + If all the per-row results are either unequal or null, with at least one + null, then the result of NOT IN is null. @@ -8109,23 +8109,19 @@ SELECT col1 FROM tab1 subquery, which must return exactly as many columns as there are expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result, - using the given operator. Presently, - only = and <> operators are allowed - in row-wise ANY constructs. - The result of ANY is true if any equal or unequal row is - found, respectively. - The result is false if no such row is found (including the special - case where the subquery returns no rows). + using the given operator. + The result of ANY is true if the comparison + returns true for any subquery row. + The result is false if the comparison returns false for every + subquery row (including the special case where the subquery returns no + rows). + The result is NULL if the comparison does not return true for any row, + and it returns NULL for at least one row. - As usual, null values in the rows are combined per - the normal rules of SQL Boolean expressions. Two rows are considered - equal if all their corresponding members are non-null and equal; the rows - are unequal if any corresponding members are non-null and unequal; - otherwise the result of that row comparison is unknown (null). - If there is at least one null row result, then the result of ANY - cannot be false; it will be true or null. + See for details about the meaning + of a row-wise comparison. @@ -8145,20 +8141,14 @@ SELECT col1 FROM tab1 The result of ALL is true if all rows yield true (including the special case where the subquery returns no rows). The result is false if any false result is found. + The result is NULL if the comparison does not return false for any row, + and it returns NULL for at least one row. NOT IN is equivalent to <> ALL. - - Note that if there are no failures but at least one right-hand row yields - null for the operator's result, the result of the ALL construct - will be null, not true. - This is in accordance with SQL's normal rules for Boolean combinations - of null values. - - As with EXISTS, it's unwise to assume that the subquery will be evaluated completely. @@ -8175,24 +8165,19 @@ SELECT col1 FROM tab1 subquery, which must return exactly as many columns as there are expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result, - using the given operator. Presently, - only = and <> operators are allowed - in row-wise ALL queries. - The result of ALL is true if all subquery rows are equal - or unequal, respectively (including the special + using the given operator. + The result of ALL is true if the comparison + returns true for all subquery rows (including the special case where the subquery returns no rows). - The result is false if any row is found to be unequal or equal, - respectively. + The result is false if the comparison returns false for any + subquery row. + The result is NULL if the comparison does not return false for any + subquery row, and it returns NULL for at least one row. - As usual, null values in the rows are combined per - the normal rules of SQL Boolean expressions. Two rows are considered - equal if all their corresponding members are non-null and equal; the rows - are unequal if any corresponding members are non-null and unequal; - otherwise the result of that row comparison is unknown (null). - If there is at least one null row result, then the result of ALL - cannot be true; it will be false or null. + See for details about the meaning + of a row-wise comparison. @@ -8216,17 +8201,11 @@ SELECT col1 FROM tab1 the subquery cannot return more than one row. (If it returns zero rows, the result is taken to be null.) The left-hand side is evaluated and compared row-wise to the single subquery result row. - Presently, only = and <> operators are allowed - in row-wise comparisons. - The result is true if the two rows are equal or unequal, respectively. - As usual, null values in the rows are combined per - the normal rules of SQL Boolean expressions. Two rows are considered - equal if all their corresponding members are non-null and equal; the rows - are unequal if any corresponding members are non-null and unequal; - otherwise the result of the row comparison is unknown (null). + See for details about the meaning + of a row-wise comparison. @@ -8255,6 +8234,10 @@ SELECT col1 FROM tab1 SOME + + row-wise comparison + + comparison row-wise @@ -8264,6 +8247,10 @@ SELECT col1 FROM tab1 IS DISTINCT FROM + + IS NOT DISTINCT FROM + + IS NULL @@ -8288,7 +8275,7 @@ SELECT col1 FROM tab1 <literal>IN</literal> -expression IN (value, ...) +expression IN (value , ...) @@ -8319,7 +8306,7 @@ OR <literal>NOT IN</literal> -expression NOT IN (value, ...) +expression NOT IN (value , ...) @@ -8425,7 +8412,7 @@ AND - + Row-wise Comparison @@ -8436,23 +8423,52 @@ AND Each side is a row constructor, as described in . The two row values must have the same number of fields. - Each side is evaluated and they are compared row-wise. - Presently, only = and <> operators are allowed - in row-wise comparisons. - The result is true if the two rows are equal or unequal, respectively. + Each side is evaluated and they are compared row-wise. Row comparisons + are allowed when the operator is + =, + <>, + <, + <=, + > or + >=, + or has semantics similar to one of these. (To be specific, an operator + can be a row comparison operator if it is a member of a btree operator + class, or is the negator of the = member of a btree operator + class.) - As usual, null values in the rows are combined per - the normal rules of SQL Boolean expressions. Two rows are considered + The = and <> cases work slightly differently + from the others. Two rows are considered equal if all their corresponding members are non-null and equal; the rows are unequal if any corresponding members are non-null and unequal; otherwise the result of the row comparison is unknown (null). - - IS DISTINCT FROM - + + For the <, <=, > and + >= cases, the row elements are compared left-to-right, + stopping as soon as an unequal or null pair of elements is found. + If either of this pair of elements is null, the result of the + row comparison is unknown (null); otherwise comparison of this pair + of elements determines the result. For example, + ROW(1,2,NULL) < ROW(1,3,0) + yields true, not null, because the third pair of elements are not + considered. + + + + + Prior to PostgreSQL 8.2, the + <, <=, > and >= + cases were not handled per SQL specification. A comparison like + ROW(a,b) < ROW(c,d) + was implemented as + a < c AND b < d + whereas the correct behavior is equivalent to + a < c OR (a = c AND b < d). + + row_constructor IS DISTINCT FROM row_constructor @@ -8466,6 +8482,18 @@ AND be either true or false, never null. + +row_constructor IS NOT DISTINCT FROM row_constructor + + + + This construct is similar to a = row comparison, + but it does not yield null for null inputs. Instead, any null value is + considered unequal to (distinct from) any non-null value, and any two + nulls are considered equal (not distinct). Thus the result will always + be either true or false, never null. + + row_constructor IS NULL row_constructor IS NOT NULL diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 245b8965f8..fb0dce5d23 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.48 2005/11/22 18:17:07 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.49 2005/12/28 01:29:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1129,23 +1129,28 @@ find_expr_references_walker(Node *node, &context->addrs); /* fall through to examine arguments */ } - if (IsA(node, SubLink)) - { - SubLink *sublink = (SubLink *) node; - ListCell *opid; - - foreach(opid, sublink->operOids) - { - add_object_address(OCLASS_OPERATOR, lfirst_oid(opid), 0, - &context->addrs); - } - /* fall through to examine arguments */ - } if (is_subplan(node)) { /* Extra work needed here if we ever need this case */ elog(ERROR, "already-planned subqueries not supported"); } + if (IsA(node, RowCompareExpr)) + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + ListCell *l; + + foreach(l, rcexpr->opnos) + { + add_object_address(OCLASS_OPERATOR, lfirst_oid(l), 0, + &context->addrs); + } + foreach(l, rcexpr->opclasses) + { + add_object_address(OCLASS_OPCLASS, lfirst_oid(l), 0, + &context->addrs); + } + /* fall through to examine arguments */ + } if (IsA(node, Query)) { /* Recurse into RTE subquery or not-yet-planned sublink subquery */ diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 2df9f1685c..fe78a0fa08 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.186 2005/12/14 16:28:32 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.187 2005/12/28 01:29:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/nbtree.h" #include "catalog/pg_type.h" #include "commands/typecmds.h" #include "executor/execdebug.h" @@ -104,6 +105,9 @@ static Datum ExecEvalArray(ArrayExprState *astate, static Datum ExecEvalRow(RowExprState *rstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRowCompare(RowCompareExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -2306,6 +2310,76 @@ ExecEvalRow(RowExprState *rstate, return HeapTupleGetDatum(tuple); } +/* ---------------------------------------------------------------- + * ExecEvalRowCompare - ROW() comparison-op ROW() + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRowCompare(RowCompareExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + bool result; + RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype; + int32 cmpresult = 0; + ListCell *l; + ListCell *r; + int i; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + i = 0; + forboth(l, rstate->largs, r, rstate->rargs) + { + ExprState *le = (ExprState *) lfirst(l); + ExprState *re = (ExprState *) lfirst(r); + FunctionCallInfoData locfcinfo; + + InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2, + NULL, NULL); + locfcinfo.arg[0] = ExecEvalExpr(le, econtext, + &locfcinfo.argnull[0], NULL); + locfcinfo.arg[1] = ExecEvalExpr(re, econtext, + &locfcinfo.argnull[1], NULL); + if (rstate->funcs[i].fn_strict && + (locfcinfo.argnull[0] || locfcinfo.argnull[1])) + return (Datum) 0; /* force NULL result */ + locfcinfo.isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); + if (locfcinfo.isnull) + return (Datum) 0; /* force NULL result */ + if (cmpresult != 0) + break; /* no need to compare remaining columns */ + i++; + } + + switch (rctype) + { + /* EQ and NE cases aren't allowed here */ + case ROWCOMPARE_LT: + result = (cmpresult < 0); + break; + case ROWCOMPARE_LE: + result = (cmpresult <= 0); + break; + case ROWCOMPARE_GE: + result = (cmpresult >= 0); + break; + case ROWCOMPARE_GT: + result = (cmpresult > 0); + break; + default: + elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype); + result = 0; /* keep compiler quiet */ + break; + } + + *isNull = false; + return BoolGetDatum(result); +} + /* ---------------------------------------------------------------- * ExecEvalCoalesce * ---------------------------------------------------------------- @@ -3118,8 +3192,8 @@ ExecInitExpr(Expr *node, PlanState *parent) sstate->sub_estate = NULL; sstate->planstate = NULL; - sstate->exprs = (List *) - ExecInitExpr((Expr *) subplan->exprs, parent); + sstate->testexpr = + ExecInitExpr((Expr *) subplan->testexpr, parent); sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent); @@ -3336,6 +3410,66 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) rstate; } break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + RowCompareExprState *rstate = makeNode(RowCompareExprState); + int nopers = list_length(rcexpr->opnos); + List *outlist; + ListCell *l; + ListCell *l2; + int i; + + rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare; + Assert(list_length(rcexpr->largs) == nopers); + outlist = NIL; + foreach(l, rcexpr->largs) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->largs = outlist; + Assert(list_length(rcexpr->rargs) == nopers); + outlist = NIL; + foreach(l, rcexpr->rargs) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->rargs = outlist; + Assert(list_length(rcexpr->opclasses) == nopers); + rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo)); + i = 0; + forboth(l, rcexpr->opnos, l2, rcexpr->opclasses) + { + Oid opno = lfirst_oid(l); + Oid opclass = lfirst_oid(l2); + int strategy; + Oid subtype; + bool recheck; + Oid proc; + + get_op_opclass_properties(opno, opclass, + &strategy, &subtype, &recheck); + proc = get_opclass_proc(opclass, subtype, BTORDER_PROC); + /* + * If we enforced permissions checks on index support + * functions, we'd need to make a check here. But the + * index support machinery doesn't do that, and neither + * does this code. + */ + fmgr_info(proc, &(rstate->funcs[i])); + i++; + } + state = (ExprState *) rstate; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; @@ -3382,6 +3516,12 @@ ExecInitExpr(Expr *node, PlanState *parent) (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("could not identify a comparison function for type %s", format_type_be(minmaxexpr->minmaxtype)))); + /* + * If we enforced permissions checks on index support + * functions, we'd need to make a check here. But the + * index support machinery doesn't do that, and neither + * does this code. + */ fmgr_info(typentry->cmp_proc, &(mstate->cfunc)); state = (ExprState *) mstate; } @@ -3484,7 +3624,7 @@ ExecInitExprInitPlan(SubPlan *node, PlanState *parent) sstate->sub_estate = NULL; sstate->planstate = NULL; - sstate->exprs = (List *) ExecInitExpr((Expr *) node->exprs, parent); + sstate->testexpr = ExecInitExpr((Expr *) node->testexpr, parent); sstate->args = (List *) ExecInitExpr((Expr *) node->args, parent); sstate->xprstate.expr = (Expr *) node; diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index e35430d28b..80679d9f63 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.71 2005/11/22 18:17:10 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.72 2005/12/28 01:29:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,6 +23,7 @@ #include "executor/executor.h" #include "executor/nodeSubplan.h" #include "nodes/makefuncs.h" +#include "optimizer/clauses.h" #include "parser/parse_expr.h" #include "utils/array.h" #include "utils/datum.h" @@ -205,7 +206,6 @@ ExecScanSubPlan(SubPlanState *node, SubPlan *subplan = (SubPlan *) node->xprstate.expr; PlanState *planstate = node->planstate; SubLinkType subLinkType = subplan->subLinkType; - bool useOr = subplan->useOr; MemoryContext oldcontext; TupleTableSlot *slot; Datum result; @@ -245,15 +245,13 @@ ExecScanSubPlan(SubPlanState *node, /* * For all sublink types except EXPR_SUBLINK and ARRAY_SUBLINK, the result * is boolean as are the results of the combining operators. We combine - * results within a tuple (if there are multiple columns) using OR - * semantics if "useOr" is true, AND semantics if not. We then combine * results across tuples (if the subplan produces more than one) using OR * semantics for ANY_SUBLINK or AND semantics for ALL_SUBLINK. - * (MULTIEXPR_SUBLINK doesn't allow multiple tuples from the subplan.) + * (ROWCOMPARE_SUBLINK doesn't allow multiple tuples from the subplan.) * NULL results from the combining operators are handled according to the * usual SQL semantics for OR and AND. The result for no input tuples is * FALSE for ANY_SUBLINK, TRUE for ALL_SUBLINK, NULL for - * MULTIEXPR_SUBLINK. + * ROWCOMPARE_SUBLINK. * * For EXPR_SUBLINK we require the subplan to produce no more than one * tuple, else an error is raised. For ARRAY_SUBLINK we allow the subplan @@ -269,9 +267,9 @@ ExecScanSubPlan(SubPlanState *node, slot = ExecProcNode(planstate)) { TupleDesc tdesc = slot->tts_tupleDescriptor; - Datum rowresult = BoolGetDatum(!useOr); - bool rownull = false; - int col = 1; + Datum rowresult; + bool rownull; + int col; ListCell *plst; if (subLinkType == EXISTS_SUBLINK) @@ -304,7 +302,7 @@ ExecScanSubPlan(SubPlanState *node, node->curTuple = ExecCopySlotTuple(slot); MemoryContextSwitchTo(node->sub_estate->es_query_cxt); - result = heap_getattr(node->curTuple, col, tdesc, isNull); + result = heap_getattr(node->curTuple, 1, tdesc, isNull); /* keep scanning subplan to make sure there's only one tuple */ continue; } @@ -324,8 +322,8 @@ ExecScanSubPlan(SubPlanState *node, continue; } - /* cannot allow multiple input tuples for MULTIEXPR sublink either */ - if (subLinkType == MULTIEXPR_SUBLINK && found) + /* cannot allow multiple input tuples for ROWCOMPARE sublink either */ + if (subLinkType == ROWCOMPARE_SUBLINK && found) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("more than one row returned by a subquery used as an expression"))); @@ -333,69 +331,25 @@ ExecScanSubPlan(SubPlanState *node, found = true; /* - * For ALL, ANY, and MULTIEXPR sublinks, iterate over combining - * operators for columns of tuple. + * For ALL, ANY, and ROWCOMPARE sublinks, load up the Params + * representing the columns of the sub-select, and then evaluate + * the combining expression. */ - Assert(list_length(node->exprs) == list_length(subplan->paramIds)); - - forboth(l, node->exprs, plst, subplan->paramIds) + col = 1; + foreach(plst, subplan->paramIds) { - ExprState *exprstate = (ExprState *) lfirst(l); int paramid = lfirst_int(plst); ParamExecData *prmdata; - Datum expresult; - bool expnull; - /* - * Load up the Param representing this column of the sub-select. - */ prmdata = &(econtext->ecxt_param_exec_vals[paramid]); Assert(prmdata->execPlan == NULL); - prmdata->value = slot_getattr(slot, col, - &(prmdata->isnull)); - - /* - * Now we can eval the combining operator for this column. - */ - expresult = ExecEvalExprSwitchContext(exprstate, econtext, - &expnull, NULL); - - /* - * Combine the result into the row result as appropriate. - */ - if (col == 1) - { - rowresult = expresult; - rownull = expnull; - } - else if (useOr) - { - /* combine within row per OR semantics */ - if (expnull) - rownull = true; - else if (DatumGetBool(expresult)) - { - rowresult = BoolGetDatum(true); - rownull = false; - break; /* needn't look at any more columns */ - } - } - else - { - /* combine within row per AND semantics */ - if (expnull) - rownull = true; - else if (!DatumGetBool(expresult)) - { - rowresult = BoolGetDatum(false); - rownull = false; - break; /* needn't look at any more columns */ - } - } - + prmdata->value = slot_getattr(slot, col, &(prmdata->isnull)); col++; } + rowresult = ExecEvalExprSwitchContext(node->testexpr, econtext, + &rownull, NULL); + if (subLinkType == ANY_SUBLINK) { /* combine across rows per OR semantics */ @@ -422,7 +376,7 @@ ExecScanSubPlan(SubPlanState *node, } else { - /* must be MULTIEXPR_SUBLINK */ + /* must be ROWCOMPARE_SUBLINK */ result = rowresult; *isNull = rownull; } @@ -433,11 +387,11 @@ ExecScanSubPlan(SubPlanState *node, /* * deal with empty subplan result. result/isNull were previously * initialized correctly for all sublink types except EXPR, ARRAY, and - * MULTIEXPR; for those, return NULL. + * ROWCOMPARE; for those, return NULL. */ if (subLinkType == EXPR_SUBLINK || subLinkType == ARRAY_SUBLINK || - subLinkType == MULTIEXPR_SUBLINK) + subLinkType == ROWCOMPARE_SUBLINK) { result = (Datum) 0; *isNull = true; @@ -463,7 +417,7 @@ buildSubPlanHash(SubPlanState *node) { SubPlan *subplan = (SubPlan *) node->xprstate.expr; PlanState *planstate = node->planstate; - int ncols = list_length(node->exprs); + int ncols = list_length(subplan->paramIds); ExprContext *innerecontext = node->innerecontext; MemoryContext tempcxt = innerecontext->ecxt_per_tuple_memory; MemoryContext oldcontext; @@ -471,7 +425,6 @@ buildSubPlanHash(SubPlanState *node) TupleTableSlot *slot; Assert(subplan->subLinkType == ANY_SUBLINK); - Assert(!subplan->useOr); /* * If we already had any hash tables, destroy 'em; then create empty hash @@ -764,11 +717,12 @@ ExecInitSubPlan(SubPlanState *node, EState *estate) TupleDesc tupDesc; TupleTable tupTable; TupleTableSlot *slot; - List *lefttlist, + List *oplist, + *lefttlist, *righttlist, *leftptlist, *rightptlist; - ListCell *lexpr; + ListCell *l; /* We need a memory context to hold the hash table(s) */ node->tablecxt = @@ -780,7 +734,7 @@ ExecInitSubPlan(SubPlanState *node, EState *estate) /* and a short-lived exprcontext for function evaluation */ node->innerecontext = CreateExprContext(estate); /* Silly little array of column numbers 1..n */ - ncols = list_length(node->exprs); + ncols = list_length(subplan->paramIds); node->keyColIdx = (AttrNumber *) palloc(ncols * sizeof(AttrNumber)); for (i = 0; i < ncols; i++) node->keyColIdx[i] = i + 1; @@ -799,14 +753,34 @@ ExecInitSubPlan(SubPlanState *node, EState *estate) * We also extract the combining operators themselves to initialize * the equality and hashing functions for the hash tables. */ + if (IsA(node->testexpr->expr, OpExpr)) + { + /* single combining operator */ + oplist = list_make1(node->testexpr); + } + else if (and_clause((Node *) node->testexpr->expr)) + { + /* multiple combining operators */ + Assert(IsA(node->testexpr, BoolExprState)); + oplist = ((BoolExprState *) node->testexpr)->args; + } + else + { + /* shouldn't see anything else in a hashable subplan */ + elog(ERROR, "unrecognized testexpr type: %d", + (int) nodeTag(node->testexpr->expr)); + oplist = NIL; /* keep compiler quiet */ + } + Assert(list_length(oplist) == ncols); + lefttlist = righttlist = NIL; leftptlist = rightptlist = NIL; node->eqfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo)); node->hashfunctions = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo)); i = 1; - foreach(lexpr, node->exprs) + foreach(l, oplist) { - FuncExprState *fstate = (FuncExprState *) lfirst(lexpr); + FuncExprState *fstate = (FuncExprState *) lfirst(l); OpExpr *opexpr = (OpExpr *) fstate->xprstate.expr; ExprState *exstate; Expr *expr; @@ -967,7 +941,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) if (found && (subLinkType == EXPR_SUBLINK || - subLinkType == MULTIEXPR_SUBLINK)) + subLinkType == ROWCOMPARE_SUBLINK)) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("more than one row returned by a subquery used as an expression"))); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1d816ead3a..7a16cbcff5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.323 2005/12/20 02:30:35 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.324 2005/12/28 01:29:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -862,10 +862,8 @@ _copySubLink(SubLink *from) SubLink *newnode = makeNode(SubLink); COPY_SCALAR_FIELD(subLinkType); - COPY_SCALAR_FIELD(useOr); - COPY_NODE_FIELD(lefthand); + COPY_NODE_FIELD(testexpr); COPY_NODE_FIELD(operName); - COPY_NODE_FIELD(operOids); COPY_NODE_FIELD(subselect); return newnode; @@ -880,8 +878,7 @@ _copySubPlan(SubPlan *from) SubPlan *newnode = makeNode(SubPlan); COPY_SCALAR_FIELD(subLinkType); - COPY_SCALAR_FIELD(useOr); - COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(testexpr); COPY_NODE_FIELD(paramIds); COPY_NODE_FIELD(plan); COPY_SCALAR_FIELD(plan_id); @@ -1033,6 +1030,23 @@ _copyRowExpr(RowExpr *from) return newnode; } +/* + * _copyRowCompareExpr + */ +static RowCompareExpr * +_copyRowCompareExpr(RowCompareExpr *from) +{ + RowCompareExpr *newnode = makeNode(RowCompareExpr); + + COPY_SCALAR_FIELD(rctype); + COPY_NODE_FIELD(opnos); + COPY_NODE_FIELD(opclasses); + COPY_NODE_FIELD(largs); + COPY_NODE_FIELD(rargs); + + return newnode; +} + /* * _copyCoalesceExpr */ @@ -2876,6 +2890,9 @@ copyObject(void *from) case T_RowExpr: retval = _copyRowExpr(from); break; + case T_RowCompareExpr: + retval = _copyRowCompareExpr(from); + break; case T_CoalesceExpr: retval = _copyCoalesceExpr(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 824a7ff82c..b006cec150 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.259 2005/12/20 02:30:35 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.260 2005/12/28 01:29:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -156,6 +156,7 @@ _equalParam(Param *a, Param *b) break; case PARAM_NUM: case PARAM_EXEC: + case PARAM_SUBLINK: COMPARE_SCALAR_FIELD(paramid); break; default: @@ -295,10 +296,8 @@ static bool _equalSubLink(SubLink *a, SubLink *b) { COMPARE_SCALAR_FIELD(subLinkType); - COMPARE_SCALAR_FIELD(useOr); - COMPARE_NODE_FIELD(lefthand); + COMPARE_NODE_FIELD(testexpr); COMPARE_NODE_FIELD(operName); - COMPARE_NODE_FIELD(operOids); COMPARE_NODE_FIELD(subselect); return true; @@ -308,8 +307,7 @@ static bool _equalSubPlan(SubPlan *a, SubPlan *b) { COMPARE_SCALAR_FIELD(subLinkType); - COMPARE_SCALAR_FIELD(useOr); - COMPARE_NODE_FIELD(exprs); + COMPARE_NODE_FIELD(testexpr); COMPARE_NODE_FIELD(paramIds); /* should compare plans, but have to settle for comparing plan IDs */ COMPARE_SCALAR_FIELD(plan_id); @@ -440,6 +438,18 @@ _equalRowExpr(RowExpr *a, RowExpr *b) return true; } +static bool +_equalRowCompareExpr(RowCompareExpr *a, RowCompareExpr *b) +{ + COMPARE_SCALAR_FIELD(rctype); + COMPARE_NODE_FIELD(opnos); + COMPARE_NODE_FIELD(opclasses); + COMPARE_NODE_FIELD(largs); + COMPARE_NODE_FIELD(rargs); + + return true; +} + static bool _equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b) { @@ -1919,6 +1929,9 @@ equal(void *a, void *b) case T_RowExpr: retval = _equalRowExpr(a, b); break; + case T_RowCompareExpr: + retval = _equalRowCompareExpr(a, b); + break; case T_CoalesceExpr: retval = _equalCoalesceExpr(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index aa5fd99db8..b60eab31fd 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.265 2005/12/20 02:30:35 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.266 2005/12/28 01:29:59 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -736,10 +736,8 @@ _outSubLink(StringInfo str, SubLink *node) WRITE_NODE_TYPE("SUBLINK"); WRITE_ENUM_FIELD(subLinkType, SubLinkType); - WRITE_BOOL_FIELD(useOr); - WRITE_NODE_FIELD(lefthand); + WRITE_NODE_FIELD(testexpr); WRITE_NODE_FIELD(operName); - WRITE_NODE_FIELD(operOids); WRITE_NODE_FIELD(subselect); } @@ -749,8 +747,7 @@ _outSubPlan(StringInfo str, SubPlan *node) WRITE_NODE_TYPE("SUBPLAN"); WRITE_ENUM_FIELD(subLinkType, SubLinkType); - WRITE_BOOL_FIELD(useOr); - WRITE_NODE_FIELD(exprs); + WRITE_NODE_FIELD(testexpr); WRITE_NODE_FIELD(paramIds); WRITE_NODE_FIELD(plan); WRITE_INT_FIELD(plan_id); @@ -855,6 +852,18 @@ _outRowExpr(StringInfo str, RowExpr *node) WRITE_ENUM_FIELD(row_format, CoercionForm); } +static void +_outRowCompareExpr(StringInfo str, RowCompareExpr *node) +{ + WRITE_NODE_TYPE("ROWCOMPARE"); + + WRITE_ENUM_FIELD(rctype, RowCompareType); + WRITE_NODE_FIELD(opnos); + WRITE_NODE_FIELD(opclasses); + WRITE_NODE_FIELD(largs); + WRITE_NODE_FIELD(rargs); +} + static void _outCoalesceExpr(StringInfo str, CoalesceExpr *node) { @@ -1936,6 +1945,9 @@ _outNode(StringInfo str, void *obj) case T_RowExpr: _outRowExpr(str, obj); break; + case T_RowCompareExpr: + _outRowCompareExpr(str, obj); + break; case T_CoalesceExpr: _outCoalesceExpr(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 46c9983446..eb2886d843 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.182 2005/10/15 02:49:19 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.183 2005/12/28 01:29:59 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -494,10 +494,8 @@ _readSubLink(void) READ_LOCALS(SubLink); READ_ENUM_FIELD(subLinkType, SubLinkType); - READ_BOOL_FIELD(useOr); - READ_NODE_FIELD(lefthand); + READ_NODE_FIELD(testexpr); READ_NODE_FIELD(operName); - READ_NODE_FIELD(operOids); READ_NODE_FIELD(subselect); READ_DONE(); @@ -645,6 +643,23 @@ _readRowExpr(void) READ_DONE(); } +/* + * _readRowCompareExpr + */ +static RowCompareExpr * +_readRowCompareExpr(void) +{ + READ_LOCALS(RowCompareExpr); + + READ_ENUM_FIELD(rctype, RowCompareType); + READ_NODE_FIELD(opnos); + READ_NODE_FIELD(opclasses); + READ_NODE_FIELD(largs); + READ_NODE_FIELD(rargs); + + READ_DONE(); +} + /* * _readCoalesceExpr */ @@ -996,6 +1011,8 @@ parseNodeString(void) return_value = _readArrayExpr(); else if (MATCH("ROW", 3)) return_value = _readRowExpr(); + else if (MATCH("ROWCOMPARE", 10)) + return_value = _readRowCompareExpr(); else if (MATCH("COALESCE", 8)) return_value = _readCoalesceExpr(); else if (MATCH("MINMAX", 6)) diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index e45e454a37..c258accd8e 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -49,7 +49,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.151 2005/11/26 22:14:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.152 2005/12/28 01:29:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1609,6 +1609,13 @@ cost_qual_eval_walker(Node *node, QualCost *total) total->per_tuple += cpu_operator_cost * estimate_array_length(arraynode) * 0.5; } + else if (IsA(node, RowCompareExpr)) + { + /* Conservatively assume we will check all the columns */ + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + + total->per_tuple += cpu_operator_cost * list_length(rcexpr->opnos); + } else if (IsA(node, SubLink)) { /* This routine should not be applied to un-planned expressions */ @@ -1624,7 +1631,6 @@ cost_qual_eval_walker(Node *node, QualCost *total) * * An exception occurs when we have decided we can implement the * subplan by hashing. - * */ SubPlan *subplan = (SubPlan *) node; Plan *plan = subplan->plan; @@ -1643,7 +1649,7 @@ cost_qual_eval_walker(Node *node, QualCost *total) /* * The per-tuple costs include the cost of evaluating the lefthand * expressions, plus the cost of probing the hashtable. Recursion - * into the exprs list will handle the lefthand expressions + * into the testexpr will handle the lefthand expressions * properly, and will count one cpu_operator_cost for each * comparison operator. That is probably too low for the probing * cost, but it's hard to make a better estimate, so live with it diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 5775b0521f..5efbb10f03 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -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); } /* diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 2cdb3b3573..2b6583c1da 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.204 2005/12/20 02:30:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.205 2005/12/28 01:30:00 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -540,6 +540,8 @@ expression_returns_set_walker(Node *node, void *context) return false; if (IsA(node, RowExpr)) return false; + if (IsA(node, RowCompareExpr)) + return false; if (IsA(node, CoalesceExpr)) return false; if (IsA(node, MinMaxExpr)) @@ -651,12 +653,12 @@ contain_mutable_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } - if (IsA(node, SubLink)) + if (IsA(node, RowCompareExpr)) { - SubLink *sublink = (SubLink *) node; + RowCompareExpr *rcexpr = (RowCompareExpr *) node; ListCell *opid; - foreach(opid, sublink->operOids) + foreach(opid, rcexpr->opnos) { if (op_volatile(lfirst_oid(opid)) != PROVOLATILE_IMMUTABLE) return true; @@ -734,12 +736,13 @@ contain_volatile_functions_walker(Node *node, void *context) return true; /* else fall through to check args */ } - if (IsA(node, SubLink)) + if (IsA(node, RowCompareExpr)) { - SubLink *sublink = (SubLink *) node; + /* RowCompare probably can't have volatile ops, but check anyway */ + RowCompareExpr *rcexpr = (RowCompareExpr *) node; ListCell *opid; - foreach(opid, sublink->operOids) + foreach(opid, rcexpr->opnos) { if (op_volatile(lfirst_oid(opid)) == PROVOLATILE_VOLATILE) return true; @@ -847,6 +850,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; if (IsA(node, RowExpr)) return true; + if (IsA(node, RowCompareExpr)) + return true; if (IsA(node, CoalesceExpr)) return true; if (IsA(node, MinMaxExpr)) @@ -2857,8 +2862,8 @@ evaluate_expr(Expr *expr, Oid result_type) * FromExpr, JoinExpr, and SetOperationStmt nodes are handled, so that query * jointrees and setOperation trees can be processed without additional code. * - * expression_tree_walker will handle SubLink nodes by recursing normally into - * the "lefthand" arguments (which are expressions belonging to the outer + * expression_tree_walker will handle SubLink nodes by recursing normally + * into the "testexpr" subtree (which is an expression belonging to the outer * plan). It will also call the walker on the sub-Query node; however, when * expression_tree_walker itself is called on a Query node, it does nothing * and returns "false". The net effect is that unless the walker does @@ -2882,7 +2887,7 @@ evaluate_expr(Expr *expr, Oid result_type) * walker on all the expression subtrees of the given Query node. * * expression_tree_walker will handle SubPlan nodes by recursing normally - * into the "exprs" and "args" lists (which are expressions belonging to + * into the "testexpr" and the "args" list (which are expressions belonging to * the outer plan). It will not touch the completed subplan, however. Since * there is no link to the original Query, it is not possible to recurse into * subselects of an already-planned expression tree. This is OK for current @@ -2992,7 +2997,7 @@ expression_tree_walker(Node *node, { SubLink *sublink = (SubLink *) node; - if (expression_tree_walker((Node *) sublink->lefthand, + if (expression_tree_walker(sublink->testexpr, walker, context)) return true; @@ -3007,8 +3012,8 @@ expression_tree_walker(Node *node, { SubPlan *subplan = (SubPlan *) node; - /* recurse into the exprs list, but not into the Plan */ - if (expression_tree_walker((Node *) subplan->exprs, + /* recurse into the testexpr, but not into the Plan */ + if (expression_tree_walker(subplan->testexpr, walker, context)) return true; /* also examine args list */ @@ -3058,6 +3063,16 @@ expression_tree_walker(Node *node, return walker(((ArrayExpr *) node)->elements, context); case T_RowExpr: return walker(((RowExpr *) node)->args, context); + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + + if (walker(rcexpr->largs, context)) + return true; + if (walker(rcexpr->rargs, context)) + return true; + } + break; case T_CoalesceExpr: return walker(((CoalesceExpr *) node)->args, context); case T_MinMaxExpr: @@ -3263,7 +3278,7 @@ range_table_walker(List *rtable, * and qualifier clauses during the planning stage. * * expression_tree_mutator will handle SubLink nodes by recursing normally - * into the "lefthand" arguments (which are expressions belonging to the outer + * into the "testexpr" subtree (which is an expression belonging to the outer * plan). It will also call the mutator on the sub-Query node; however, when * expression_tree_mutator itself is called on a Query node, it does nothing * and returns the unmodified Query node. The net effect is that unless the @@ -3272,8 +3287,8 @@ range_table_walker(List *rtable, * SubLink node. Mutators that want to descend into sub-selects will usually * do so by recognizing Query nodes and calling query_tree_mutator (below). * - * expression_tree_mutator will handle a SubPlan node by recursing into - * the "exprs" and "args" lists (which belong to the outer plan), but it + * expression_tree_mutator will handle a SubPlan node by recursing into the + * "testexpr" and the "args" list (which belong to the outer plan), but it * will simply copy the link to the inner plan, since that's typically what * expression tree mutators want. A mutator that wants to modify the subplan * can force appropriate behavior by recognizing SubPlan expression nodes @@ -3404,7 +3419,7 @@ expression_tree_mutator(Node *node, SubLink *newnode; FLATCOPY(newnode, sublink, SubLink); - MUTATE(newnode->lefthand, sublink->lefthand, List *); + MUTATE(newnode->testexpr, sublink->testexpr, Node *); /* * Also invoke the mutator on the sublink's Query node, so it @@ -3420,8 +3435,8 @@ expression_tree_mutator(Node *node, SubPlan *newnode; FLATCOPY(newnode, subplan, SubPlan); - /* transform exprs list */ - MUTATE(newnode->exprs, subplan->exprs, List *); + /* transform testexpr */ + MUTATE(newnode->testexpr, subplan->testexpr, Node *); /* transform args list (params to be passed to subplan) */ MUTATE(newnode->args, subplan->args, List *); /* but not the sub-Plan itself, which is referenced as-is */ @@ -3513,6 +3528,17 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + RowCompareExpr *newnode; + + FLATCOPY(newnode, rcexpr, RowCompareExpr); + MUTATE(newnode->largs, rcexpr->largs, List *); + MUTATE(newnode->rargs, rcexpr->rargs, List *); + return (Node *) newnode; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6c0145e298..201a972e09 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.519 2005/12/27 04:00:07 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.520 2005/12/28 01:30:00 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -6774,10 +6774,7 @@ a_expr: c_expr { $$ = $1; } /* generate foo = ANY (subquery) */ SubLink *n = (SubLink *) $3; n->subLinkType = ANY_SUBLINK; - if (IsA($1, RowExpr)) - n->lefthand = ((RowExpr *) $1)->args; - else - n->lefthand = list_make1($1); + n->testexpr = $1; n->operName = list_make1(makeString("=")); $$ = (Node *)n; } @@ -6796,10 +6793,7 @@ a_expr: c_expr { $$ = $1; } /* Make an = ANY node */ SubLink *n = (SubLink *) $4; n->subLinkType = ANY_SUBLINK; - if (IsA($1, RowExpr)) - n->lefthand = ((RowExpr *) $1)->args; - else - n->lefthand = list_make1($1); + n->testexpr = $1; n->operName = list_make1(makeString("=")); /* Stick a NOT on top */ $$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, (Node *) n); @@ -6814,10 +6808,7 @@ a_expr: c_expr { $$ = $1; } { SubLink *n = makeNode(SubLink); n->subLinkType = $3; - if (IsA($1, RowExpr)) - n->lefthand = ((RowExpr *) $1)->args; - else - n->lefthand = list_make1($1); + n->testexpr = $1; n->operName = $2; n->subselect = $4; $$ = (Node *)n; @@ -6950,7 +6941,7 @@ c_expr: columnref { $$ = $1; } { SubLink *n = makeNode(SubLink); n->subLinkType = EXPR_SUBLINK; - n->lefthand = NIL; + n->testexpr = NULL; n->operName = NIL; n->subselect = $1; $$ = (Node *)n; @@ -6959,7 +6950,7 @@ c_expr: columnref { $$ = $1; } { SubLink *n = makeNode(SubLink); n->subLinkType = EXISTS_SUBLINK; - n->lefthand = NIL; + n->testexpr = NULL; n->operName = NIL; n->subselect = $2; $$ = (Node *)n; @@ -6968,7 +6959,7 @@ c_expr: columnref { $$ = $1; } { SubLink *n = makeNode(SubLink); n->subLinkType = ARRAY_SUBLINK; - n->lefthand = NIL; + n->testexpr = NULL; n->operName = NIL; n->subselect = $2; $$ = (Node *)n; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index ece78b2182..923d357ee1 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,21 +8,20 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.188 2005/11/28 04:35:31 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.189 2005/12/28 01:30:00 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_proc.h" #include "commands/dbcommands.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/params.h" #include "nodes/plannodes.h" +#include "optimizer/clauses.h" #include "parser/analyze.h" #include "parser/gramparse.h" #include "parser/parse_coerce.h" @@ -33,7 +32,7 @@ #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" -#include "utils/syscache.h" + bool Transform_null_equals = false; @@ -64,8 +63,8 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); static Node *typecast_expression(ParseState *pstate, Node *expr, TypeName *typename); -static Node *make_row_op(ParseState *pstate, List *opname, - RowExpr *lrow, RowExpr *rrow); +static Node *make_row_comparison_op(ParseState *pstate, List *opname, + List *largs, List *rargs); static Node *make_row_distinct_op(ParseState *pstate, List *opname, RowExpr *lrow, RowExpr *rrow); static Expr *make_distinct_op(ParseState *pstate, List *opname, @@ -592,14 +591,14 @@ transformAExprOp(ParseState *pstate, A_Expr *a) ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK) { /* - * Convert "row op subselect" into a MULTIEXPR sublink. Formerly the + * Convert "row op subselect" into a ROWCOMPARE sublink. Formerly the * grammar did this, but now that a row construct is allowed anywhere * in expressions, it's easier to do it here. */ SubLink *s = (SubLink *) rexpr; - s->subLinkType = MULTIEXPR_SUBLINK; - s->lefthand = ((RowExpr *) lexpr)->args; + s->subLinkType = ROWCOMPARE_SUBLINK; + s->testexpr = lexpr; s->operName = a->name; result = transformExpr(pstate, (Node *) s); } @@ -612,10 +611,10 @@ transformAExprOp(ParseState *pstate, A_Expr *a) Assert(IsA(lexpr, RowExpr)); Assert(IsA(rexpr, RowExpr)); - result = make_row_op(pstate, - a->name, - (RowExpr *) lexpr, - (RowExpr *) rexpr); + result = make_row_comparison_op(pstate, + a->name, + ((RowExpr *) lexpr)->args, + ((RowExpr *) rexpr)->args); } else { @@ -885,10 +884,10 @@ transformAExprIn(ParseState *pstate, A_Expr *a) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("arguments of row IN must all be row expressions"))); - cmp = make_row_op(pstate, - a->name, - (RowExpr *) copyObject(lexpr), - (RowExpr *) rexpr); + cmp = make_row_comparison_op(pstate, + a->name, + (List *) copyObject(((RowExpr *) lexpr)->args), + ((RowExpr *) rexpr)->args); } else cmp = (Node *) make_op(pstate, @@ -1080,13 +1079,11 @@ transformSubLink(ParseState *pstate, SubLink *sublink) if (sublink->subLinkType == EXISTS_SUBLINK) { /* - * EXISTS needs no lefthand or combining operator. These fields - * should be NIL already, but make sure. + * EXISTS needs no test expression or combining operator. + * These fields should be null already, but make sure. */ - sublink->lefthand = NIL; + sublink->testexpr = NULL; sublink->operName = NIL; - sublink->operOids = NIL; - sublink->useOr = FALSE; } else if (sublink->subLinkType == EXPR_SUBLINK || sublink->subLinkType == ARRAY_SUBLINK) @@ -1111,128 +1108,72 @@ transformSubLink(ParseState *pstate, SubLink *sublink) } /* - * EXPR and ARRAY need no lefthand or combining operator. These fields - * should be NIL already, but make sure. + * EXPR and ARRAY need no test expression or combining operator. + * These fields should be null already, but make sure. */ - sublink->lefthand = NIL; + sublink->testexpr = NULL; sublink->operName = NIL; - sublink->operOids = NIL; - sublink->useOr = FALSE; } else { - /* ALL, ANY, or MULTIEXPR: generate operator list */ - List *left_list = sublink->lefthand; - List *right_list = qtree->targetList; - int row_length = list_length(left_list); - bool needNot = false; - List *op = sublink->operName; - char *opname = strVal(llast(op)); + /* ALL, ANY, or ROWCOMPARE: generate row-comparing expression */ + Node *lefthand; + List *left_list; + List *right_list; ListCell *l; - ListCell *ll_item; - - /* transform lefthand expressions */ - foreach(l, left_list) - lfirst(l) = transformExpr(pstate, lfirst(l)); /* - * If the expression is "<> ALL" (with unqualified opname) then - * convert it to "NOT IN". This is a hack to improve efficiency of - * expressions output by pre-7.4 Postgres. + * Transform lefthand expression, and convert to a list */ - if (sublink->subLinkType == ALL_SUBLINK && - list_length(op) == 1 && strcmp(opname, "<>") == 0) - { - sublink->subLinkType = ANY_SUBLINK; - opname = pstrdup("="); - op = list_make1(makeString(opname)); - sublink->operName = op; - needNot = true; - } - - /* Set useOr if op is "<>" (possibly qualified) */ - if (strcmp(opname, "<>") == 0) - sublink->useOr = TRUE; + lefthand = transformExpr(pstate, sublink->testexpr); + if (lefthand && IsA(lefthand, RowExpr)) + left_list = ((RowExpr *) lefthand)->args; else - sublink->useOr = FALSE; - - /* Combining operators other than =/<> is dubious... */ - if (row_length != 1 && - strcmp(opname, "=") != 0 && - strcmp(opname, "<>") != 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("row comparison cannot use operator %s", - opname))); + left_list = list_make1(lefthand); /* - * To build the list of combining operator OIDs, we must scan - * subquery's targetlist to find values that will be matched against - * lefthand values. We need to ignore resjunk targets, so doing the - * outer iteration over right_list is easier than doing it over - * left_list. + * Build a list of PARAM_SUBLINK nodes representing the + * output columns of the subquery. */ - sublink->operOids = NIL; - - ll_item = list_head(left_list); - foreach(l, right_list) + right_list = NIL; + foreach(l, qtree->targetList) { TargetEntry *tent = (TargetEntry *) lfirst(l); - Node *lexpr; - Operator optup; - Form_pg_operator opform; + Param *param; if (tent->resjunk) continue; - if (ll_item == NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery has too many columns"))); - lexpr = lfirst(ll_item); - ll_item = lnext(ll_item); + param = makeNode(Param); + param->paramkind = PARAM_SUBLINK; + param->paramid = (AttrNumber) tent->resno; + param->paramtype = exprType((Node *) tent->expr); - /* - * It's OK to use oper() not compatible_oper() here, because - * make_subplan() will insert type coercion calls if needed. - */ - optup = oper(op, - exprType(lexpr), - exprType((Node *) tent->expr), - false); - opform = (Form_pg_operator) GETSTRUCT(optup); - - if (opform->oprresult != BOOLOID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("operator %s must return type boolean, not type %s", - opname, - format_type_be(opform->oprresult)), - errhint("The operator of a quantified predicate subquery must return type boolean."))); - - if (get_func_retset(opform->oprcode)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("operator %s must not return a set", - opname), - errhint("The operator of a quantified predicate subquery must return type boolean."))); - - sublink->operOids = lappend_oid(sublink->operOids, - oprid(optup)); - - ReleaseSysCache(optup); + right_list = lappend(right_list, param); } - if (ll_item != NULL) + + /* + * We could rely on make_row_comparison_op to complain if the + * list lengths differ, but we prefer to generate a more specific + * error message. + */ + if (list_length(left_list) < list_length(right_list)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery has too many columns"))); + if (list_length(left_list) > list_length(right_list)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("subquery has too few columns"))); - if (needNot) - { - result = coerce_to_boolean(pstate, result, "NOT"); - result = (Node *) makeBoolExpr(NOT_EXPR, - list_make1(result)); - } + /* + * Identify the combining operator(s) and generate a suitable + * row-comparison expression. + */ + sublink->testexpr = make_row_comparison_op(pstate, + sublink->operName, + left_list, + right_list); } return result; @@ -1673,6 +1614,9 @@ exprType(Node *expr) case T_RowExpr: type = ((RowExpr *) expr)->row_typeid; break; + case T_RowCompareExpr: + type = BOOLOID; + break; case T_CoalesceExpr: type = ((CoalesceExpr *) expr)->coalescetype; break; @@ -1953,76 +1897,258 @@ typecast_expression(ParseState *pstate, Node *expr, TypeName *typename) } /* - * Transform a "row op row" construct + * Transform a "row compare-op row" construct * - * The input RowExprs are already transformed + * The inputs are lists of already-transformed expressions. + * As with coerce_type, pstate may be NULL if no special unknown-Param + * processing is wanted. + * + * The output may be a single OpExpr, an AND or OR combination of OpExprs, + * or a RowCompareExpr. In all cases it is guaranteed to return boolean. + * The AND, OR, and RowCompareExpr cases further imply things about the + * behavior of the operators (ie, they behave as =, <>, or < <= > >=). */ static Node * -make_row_op(ParseState *pstate, List *opname, - RowExpr *lrow, RowExpr *rrow) +make_row_comparison_op(ParseState *pstate, List *opname, + List *largs, List *rargs) { - Node *result = NULL; - List *largs = lrow->args; - List *rargs = rrow->args; + RowCompareExpr *rcexpr; + RowCompareType rctype; + List *opexprs; + List *opnos; + List *opclasses; ListCell *l, *r; - char *oprname; - BoolExprType boolop; + List **opclass_lists; + List **opstrat_lists; + Bitmapset *strats; + int nopers; + int i; - if (list_length(largs) != list_length(rargs)) + nopers = list_length(largs); + if (nopers != list_length(rargs)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unequal number of entries in row expressions"))); /* - * XXX it's really wrong to generate a simple AND combination for < <= > - * >=. We probably need to invent a new runtime node type to handle those - * correctly. For the moment, though, keep on doing this ... + * We can't compare zero-length rows because there is no principled + * basis for figuring out what the operator is. */ - oprname = strVal(llast(opname)); - - if ((strcmp(oprname, "=") == 0) || - (strcmp(oprname, "<") == 0) || - (strcmp(oprname, "<=") == 0) || - (strcmp(oprname, ">") == 0) || - (strcmp(oprname, ">=") == 0)) - boolop = AND_EXPR; - else if (strcmp(oprname, "<>") == 0) - boolop = OR_EXPR; - else - { + if (nopers == 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("operator %s is not supported for row expressions", - oprname))); - boolop = 0; /* keep compiler quiet */ - } + errmsg("cannot compare rows of zero length"))); + /* + * Identify all the pairwise operators, using make_op so that + * behavior is the same as in the simple scalar case. + */ + opexprs = NIL; forboth(l, largs, r, rargs) { Node *larg = (Node *) lfirst(l); Node *rarg = (Node *) lfirst(r); - Node *cmp; + OpExpr *cmp; - cmp = (Node *) make_op(pstate, opname, larg, rarg); - cmp = coerce_to_boolean(pstate, cmp, "row comparison"); - if (result == NULL) - result = cmp; - else - result = (Node *) makeBoolExpr(boolop, - list_make2(result, cmp)); + cmp = (OpExpr *) make_op(pstate, opname, larg, rarg); + Assert(IsA(cmp, OpExpr)); + + /* + * We don't use coerce_to_boolean here because we insist on the + * operator yielding boolean directly, not via coercion. If it + * doesn't yield bool it won't be in any index opclasses... + */ + if (cmp->opresulttype != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("row comparison operator must yield type boolean, " + "not type %s", + format_type_be(cmp->opresulttype)))); + if (expression_returns_set((Node *) cmp)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("row comparison operator must not return a set"))); + opexprs = lappend(opexprs, cmp); } - if (result == NULL) + /* + * If rows are length 1, just return the single operator. In this + * case we don't insist on identifying btree semantics for the operator + * (but we still require it to return boolean). + */ + if (nopers == 1) + return (Node *) linitial(opexprs); + + /* + * Now we must determine which row comparison semantics (= <> < <= > >=) + * apply to this set of operators. We look for btree opclasses containing + * the operators, and see which interpretations (strategy numbers) exist + * for each operator. + */ + opclass_lists = (List **) palloc(nopers * sizeof(List *)); + opstrat_lists = (List **) palloc(nopers * sizeof(List *)); + strats = NULL; + i = 0; + foreach(l, opexprs) { - /* zero-length rows? Generate constant TRUE or FALSE */ - if (boolop == AND_EXPR) - result = makeBoolConst(true, false); + Bitmapset *this_strats; + ListCell *j; + + get_op_btree_interpretation(((OpExpr *) lfirst(l))->opno, + &opclass_lists[i], &opstrat_lists[i]); + /* + * convert strategy number list to a Bitmapset to make the intersection + * calculation easy. + */ + this_strats = NULL; + foreach(j, opstrat_lists[i]) + { + this_strats = bms_add_member(this_strats, lfirst_int(j)); + } + if (i == 0) + strats = this_strats; else - result = makeBoolConst(false, false); + strats = bms_int_members(strats, this_strats); + i++; } - return result; + switch (bms_membership(strats)) + { + case BMS_EMPTY_SET: + /* No common interpretation, so fail */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine interpretation of row comparison operator %s", + strVal(llast(opname))), + errhint("Row comparison operators must be associated with btree operator classes."))); + rctype = 0; /* keep compiler quiet */ + break; + case BMS_SINGLETON: + /* Simple case: just one possible interpretation */ + rctype = bms_singleton_member(strats); + break; + case BMS_MULTIPLE: + default: /* keep compiler quiet */ + { + /* + * Prefer the interpretation with the most default opclasses. + */ + int best_defaults = 0; + bool multiple_best = false; + int this_rctype; + + rctype = 0; /* keep compiler quiet */ + while ((this_rctype = bms_first_member(strats)) >= 0) + { + int ndefaults = 0; + + for (i = 0; i < nopers; i++) + { + forboth(l, opclass_lists[i], r, opstrat_lists[i]) + { + Oid opclass = lfirst_oid(l); + int opstrat = lfirst_int(r); + + if (opstrat == this_rctype && + opclass_is_default(opclass)) + ndefaults++; + } + } + if (ndefaults > best_defaults) + { + best_defaults = ndefaults; + rctype = this_rctype; + multiple_best = false; + } + else if (ndefaults == best_defaults) + multiple_best = true; + } + if (best_defaults == 0 || multiple_best) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine interpretation of row comparison operator %s", + strVal(llast(opname))), + errdetail("There are multiple equally-plausible candidates."))); + break; + } + } + + /* + * For = and <> cases, we just combine the pairwise operators with + * AND or OR respectively. + * + * Note: this is presently the only place where the parser generates + * BoolExpr with more than two arguments. Should be OK since the + * rest of the system thinks BoolExpr is N-argument anyway. + */ + if (rctype == ROWCOMPARE_EQ) + return (Node *) makeBoolExpr(AND_EXPR, opexprs); + if (rctype == ROWCOMPARE_NE) + return (Node *) makeBoolExpr(OR_EXPR, opexprs); + + /* + * Otherwise we need to determine exactly which opclass to associate + * with each operator. + */ + opclasses = NIL; + for (i = 0; i < nopers; i++) + { + Oid best_opclass = 0; + int ndefault = 0; + int nmatch = 0; + + forboth(l, opclass_lists[i], r, opstrat_lists[i]) + { + Oid opclass = lfirst_oid(l); + int opstrat = lfirst_int(r); + + if (opstrat == rctype) + { + if (ndefault == 0) + best_opclass = opclass; + if (opclass_is_default(opclass)) + ndefault++; + else + nmatch++; + } + } + if (ndefault == 1 || (ndefault == 0 && nmatch == 1)) + opclasses = lappend_oid(opclasses, best_opclass); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine interpretation of row comparison operator %s", + strVal(llast(opname))), + errdetail("There are multiple equally-plausible candidates."))); + } + + /* + * Now deconstruct the OpExprs and create a RowCompareExpr. + * + * Note: can't just reuse the passed largs/rargs lists, because of + * possibility that make_op inserted coercion operations. + */ + opnos = NIL; + largs = NIL; + rargs = NIL; + foreach(l, opexprs) + { + OpExpr *cmp = (OpExpr *) lfirst(l); + + opnos = lappend_oid(opnos, cmp->opno); + largs = lappend(largs, linitial(cmp->args)); + rargs = lappend(rargs, lsecond(cmp->args)); + } + + rcexpr = makeNode(RowCompareExpr); + rcexpr->rctype = rctype; + rcexpr->opnos = opnos; + rcexpr->opclasses = opclasses; + rcexpr->largs = largs; + rcexpr->rargs = rargs; + + return (Node *) rcexpr; } /* diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c index c83f7b4c4c..22fccc6ce8 100644 --- a/src/backend/parser/parse_oper.c +++ b/src/backend/parser/parse_oper.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.83 2005/11/22 18:17:16 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.84 2005/12/28 01:30:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -39,6 +39,9 @@ static const char *op_signature_string(List *op, char oprkind, Oid arg1, Oid arg2); static void op_error(List *op, char oprkind, Oid arg1, Oid arg2, FuncDetailCode fdresult); +static Expr *make_op_expr(ParseState *pstate, Operator op, + Node *ltree, Node *rtree, + Oid ltypeId, Oid rtypeId); /* @@ -942,7 +945,7 @@ make_scalar_array_op(ParseState *pstate, List *opname, * As with coerce_type, pstate may be NULL if no special unknown-Param * processing is wanted. */ -Expr * +static Expr * make_op_expr(ParseState *pstate, Operator op, Node *ltree, Node *rtree, Oid ltypeId, Oid rtypeId) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5a102e5fed..eaf4f19507 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.210 2005/12/10 19:21:03 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.211 2005/12/28 01:30:00 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -215,7 +215,6 @@ static void printSubscripts(ArrayRef *aref, deparse_context *context); static char *generate_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes); static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2); -static void print_operator_name(StringInfo buf, List *opname); static text *string_to_text(char *str); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -3106,6 +3105,7 @@ get_rule_expr(Node *node, deparse_context *context, break; case PARAM_NUM: case PARAM_EXEC: + case PARAM_SUBLINK: appendStringInfo(buf, "$%d", param->paramid); break; default: @@ -3514,6 +3514,50 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + ListCell *arg; + char *sep; + + /* + * SQL99 allows "ROW" to be omitted when there is more than + * one column, but for simplicity we always print it. + */ + appendStringInfo(buf, "(ROW("); + sep = ""; + foreach(arg, rcexpr->largs) + { + Node *e = (Node *) lfirst(arg); + + appendStringInfoString(buf, sep); + get_rule_expr(e, context, true); + sep = ", "; + } + /* + * We assume that the name of the first-column operator + * will do for all the rest too. This is definitely + * open to failure, eg if some but not all operators + * were renamed since the construct was parsed, but there + * seems no way to be perfect. + */ + appendStringInfo(buf, ") %s ROW(", + generate_operator_name(linitial_oid(rcexpr->opnos), + exprType(linitial(rcexpr->largs)), + exprType(linitial(rcexpr->rargs)))); + sep = ""; + foreach(arg, rcexpr->rargs) + { + Node *e = (Node *) lfirst(arg); + + appendStringInfoString(buf, sep); + get_rule_expr(e, context, true); + sep = ", "; + } + appendStringInfo(buf, "))"); + } + break; + case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; @@ -3967,6 +4011,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) { StringInfo buf = context->buf; Query *query = (Query *) (sublink->subselect); + char *opname = NULL; bool need_paren; if (sublink->subLinkType == ARRAY_SUBLINK) @@ -3974,25 +4019,67 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) else appendStringInfoChar(buf, '('); - if (sublink->lefthand != NIL) + /* + * Note that we print the name of only the first operator, when there + * are multiple combining operators. This is an approximation that + * could go wrong in various scenarios (operators in different schemas, + * renamed operators, etc) but there is not a whole lot we can do about + * it, since the syntax allows only one operator to be shown. + */ + if (sublink->testexpr) { - need_paren = (list_length(sublink->lefthand) > 1); - if (need_paren) + if (IsA(sublink->testexpr, OpExpr)) + { + /* single combining operator */ + OpExpr *opexpr = (OpExpr *) sublink->testexpr; + + get_rule_expr(linitial(opexpr->args), context, true); + opname = generate_operator_name(opexpr->opno, + exprType(linitial(opexpr->args)), + exprType(lsecond(opexpr->args))); + } + else if (IsA(sublink->testexpr, BoolExpr)) + { + /* multiple combining operators, = or <> cases */ + char *sep; + ListCell *l; + appendStringInfoChar(buf, '('); - get_rule_expr((Node *) sublink->lefthand, context, true); - if (need_paren) + sep = ""; + foreach(l, ((BoolExpr *) sublink->testexpr)->args) + { + OpExpr *opexpr = (OpExpr *) lfirst(l); + + Assert(IsA(opexpr, OpExpr)); + appendStringInfoString(buf, sep); + get_rule_expr(linitial(opexpr->args), context, true); + if (!opname) + opname = generate_operator_name(opexpr->opno, + exprType(linitial(opexpr->args)), + exprType(lsecond(opexpr->args))); + sep = ", "; + } appendStringInfoChar(buf, ')'); - appendStringInfoChar(buf, ' '); + } + else if (IsA(sublink->testexpr, RowCompareExpr)) + { + /* multiple combining operators, < <= > >= cases */ + RowCompareExpr *rcexpr = (RowCompareExpr *) sublink->testexpr; + + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) rcexpr->largs, context, true); + opname = generate_operator_name(linitial_oid(rcexpr->opnos), + exprType(linitial(rcexpr->largs)), + exprType(linitial(rcexpr->rargs))); + appendStringInfoChar(buf, ')'); + } + else + elog(ERROR, "unrecognized testexpr type: %d", + (int) nodeTag(sublink->testexpr)); } need_paren = true; - /* - * XXX we regurgitate the originally given operator name, with or without - * schema qualification. This is not necessarily 100% right but it's the - * best we can do, since the operators actually used might not all be in - * the same schema. - */ switch (sublink->subLinkType) { case EXISTS_SUBLINK: @@ -4000,27 +4087,18 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) break; case ANY_SUBLINK: - if (list_length(sublink->operName) == 1 && - strcmp(strVal(linitial(sublink->operName)), "=") == 0) - { - /* Represent = ANY as IN */ - appendStringInfo(buf, "IN "); - } + if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */ + appendStringInfo(buf, " IN "); else - { - print_operator_name(buf, sublink->operName); - appendStringInfo(buf, " ANY "); - } + appendStringInfo(buf, " %s ANY ", opname); break; case ALL_SUBLINK: - print_operator_name(buf, sublink->operName); - appendStringInfo(buf, " ALL "); + appendStringInfo(buf, " %s ALL ", opname); break; - case MULTIEXPR_SUBLINK: - print_operator_name(buf, sublink->operName); - appendStringInfoChar(buf, ' '); + case ROWCOMPARE_SUBLINK: + appendStringInfo(buf, " %s ", opname); break; case EXPR_SUBLINK: @@ -4812,30 +4890,6 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2) return buf.data; } -/* - * Print out a possibly-qualified operator name - */ -static void -print_operator_name(StringInfo buf, List *opname) -{ - ListCell *op = list_head(opname); - int nnames = list_length(opname); - - if (nnames == 1) - appendStringInfoString(buf, strVal(lfirst(op))); - else - { - appendStringInfo(buf, "OPERATOR("); - while (nnames-- > 1) - { - appendStringInfo(buf, "%s.", - quote_identifier(strVal(lfirst(op)))); - op = lnext(op); - } - appendStringInfo(buf, "%s)", strVal(lfirst(op))); - } -} - /* * Given a C string, produce a TEXT datum. * diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 40dd680659..d470b28f24 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 - * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.130 2005/11/17 22:14:53 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.131 2005/12/28 01:30:01 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -183,6 +183,99 @@ get_op_hash_function(Oid opno) return InvalidOid; } +/* + * get_op_btree_interpretation + * Given an operator's OID, find out which btree opclasses it belongs to, + * and what strategy number it has within each one. The results are + * returned as an OID list and a parallel integer list. + * + * In addition to the normal btree operators, we consider a <> operator to be + * a "member" of an opclass if its negator is the opclass' equality operator. + * ROWCOMPARE_NE is returned as the strategy number for this case. + */ +void +get_op_btree_interpretation(Oid opno, List **opclasses, List **opstrats) +{ + Oid lefttype, + righttype; + CatCList *catlist; + bool op_negated; + int i; + + *opclasses = NIL; + *opstrats = NIL; + + /* + * Get the nominal left-hand input type of the operator; we will ignore + * opclasses that don't have that as the expected input datatype. This + * is a kluge to avoid being confused by binary-compatible opclasses + * (such as text_ops and varchar_ops, which share the same operators). + */ + op_input_types(opno, &lefttype, &righttype); + Assert(OidIsValid(lefttype)); + + /* + * Find all the pg_amop entries containing the operator. + */ + catlist = SearchSysCacheList(AMOPOPID, 1, + ObjectIdGetDatum(opno), + 0, 0, 0); + /* + * If we can't find any opclass containing the op, perhaps it is a + * <> operator. See if it has a negator that is in an opclass. + */ + op_negated = false; + if (catlist->n_members == 0) + { + Oid op_negator = get_negator(opno); + + if (OidIsValid(op_negator)) + { + op_negated = true; + ReleaseSysCacheList(catlist); + catlist = SearchSysCacheList(AMOPOPID, 1, + ObjectIdGetDatum(op_negator), + 0, 0, 0); + } + } + + /* Now search the opclasses */ + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple op_tuple = &catlist->members[i]->tuple; + Form_pg_amop op_form = (Form_pg_amop) GETSTRUCT(op_tuple); + Oid opclass_id; + StrategyNumber op_strategy; + + opclass_id = op_form->amopclaid; + + /* must be btree */ + if (!opclass_is_btree(opclass_id)) + continue; + + /* must match operator input type exactly */ + if (get_opclass_input_type(opclass_id) != lefttype) + continue; + + /* Get the operator's btree strategy number */ + op_strategy = (StrategyNumber) op_form->amopstrategy; + Assert(op_strategy >= 1 && op_strategy <= 5); + + if (op_negated) + { + /* Only consider negators that are = */ + if (op_strategy != BTEqualStrategyNumber) + continue; + op_strategy = ROWCOMPARE_NE; + } + + *opclasses = lappend_oid(*opclasses, opclass_id); + *opstrats = lappend_int(*opstrats, op_strategy); + } + + ReleaseSysCacheList(catlist); +} + /* ---------- AMPROC CACHES ---------- */ @@ -433,6 +526,55 @@ opclass_is_hash(Oid opclass) return result; } +/* + * opclass_is_default + * + * Returns TRUE iff the specified opclass is the default for its + * index access method and input data type. + */ +bool +opclass_is_default(Oid opclass) +{ + HeapTuple tp; + Form_pg_opclass cla_tup; + bool result; + + tp = SearchSysCache(CLAOID, + ObjectIdGetDatum(opclass), + 0, 0, 0); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + cla_tup = (Form_pg_opclass) GETSTRUCT(tp); + + result = cla_tup->opcdefault; + ReleaseSysCache(tp); + return result; +} + +/* + * get_opclass_input_type + * + * Returns the OID of the datatype the opclass indexes. + */ +Oid +get_opclass_input_type(Oid opclass) +{ + HeapTuple tp; + Form_pg_opclass cla_tup; + Oid result; + + tp = SearchSysCache(CLAOID, + ObjectIdGetDatum(opclass), + 0, 0, 0); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + cla_tup = (Form_pg_opclass) GETSTRUCT(tp); + + result = cla_tup->opcintype; + ReleaseSysCache(tp); + return result; +} + /* ---------- OPERATOR CACHE ---------- */ /* diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index d2637e37eb..a7bad3dfd9 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.307 2005/11/17 22:14:54 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.308 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200511171 +#define CATALOG_VERSION_NO 200512271 #endif diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 062985aacf..d07dc57297 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.146 2005/12/02 20:03:42 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.147 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -563,7 +563,7 @@ typedef struct SubPlanState ExprState xprstate; EState *sub_estate; /* subselect plan has its own EState */ struct PlanState *planstate; /* subselect plan's state tree */ - List *exprs; /* states of combining expression(s) */ + ExprState *testexpr; /* state of combining expression */ List *args; /* states of argument expression(s) */ bool needShutdown; /* TRUE = need to shutdown subplan */ HeapTuple curTuple; /* copy of most recent tuple from subplan */ @@ -671,6 +671,18 @@ typedef struct RowExprState TupleDesc tupdesc; /* descriptor for result tuples */ } RowExprState; +/* ---------------- + * RowCompareExprState node + * ---------------- + */ +typedef struct RowCompareExprState +{ + ExprState xprstate; + List *largs; /* the left-hand input arguments */ + List *rargs; /* the right-hand input arguments */ + FmgrInfo *funcs; /* array of comparison function info */ +} RowCompareExprState; + /* ---------------- * CoalesceExprState node * ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 0d6a4871ac..2edf415583 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.179 2005/12/20 02:30:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.180 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -125,6 +125,7 @@ typedef enum NodeTag T_CaseTestExpr, T_ArrayExpr, T_RowExpr, + T_RowCompareExpr, T_CoalesceExpr, T_MinMaxExpr, T_NullIfExpr, @@ -159,6 +160,7 @@ typedef enum NodeTag T_CaseWhenState, T_ArrayExprState, T_RowExprState, + T_RowCompareExprState, T_CoalesceExprState, T_MinMaxExprState, T_CoerceToDomainState, diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h index 58e2fe691f..c328026b5f 100644 --- a/src/include/nodes/params.h +++ b/src/include/nodes/params.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.28 2004/12/31 22:03:34 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.29 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,11 @@ * PARAM_EXEC: The parameter is an internal executor parameter. * It has a number contained in the `paramid' field. * + * PARAM_SUBLINK: The parameter represents an output column of a SubLink + * node's sub-select. The column number is contained in the + * `paramid' field. (This type of Param is converted to + * PARAM_EXEC during planning.) + * * PARAM_INVALID should never appear in a Param node; it's used to mark * the end of a ParamListInfo array. * @@ -44,6 +49,7 @@ #define PARAM_NAMED 11 #define PARAM_NUM 12 #define PARAM_EXEC 15 +#define PARAM_SUBLINK 16 #define PARAM_INVALID 100 diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 40fda441b9..481f901892 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.110 2005/12/20 02:30:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.111 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -158,6 +158,11 @@ typedef struct Const * * PARAM_EXEC: The parameter is an internal executor parameter. * It has a number contained in the `paramid' field. + * + * PARAM_SUBLINK: The parameter represents an output column of a SubLink + * node's sub-select. The column number is contained in the + * `paramid' field. (This type of Param is converted to + * PARAM_EXEC during planning.) * ---------------- */ typedef struct Param @@ -329,7 +334,7 @@ typedef struct BoolExpr List *args; /* arguments to this expression */ } BoolExpr; -/* ---------------- +/* * SubLink * * A SubLink represents a subselect appearing in an expression, and in some @@ -338,46 +343,42 @@ typedef struct BoolExpr * EXISTS_SUBLINK EXISTS(SELECT ...) * ALL_SUBLINK (lefthand) op ALL (SELECT ...) * ANY_SUBLINK (lefthand) op ANY (SELECT ...) - * MULTIEXPR_SUBLINK (lefthand) op (SELECT ...) + * ROWCOMPARE_SUBLINK (lefthand) op (SELECT ...) * EXPR_SUBLINK (SELECT with single targetlist item ...) * ARRAY_SUBLINK ARRAY(SELECT with single targetlist item ...) - * For ALL, ANY, and MULTIEXPR, the lefthand is a list of expressions of the - * same length as the subselect's targetlist. MULTIEXPR will *always* have + * For ALL, ANY, and ROWCOMPARE, the lefthand is a list of expressions of the + * same length as the subselect's targetlist. ROWCOMPARE will *always* have * a list with more than one entry; if the subselect has just one target * then the parser will create an EXPR_SUBLINK instead (and any operator * above the subselect will be represented separately). Note that both - * MULTIEXPR and EXPR require the subselect to deliver only one row. + * ROWCOMPARE and EXPR require the subselect to deliver only one row. + * ALL, ANY, and ROWCOMPARE require the combining operators to deliver boolean + * results. ALL and ANY combine the per-row results using AND and OR + * semantics respectively. * ARRAY requires just one target column, and creates an array of the target * column's type using one or more rows resulting from the subselect. - * ALL, ANY, and MULTIEXPR require the combining operators to deliver boolean - * results. These are reduced to one result per row using OR or AND semantics - * depending on the "useOr" flag. ALL and ANY combine the per-row results - * using AND and OR semantics respectively. * * SubLink is classed as an Expr node, but it is not actually executable; * it must be replaced in the expression tree by a SubPlan node during * planning. * - * NOTE: in the raw output of gram.y, lefthand contains a list of raw - * expressions; useOr and operOids are not filled in yet. Also, subselect - * is a raw parsetree. During parse analysis, the parser transforms the - * lefthand expression list using normal expression transformation rules. - * It fills operOids with the OIDs representing the specific operator(s) - * to apply to each pair of lefthand and targetlist expressions. - * And subselect is transformed to a Query. This is the representation - * seen in saved rules and in the rewriter. + * NOTE: in the raw output of gram.y, testexpr contains just the raw form + * of the lefthand expression (if any), and operName is the String name of + * the combining operator. Also, subselect is a raw parsetree. During parse + * analysis, the parser transforms testexpr into a complete boolean expression + * that compares the lefthand value(s) to PARAM_SUBLINK nodes representing the + * output columns of the subselect. And subselect is transformed to a Query. + * This is the representation seen in saved rules and in the rewriter. * - * In EXISTS, EXPR, and ARRAY SubLinks, lefthand, operName, and operOids are - * unused and are always NIL. useOr is not significant either for these - * sublink types. - * ---------------- + * In EXISTS, EXPR, and ARRAY SubLinks, testexpr and operName are unused and + * are always null. */ typedef enum SubLinkType { EXISTS_SUBLINK, ALL_SUBLINK, ANY_SUBLINK, - MULTIEXPR_SUBLINK, + ROWCOMPARE_SUBLINK, EXPR_SUBLINK, ARRAY_SUBLINK } SubLinkType; @@ -386,12 +387,9 @@ typedef enum SubLinkType typedef struct SubLink { Expr xpr; - SubLinkType subLinkType; /* EXISTS, ALL, ANY, MULTIEXPR, EXPR */ - bool useOr; /* TRUE to combine column results with "OR" - * not "AND" */ - List *lefthand; /* list of outer-query expressions on the left */ + SubLinkType subLinkType; /* see above */ + Node *testexpr; /* outer-query test for ALL/ANY/ROWCOMPARE */ List *operName; /* originally specified operator name */ - List *operOids; /* OIDs of actual combining operators */ Node *subselect; /* subselect as Query* or parsetree */ } SubLink; @@ -402,14 +400,18 @@ typedef struct SubLink * nodes after it has finished planning the subquery. SubPlan contains * a sub-plantree and rtable instead of a sub-Query. * - * In an ordinary subplan, "exprs" points to a list of executable expressions - * (OpExpr trees) for the combining operators; their left-hand arguments are - * the original lefthand expressions, and their right-hand arguments are - * PARAM_EXEC Param nodes representing the outputs of the sub-select. - * (NOTE: runtime coercion functions may be inserted as well.) But if the - * sub-select becomes an initplan rather than a subplan, these executable - * expressions are part of the outer plan's expression tree (and the SubPlan - * node itself is not). In this case "exprs" is NIL to avoid duplication. + * In an ordinary subplan, testexpr points to an executable expression + * (OpExpr, an AND/OR tree of OpExprs, or RowCompareExpr) for the combining + * operator(s); the left-hand arguments are the original lefthand expressions, + * and the right-hand arguments are PARAM_EXEC Param nodes representing the + * outputs of the sub-select. (NOTE: runtime coercion functions may be + * inserted as well.) This is just the same expression tree as testexpr in + * the original SubLink node, but the PARAM_SUBLINK nodes are replaced by + * suitably numbered PARAM_EXEC nodes. + * + * If the sub-select becomes an initplan rather than a subplan, the executable + * expression is part of the outer plan's expression tree (and the SubPlan + * node itself is not). In this case testexpr is NULL to avoid duplication. * * The planner also derives lists of the values that need to be passed into * and out of the subplan. Input values are represented as a list "args" of @@ -426,13 +428,10 @@ typedef struct SubPlan { Expr xpr; /* Fields copied from original SubLink: */ - SubLinkType subLinkType; /* EXISTS, ALL, ANY, MULTIEXPR, EXPR */ - bool useOr; /* TRUE to combine column results with "OR" - * not "AND" */ - /* The combining operators, transformed to executable expressions: */ - List *exprs; /* list of OpExpr expression trees */ + SubLinkType subLinkType; /* see above */ + /* The combining operators, transformed to an executable expression: */ + Node *testexpr; /* OpExpr or RowCompareExpr expression tree */ List *paramIds; /* IDs of Params embedded in the above */ - /* Note: paramIds has a one-to-one correspondence to the exprs list */ /* The subselect, transformed to a Plan: */ struct Plan *plan; /* subselect plan itself */ int plan_id; /* dummy thing because of we haven't equal @@ -642,6 +641,41 @@ typedef struct RowExpr CoercionForm row_format; /* how to display this node */ } RowExpr; +/* + * RowCompareExpr - row-wise comparison, such as (a, b) <= (1, 2) + * + * We support row comparison for any operator that can be determined to + * act like =, <>, <, <=, >, or >= (we determine this by looking for the + * operator in btree opclasses). Note that the same operator name might + * map to a different operator for each pair of row elements, since the + * element datatypes can vary. + * + * A RowCompareExpr node is only generated for the < <= > >= cases; + * the = and <> cases are translated to simple AND or OR combinations + * of the pairwise comparisons. However, we include = and <> in the + * RowCompareType enum for the convenience of parser logic. + */ +typedef enum RowCompareType +{ + /* Values of this enum are chosen to match btree strategy numbers */ + ROWCOMPARE_LT = 1, /* BTLessStrategyNumber */ + ROWCOMPARE_LE = 2, /* BTLessEqualStrategyNumber */ + ROWCOMPARE_EQ = 3, /* BTEqualStrategyNumber */ + ROWCOMPARE_GE = 4, /* BTGreaterEqualStrategyNumber */ + ROWCOMPARE_GT = 5, /* BTGreaterStrategyNumber */ + ROWCOMPARE_NE = 6 /* no such btree strategy */ +} RowCompareType; + +typedef struct RowCompareExpr +{ + Expr xpr; + RowCompareType rctype; /* LT LE GE or GT, never EQ or NE */ + List *opnos; /* OID list of pairwise comparison ops */ + List *opclasses; /* OID list of containing operator classes */ + List *largs; /* the left-hand input arguments */ + List *rargs; /* the right-hand input arguments */ +} RowCompareExpr; + /* * CoalesceExpr - a COALESCE expression */ diff --git a/src/include/parser/parse_oper.h b/src/include/parser/parse_oper.h index 93e15d5108..86cb478789 100644 --- a/src/include/parser/parse_oper.h +++ b/src/include/parser/parse_oper.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_oper.h,v 1.36 2004/12/31 22:03:38 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_oper.h,v 1.37 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -59,8 +59,5 @@ extern Expr *make_op(ParseState *pstate, List *opname, extern Expr *make_scalar_array_op(ParseState *pstate, List *opname, bool useOr, Node *ltree, Node *rtree); -extern Expr *make_op_expr(ParseState *pstate, Operator op, - Node *ltree, Node *rtree, - Oid ltypeId, Oid rtypeId); #endif /* PARSE_OPER_H */ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index bc3d0b4430..9ba878b2ea 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.101 2005/10/15 02:49:46 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.102 2005/12/28 01:30:01 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,6 +31,8 @@ extern void get_op_opclass_properties(Oid opno, Oid opclass, bool *recheck); extern Oid get_opclass_member(Oid opclass, Oid subtype, int16 strategy); extern Oid get_op_hash_function(Oid opno); +extern void get_op_btree_interpretation(Oid opno, + List **opclasses, List **opstrats); extern Oid get_opclass_proc(Oid opclass, Oid subtype, int16 procnum); extern char *get_attname(Oid relid, AttrNumber attnum); extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum); @@ -41,6 +43,8 @@ extern void get_atttypetypmod(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod); extern bool opclass_is_btree(Oid opclass); extern bool opclass_is_hash(Oid opclass); +extern bool opclass_is_default(Oid opclass); +extern Oid get_opclass_input_type(Oid opclass); extern RegProcedure get_opcode(Oid opno); extern char *get_opname(Oid opno); extern void op_input_types(Oid opno, Oid *lefttype, Oid *righttype); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 11b0bc0eb3..15c6085569 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.157 2005/11/22 18:17:33 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.158 2005/12/28 01:30:01 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -4283,6 +4283,18 @@ exec_simple_check_node(Node *node) return TRUE; } + case T_RowCompareExpr: + { + RowCompareExpr *expr = (RowCompareExpr *) node; + + if (!exec_simple_check_node((Node *) expr->largs)) + return FALSE; + if (!exec_simple_check_node((Node *) expr->rargs)) + return FALSE; + + return TRUE; + } + case T_CoalesceExpr: { CoalesceExpr *expr = (CoalesceExpr *) node; diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index ce3bd80db7..f0b4791df4 100644 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@ -117,3 +117,125 @@ select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people; Jim | abcdefghijklabcdefgh | 1200000 (2 rows) +-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally +-- non-spec-compliant way. +select ROW(1,2) < ROW(1,3) as true; + true +------ + t +(1 row) + +select ROW(1,2) < ROW(1,1) as false; + false +------- + f +(1 row) + +select ROW(1,2) < ROW(1,NULL) as null; + null +------ + +(1 row) + +select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined + true +------ + t +(1 row) + +select ROW(11,'ABC') < ROW(11,'DEF') as true; + true +------ + t +(1 row) + +select ROW(11,'ABC') > ROW(11,'DEF') as false; + false +------- + f +(1 row) + +select ROW(12,'ABC') > ROW(11,'DEF') as true; + true +------ + t +(1 row) + +-- = and <> have different NULL-behavior than < etc +select ROW(1,2,3) < ROW(1,NULL,4) as null; + null +------ + +(1 row) + +select ROW(1,2,3) = ROW(1,NULL,4) as false; + false +------- + f +(1 row) + +select ROW(1,2,3) <> ROW(1,NULL,4) as true; + true +------ + t +(1 row) + +-- We allow operators beyond the six standard ones, if they have btree +-- operator classes. +select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true; + true +------ + t +(1 row) + +select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false; + false +------- + f +(1 row) + +select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; +ERROR: could not determine interpretation of row comparison operator ~~ +HINT: Row comparison operators must be associated with btree operator classes. +-- Check row comparison with a subselect +select unique1, unique2 from tenk1 +where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3); + unique1 | unique2 +---------+--------- + 1 | 2838 + 0 | 9998 +(2 rows) + +-- Also check row comparison with an indexable condition +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; + thousand | tenthous +----------+---------- + 997 | 5997 + 997 | 6997 + 997 | 7997 + 997 | 8997 + 997 | 9997 + 998 | 998 + 998 | 1998 + 998 | 2998 + 998 | 3998 + 998 | 4998 + 998 | 5998 + 998 | 6998 + 998 | 7998 + 998 | 8998 + 998 | 9998 + 999 | 999 + 999 | 1999 + 999 | 2999 + 999 | 3999 + 999 | 4999 + 999 | 5999 + 999 | 6999 + 999 | 7999 + 999 | 8999 + 999 | 9999 +(25 rows) + diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index d09ff662ab..613c4e91f9 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -72,3 +72,35 @@ insert into pp values (repeat('abcdefghijkl', 100000)); insert into people select ('Jim', f1, null)::fullname, current_date from pp; select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people; + +-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally +-- non-spec-compliant way. + +select ROW(1,2) < ROW(1,3) as true; +select ROW(1,2) < ROW(1,1) as false; +select ROW(1,2) < ROW(1,NULL) as null; +select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined +select ROW(11,'ABC') < ROW(11,'DEF') as true; +select ROW(11,'ABC') > ROW(11,'DEF') as false; +select ROW(12,'ABC') > ROW(11,'DEF') as true; + +-- = and <> have different NULL-behavior than < etc +select ROW(1,2,3) < ROW(1,NULL,4) as null; +select ROW(1,2,3) = ROW(1,NULL,4) as false; +select ROW(1,2,3) <> ROW(1,NULL,4) as true; + +-- We allow operators beyond the six standard ones, if they have btree +-- operator classes. +select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true; +select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false; +select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail; + +-- Check row comparison with a subselect +select unique1, unique2 from tenk1 +where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3); + +-- Also check row comparison with an indexable condition +select thousand, tenthous from tenk1 +where (thousand, tenthous) >= (997, 5000) +order by thousand, tenthous; +