<!--
-$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
-->
</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>
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>
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>
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>
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.
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>
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>
<primary>SOME</primary>
</indexterm>
+ <indexterm>
+ <primary>row-wise comparison</primary>
+ </indexterm>
+
<indexterm>
<primary>comparison</primary>
<secondary>row-wise</secondary>
<primary>IS DISTINCT FROM</primary>
</indexterm>
+ <indexterm>
+ <primary>IS NOT DISTINCT FROM</primary>
+ </indexterm>
+
<indexterm>
<primary>IS NULL</primary>
</indexterm>
<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>
<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>
</para>
</sect2>
- <sect2>
+ <sect2 id="row-wise-comparison">
<title>Row-wise Comparison</title>
<synopsis>
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>
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
* 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 $
*
*-------------------------------------------------------------------------
*/
&context->addrs);
/* fall through to examine arguments */
}
- if (IsA(node, SubLink))
+ if (is_subplan(node))
{
- SubLink *sublink = (SubLink *) node;
- ListCell *opid;
+ /* 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(opid, sublink->operOids)
+ foreach(l, rcexpr->opnos)
{
- add_object_address(OCLASS_OPERATOR, lfirst_oid(opid), 0,
+ 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 (is_subplan(node))
- {
- /* Extra work needed here if we ever need this case */
- elog(ERROR, "already-planned subqueries not supported");
- }
if (IsA(node, Query))
{
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
+#include "access/nbtree.h"
#include "catalog/pg_type.h"
#include "commands/typecmds.h"
#include "executor/execdebug.h"
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);
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
* ----------------------------------------------------------------
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);
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;
(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;
}
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
SubPlan *subplan = (SubPlan *) node->xprstate.expr;
PlanState *planstate = node->planstate;
SubLinkType subLinkType = subplan->subLinkType;
- bool useOr = subplan->useOr;
MemoryContext oldcontext;
TupleTableSlot *slot;
Datum result;
/*
* 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
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)
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;
}
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")));
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 */
}
else
{
- /* must be MULTIEXPR_SUBLINK */
+ /* must be ROWCOMPARE_SUBLINK */
result = rowresult;
*isNull = rownull;
}
/*
* 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;
{
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;
TupleTableSlot *slot;
Assert(subplan->subLinkType == ANY_SUBLINK);
- Assert(!subplan->useOr);
/*
* If we already had any hash tables, destroy 'em; then create empty hash
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 =
/* 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;
* 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;
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")));
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
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);
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
*/
case T_RowExpr:
retval = _copyRowExpr(from);
break;
+ case T_RowCompareExpr:
+ retval = _copyRowCompareExpr(from);
+ break;
case T_CoalesceExpr:
retval = _copyCoalesceExpr(from);
break;
* 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 $
*
*-------------------------------------------------------------------------
*/
break;
case PARAM_NUM:
case PARAM_EXEC:
+ case PARAM_SUBLINK:
COMPARE_SCALAR_FIELD(paramid);
break;
default:
_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;
_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);
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)
{
case T_RowExpr:
retval = _equalRowExpr(a, b);
break;
+ case T_RowCompareExpr:
+ retval = _equalRowCompareExpr(a, b);
+ break;
case T_CoalesceExpr:
retval = _equalCoalesceExpr(a, b);
break;
*
*
* 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*
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);
}
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);
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)
{
case T_RowExpr:
_outRowExpr(str, obj);
break;
+ case T_RowCompareExpr:
+ _outRowCompareExpr(str, obj);
+ break;
case T_CoalesceExpr:
_outCoalesceExpr(str, obj);
break;
*
*
* 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
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();
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
*/
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))
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
*
* An exception occurs when we have decided we can implement the
* subplan by hashing.
- *
*/
SubPlan *subplan = (SubPlan *) node;
Plan *plan = subplan->plan;
/*
* 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
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
} 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 */
} 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,
}
/*
- * 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);
* 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()
* 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 */
/*
* 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)
{
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,
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.
*/
}
/*
- * 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.
* 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;
+ Node *result;
+ convert_testexpr_context context;
- *righthandIds = NIL;
- lefthand_item = list_head(lefthand);
- tlist_item = list_head(targetlist);
+ context.rtindex = rtindex;
+ context.righthandIds = NIL;
+ result = convert_testexpr_mutator(testexpr, &context);
+ *righthandIds = context.righthandIds;
+ return result;
+}
- foreach(l, operOids)
+static Node *
+convert_testexpr_mutator(Node *node,
+ convert_testexpr_context *context)
+{
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, Param))
{
- Oid opid = lfirst_oid(l);
- Node *leftop = (Node *) lfirst(lefthand_item);
- TargetEntry *te = (TargetEntry *) lfirst(tlist_item);
- Node *rightop;
- Operator tup;
+ Param *param = (Param *) node;
- Assert(!te->resjunk);
-
- if (rtindex)
+ if (param->paramkind == PARAM_SUBLINK)
{
- /* 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.
+ * 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.)
*/
- *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);
+ if (param->paramid != list_length(context->righthandIds) + 1)
+ elog(ERROR, "unexpected PARAM_SUBLINK ID: %d", param->paramid);
- /*
- * 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);
+ 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 result;
+ return expression_tree_mutator(node,
+ convert_testexpr_mutator,
+ (void *) context);
}
/*
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;
/*
* 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;
+ }
+ 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;
}
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;
/*
* 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;
/*
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);
}
/*
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);
}
/*
*
*
* 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
return false;
if (IsA(node, RowExpr))
return false;
+ if (IsA(node, RowCompareExpr))
+ return false;
if (IsA(node, CoalesceExpr))
return false;
if (IsA(node, MinMaxExpr))
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;
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;
return true;
if (IsA(node, RowExpr))
return true;
+ if (IsA(node, RowCompareExpr))
+ return true;
if (IsA(node, CoalesceExpr))
return true;
if (IsA(node, MinMaxExpr))
* 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
* 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
{
SubLink *sublink = (SubLink *) node;
- if (expression_tree_walker((Node *) sublink->lefthand,
+ if (expression_tree_walker(sublink->testexpr,
walker, context))
return true;
{
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 */
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:
* 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
* 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
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
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 */
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;
*
*
* 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
/* 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;
}
/* 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);
{
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;
{
SubLink *n = makeNode(SubLink);
n->subLinkType = EXPR_SUBLINK;
- n->lefthand = NIL;
+ n->testexpr = NULL;
n->operName = NIL;
n->subselect = $1;
$$ = (Node *)n;
{
SubLink *n = makeNode(SubLink);
n->subLinkType = EXISTS_SUBLINK;
- n->lefthand = NIL;
+ n->testexpr = NULL;
n->operName = NIL;
n->subselect = $2;
$$ = (Node *)n;
{
SubLink *n = makeNode(SubLink);
n->subLinkType = ARRAY_SUBLINK;
- n->lefthand = NIL;
+ n->testexpr = NULL;
n->operName = NIL;
n->subselect = $2;
$$ = (Node *)n;
*
*
* 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"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
-#include "utils/syscache.h"
+
bool Transform_null_equals = false;
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,
((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);
}
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
{
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,
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)
}
/*
- * 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;
case T_RowExpr:
type = ((RowExpr *) expr)->row_typeid;
break;
+ case T_RowCompareExpr:
+ type = BOOLOID;
+ break;
case T_CoalesceExpr:
type = ((CoalesceExpr *) expr)->coalescetype;
break;
}
/*
- * 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;
-
- if (list_length(largs) != list_length(rargs))
+ List **opclass_lists;
+ List **opstrat_lists;
+ Bitmapset *strats;
+ int nopers;
+ int i;
+
+ 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;
+ 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 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)
+ {
+ 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 = (Node *) makeBoolExpr(boolop,
- list_make2(result, cmp));
+ strats = bms_int_members(strats, this_strats);
+ i++;
}
- if (result == NULL)
+ 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++)
{
- /* zero-length rows? Generate constant TRUE or FALSE */
- if (boolop == AND_EXPR)
- result = makeBoolConst(true, false);
+ 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
- result = makeBoolConst(false, false);
+ 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.")));
}
- return result;
+ /*
+ * 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;
}
/*
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
/*
* 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)
* 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.
*
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 ")
break;
case PARAM_NUM:
case PARAM_EXEC:
+ case PARAM_SUBLINK:
appendStringInfo(buf, "$%d", param->paramid);
break;
default:
}
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;
{
StringInfo buf = context->buf;
Query *query = (Query *) (sublink->subselect);
+ char *opname = NULL;
bool need_paren;
if (sublink->subLinkType == ARRAY_SUBLINK)
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:
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:
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.
*
* 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.
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 ---------- */
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 ---------- */
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200511171
+#define CATALOG_VERSION_NO 200512271
#endif
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
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
* ----------------
* 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 $
*
*-------------------------------------------------------------------------
*/
T_CaseTestExpr,
T_ArrayExpr,
T_RowExpr,
+ T_RowCompareExpr,
T_CoalesceExpr,
T_MinMaxExpr,
T_NullIfExpr,
T_CaseWhenState,
T_ArrayExprState,
T_RowExprState,
+ T_RowCompareExprState,
T_CoalesceExprState,
T_MinMaxExprState,
T_CoerceToDomainState,
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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.
*
#define PARAM_NAMED 11
#define PARAM_NUM 12
#define PARAM_EXEC 15
+#define PARAM_SUBLINK 16
#define PARAM_INVALID 100
* 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 $
*
*-------------------------------------------------------------------------
*/
*
* 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
List *args; /* arguments to this expression */
} BoolExpr;
-/* ----------------
+/*
* SubLink
*
* A SubLink represents a subselect appearing in an expression, and in some
* 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.
- *
- * 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.
- * ----------------
+ * 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, 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;
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;
* 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
{
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
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
*/
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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);
* 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.
*
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;
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)
+
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;
+