From 6e07709760a29d8dbfb93b9846c905bd40689082 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> 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 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.300 2005/12/21 23:22:55 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.301 2005/12/28 01:29:58 tgl Exp $ PostgreSQL documentation --> @@ -350,18 +350,18 @@ PostgreSQL documentation </indexterm> The ordinary comparison operators yield null (signifying <quote>unknown</>) when either input is null. Another way to do comparisons is with the - <literal>IS DISTINCT FROM</literal> construct: + <literal>IS <optional> NOT </> DISTINCT FROM</literal> construct: <synopsis> <replaceable>expression</replaceable> IS DISTINCT FROM <replaceable>expression</replaceable> <replaceable>expression</replaceable> IS NOT DISTINCT FROM <replaceable>expression</replaceable> </synopsis> - For non-null inputs, <literal>IS DISTINCT FROM</literal> this is + For non-null inputs, <literal>IS DISTINCT FROM</literal> is the same as the <literal><></> operator. However, when both inputs are null it will return false, and when just one input is null it will return true. Similarly, <literal>IS NOT DISTINCT FROM</literal> is identical to <literal>=</literal> 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 <quote>unknown</>. </para> @@ -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 <token>IN</token> is null. + If all the per-row results are either unequal or null, with at least one + null, then the result of <token>IN</token> is null. </para> </sect2> @@ -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 <token>NOT IN</token> is null. + If all the per-row results are either unequal or null, with at least one + null, then the result of <token>NOT IN</token> is null. </para> </sect2> @@ -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 <replaceable>operator</replaceable>. Presently, - only <literal>=</literal> and <literal><></literal> operators are allowed - in row-wise <token>ANY</token> constructs. - The result of <token>ANY</token> is <quote>true</> if any equal or unequal row is - found, respectively. - The result is <quote>false</> if no such row is found (including the special - case where the subquery returns no rows). + using the given <replaceable>operator</replaceable>. + The result of <token>ANY</token> is <quote>true</> if the comparison + returns true for any subquery row. + The result is <quote>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. </para> <para> - 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 <token>ANY</token> - cannot be false; it will be true or null. + See <xref linkend="row-wise-comparison"> for details about the meaning + of a row-wise comparison. </para> </sect2> @@ -8145,20 +8141,14 @@ SELECT col1 FROM tab1 The result of <token>ALL</token> is <quote>true</> if all rows yield true (including the special case where the subquery returns no rows). The result is <quote>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. </para> <para> <token>NOT IN</token> is equivalent to <literal><> ALL</literal>. </para> - <para> - 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 <token>ALL</token> construct - will be null, not true. - This is in accordance with SQL's normal rules for Boolean combinations - of null values. - </para> - <para> As with <token>EXISTS</token>, 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 <replaceable>operator</replaceable>. Presently, - only <literal>=</literal> and <literal><></literal> operators are allowed - in row-wise <token>ALL</token> queries. - The result of <token>ALL</token> is <quote>true</> if all subquery rows are equal - or unequal, respectively (including the special + using the given <replaceable>operator</replaceable>. + The result of <token>ALL</token> is <quote>true</> if the comparison + returns true for all subquery rows (including the special case where the subquery returns no rows). - The result is <quote>false</> if any row is found to be unequal or equal, - respectively. + The result is <quote>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. </para> <para> - 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 <token>ALL</token> - cannot be true; it will be false or null. + See <xref linkend="row-wise-comparison"> for details about the meaning + of a row-wise comparison. </para> </sect2> @@ -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 <literal>=</literal> and <literal><></literal> operators are allowed - in row-wise comparisons. - The result is <quote>true</> if the two rows are equal or unequal, respectively. </para> <para> - 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 <xref linkend="row-wise-comparison"> for details about the meaning + of a row-wise comparison. </para> </sect2> </sect1> @@ -8255,6 +8234,10 @@ SELECT col1 FROM tab1 <primary>SOME</primary> </indexterm> + <indexterm> + <primary>row-wise comparison</primary> + </indexterm> + <indexterm> <primary>comparison</primary> <secondary>row-wise</secondary> @@ -8264,6 +8247,10 @@ SELECT col1 FROM tab1 <primary>IS DISTINCT FROM</primary> </indexterm> + <indexterm> + <primary>IS NOT DISTINCT FROM</primary> + </indexterm> + <indexterm> <primary>IS NULL</primary> </indexterm> @@ -8288,7 +8275,7 @@ SELECT col1 FROM tab1 <title><literal>IN</literal></title> <synopsis> -<replaceable>expression</replaceable> IN (<replaceable>value</replaceable><optional>, ...</optional>) +<replaceable>expression</replaceable> IN (<replaceable>value</replaceable> <optional>, ...</optional>) </synopsis> <para> @@ -8319,7 +8306,7 @@ OR <title><literal>NOT IN</literal></title> <synopsis> -<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable><optional>, ...</optional>) +<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable> <optional>, ...</optional>) </synopsis> <para> @@ -8425,7 +8412,7 @@ AND </para> </sect2> - <sect2> + <sect2 id="row-wise-comparison"> <title>Row-wise Comparison</title> <synopsis> @@ -8436,23 +8423,52 @@ AND Each side is a row constructor, as described in <xref linkend="sql-syntax-row-constructors">. The two row values must have the same number of fields. - Each side is evaluated and they are compared row-wise. - Presently, only <literal>=</literal> and <literal><></literal> operators are allowed - in row-wise comparisons. - The result is <quote>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 <replaceable>operator</replaceable> is + <literal>=</>, + <literal><></>, + <literal><</>, + <literal><=</>, + <literal>></> or + <literal>>=</>, + 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 <literal>=</> member of a btree operator + class.) </para> <para> - As usual, null values in the rows are combined per - the normal rules of SQL Boolean expressions. Two rows are considered + The <literal>=</> and <literal><></> 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). </para> - <indexterm> - <primary>IS DISTINCT FROM</primary> - </indexterm> + <para> + For the <literal><</>, <literal><=</>, <literal>></> and + <literal>>=</> 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, + <literal>ROW(1,2,NULL) < ROW(1,3,0)</> + yields true, not null, because the third pair of elements are not + considered. + </para> + + <note> + <para> + Prior to <productname>PostgreSQL</productname> 8.2, the + <literal><</>, <literal><=</>, <literal>></> and <literal>>=</> + cases were not handled per SQL specification. A comparison like + <literal>ROW(a,b) < ROW(c,d)</> + was implemented as + <literal>a < c AND b < d</> + whereas the correct behavior is equivalent to + <literal>a < c OR (a = c AND b < d)</>. + </para> + </note> <synopsis> <replaceable>row_constructor</replaceable> IS DISTINCT FROM <replaceable>row_constructor</replaceable> @@ -8466,6 +8482,18 @@ AND be either true or false, never null. </para> +<synopsis> +<replaceable>row_constructor</replaceable> IS NOT DISTINCT FROM <replaceable>row_constructor</replaceable> +</synopsis> + + <para> + This construct is similar to a <literal>=</literal> 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. + </para> + <synopsis> <replaceable>row_constructor</replaceable> IS NULL <replaceable>row_constructor</replaceable> 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; +