From 2f63232d30ca64a8f2684af855230f23a701d371 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 10 May 2004 22:44:49 +0000 Subject: [PATCH] Promote row expressions to full-fledged citizens of the expression syntax, rather than allowing them only in a few special cases as before. In particular you can now pass a ROW() construct to a function that accepts a rowtype parameter. Internal generation of RowExprs fixes a number of corner cases that used to not work very well, such as referencing the whole-row result of a JOIN or subquery. This represents a further step in the work I started a month or so back to make rowtype values into first-class citizens. --- doc/src/sgml/func.sgml | 83 ++-- doc/src/sgml/syntax.sgml | 81 +++- doc/src/sgml/xfunc.sgml | 35 +- src/backend/executor/execQual.c | 87 +++- src/backend/executor/execTuples.c | 36 +- src/backend/nodes/copyfuncs.c | 20 +- src/backend/nodes/equalfuncs.c | 23 +- src/backend/nodes/makefuncs.c | 14 +- src/backend/nodes/outfuncs.c | 15 +- src/backend/nodes/readfuncs.c | 19 +- src/backend/optimizer/path/allpaths.c | 27 +- src/backend/optimizer/path/clausesel.c | 9 +- src/backend/optimizer/prep/prepjointree.c | 50 ++- src/backend/optimizer/util/clauses.c | 109 ++--- src/backend/optimizer/util/var.c | 53 ++- src/backend/parser/gram.y | 462 ++++++++------------- src/backend/parser/parse_coerce.c | 121 +++++- src/backend/parser/parse_expr.c | 304 ++++++++++++-- src/backend/parser/parse_target.c | 6 +- src/backend/rewrite/rewriteHandler.c | 5 +- src/backend/rewrite/rewriteManip.c | 114 +++-- src/backend/utils/adt/ruleutils.c | 38 +- src/include/catalog/catversion.h | 4 +- src/include/executor/executor.h | 3 +- src/include/nodes/execnodes.h | 13 +- src/include/nodes/makefuncs.h | 4 +- src/include/nodes/nodes.h | 4 +- src/include/nodes/primnodes.h | 19 +- src/include/optimizer/var.h | 3 +- src/include/rewrite/rewriteManip.h | 5 +- src/pl/plpgsql/src/pl_exec.c | 12 +- src/test/regress/input/misc.source | 11 + src/test/regress/output/constraints.source | 4 +- src/test/regress/output/misc.source | 39 ++ 34 files changed, 1281 insertions(+), 551 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9e306a99282..1dde8b59a3c 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ @@ -7822,13 +7822,15 @@ SELECT col1 FROM tab1 -(expression , expression ...) IN (subquery) +row_constructor IN (subquery) - The right-hand side of this form of IN is a parenthesized + The left-hand side of this form of IN is a row constructor, + as described in . + The right-hand side is a parenthesized subquery, which must return exactly as many columns as there are - expressions in the left-hand list. The left-hand expressions are + expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result. The result of IN is true if any equal subquery row is found. The result is false if no equal row is found (including the special @@ -7876,13 +7878,15 @@ SELECT col1 FROM tab1 -(expression , expression ...) NOT IN (subquery) +row_constructor NOT IN (subquery) - The right-hand side of this form of NOT IN is a parenthesized + The left-hand side of this form of NOT IN is a row constructor, + as described in . + The right-hand side is a parenthesized subquery, which must return exactly as many columns as there are - expressions in the left-hand list. The left-hand expressions are + expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result. The result of NOT IN is true if only unequal subquery rows are found (including the special case where the subquery returns no rows). @@ -7938,14 +7942,16 @@ SELECT col1 FROM tab1 -(expression , expression ...) operator ANY (subquery) -(expression , expression ...) operator SOME (subquery) +row_constructor operator ANY (subquery) +row_constructor operator SOME (subquery) - The right-hand side of this form of ANY is a parenthesized + The left-hand side of this form of ANY is a row constructor, + as described in . + The right-hand side is a parenthesized subquery, which must return exactly as many columns as there are - expressions in the left-hand list. The left-hand expressions are + expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result, using the given operator. Presently, only = and <> operators are allowed @@ -8003,13 +8009,15 @@ SELECT col1 FROM tab1 -(expression , expression ...) operator ALL (subquery) +row_constructor operator ALL (subquery) - The right-hand side of this form of ALL is a parenthesized + The left-hand side of this form of ALL is a row constructor, + as described in . + The right-hand side is a parenthesized subquery, which must return exactly as many columns as there are - expressions in the left-hand list. The left-hand expressions are + expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result, using the given operator. Presently, only = and <> operators are allowed @@ -8041,16 +8049,17 @@ SELECT col1 FROM tab1 -(expression , expression ...) operator (subquery) +row_constructor operator (subquery) - The left-hand side is a list of scalar expressions. The right-hand side is - a parenthesized subquery, which must return exactly as many columns as there - are expressions on the left-hand side. Furthermore, 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. + The left-hand side is a row constructor, + as described in . + The right-hand side is a parenthesized subquery, which must return exactly + as many columns as there are expressions in the left-hand row. Furthermore, + the subquery cannot return more than one row. (If it returns zero rows, + the result is taken to be null.) The left-hand side is evaluated and + compared row-wise to the single subquery result row. Presently, only = and <> operators are allowed in row-wise comparisons. The result is true if the two rows are equal or unequal, respectively. @@ -8223,13 +8232,14 @@ AND Row-wise Comparison -(expression , expression ...) operator (expression , expression ...) +row_constructor operator row_constructor - Each side is a list of scalar expressions; the two lists must be - of the same length. Each side is evaluated and they are compared - row-wise. + Each side is a row constructor, + as described in . + The two row values must have the same number of fields. + Each side is evaluated and they are compared row-wise. Presently, only = and <> operators are allowed in row-wise comparisons. The result is true if the two rows are equal or unequal, respectively. @@ -8242,6 +8252,29 @@ AND are unequal if any corresponding members are non-null and unequal; otherwise the result of the row comparison is unknown (null). + + +row_constructor IS DISTINCT FROM row_constructor + + + + This construct is similar to a <> row comparison, + but it does not yield null for null inputs. Instead, any null value is + considered unequal to (distinct from) any non-null value, and any two + nulls are considered equal (not distinct). Thus the result will always + be either true or false, never null. + + + +row_constructor IS NULL +row_constructor IS NOT NULL + + + + These constructs test a row value for null or not null. A row value + is considered not null if it has at least one field that is not null. + + diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index c6093b84637..24a01891d31 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1,5 +1,5 @@ @@ -920,6 +920,12 @@ SELECT 3 OPERATOR(pg_catalog.+) 4; + + + A row constructor. + + + Another value expression in parentheses, useful to group @@ -1428,6 +1434,79 @@ SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%'); + + Row Constructors + + + row + constructor + + + + A row constructor is an expression that builds a row value from values + for its member fields. A row constructor consists of the key word + ROW, a left parenthesis (, zero or more + expressions (separated by commas) for the row field values, and finally + a right parenthesis ). For example, + +SELECT myfunc(ROW(1,2.5,'this is a test')); + + The key word ROW is optional when there is more than one + expression in the list. + + + + By default, the value created by a ROW expression is of + an anonymous record type. If necessary, it can be cast to a named + composite type --- either the rowtype of a table, or a composite type + created with CREATE TYPE AS. An explicit cast may be needed + to avoid ambiguity. For example: + +CREATE TABLE mytable(f1 int, f2 float, f3 text); +CREATE FUNCTION getf1(mytable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL; +-- No cast needed since only one getf1() exists +SELECT getf1(ROW(1,2.5,'this is a test')); + getf1 +------- + 1 +(1 row) + +CREATE TYPE myrowtype AS (f1 int, f2 text, f3 numeric); +CREATE FUNCTION getf1(myrowtype) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL; +-- Now we need a cast to indicate which function to call: +SELECT getf1(ROW(1,2.5,'this is a test')); +ERROR: function getf1(record) is not unique +SELECT getf1(ROW(1,2.5,'this is a test')::mytable); + getf1 +------- + 1 +(1 row) + +SELECT getf1(CAST(ROW(11,'this is a test',2.5) AS myrowtype)); + getf1 +------- + 11 +(1 row) + + + + + Row constructors have only limited uses, other than creating an argument + value for a user-defined function that accepts a rowtype parameter, as + illustrated above. + It is possible to compare two row values or test a row with + IS NULL or IS NOT NULL, for example + +SELECT ROW(1,2.5,'this is a test') = ROW(1, 3, 'not the same'); +SELECT ROW(a, b, c) IS NOT NULL FROM table; + + For more detail see . + Row constructors can also be used in connection with subqueries, + as discussed in . + + + + Expression Evaluation Rules diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 4b06aefd362..a3c24a1c4c1 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -1,5 +1,5 @@ @@ -240,10 +240,11 @@ SELECT clean_emp(); <acronym>SQL</acronym> Functions on Composite Types - When specifying functions with arguments of composite + When writing functions with arguments of composite types, we must not only specify which argument we want (as we did above with $1 and $2) but - also the attributes of that argument. For example, suppose that + also the desired attribute (field) of that argument. For example, + suppose that emp is a table containing employee data, and therefore also the name of the composite type of each row of the table. Here is a function double_salary that computes what someone's @@ -252,16 +253,16 @@ SELECT clean_emp(); CREATE TABLE emp ( name text, - salary integer, + salary numeric, age integer, cubicle point ); -CREATE FUNCTION double_salary(emp) RETURNS integer AS ' +CREATE FUNCTION double_salary(emp) RETURNS numeric AS ' SELECT $1.salary * 2 AS salary; ' LANGUAGE SQL; -SELECT name, double_salary(emp) AS dream +SELECT name, double_salary(emp.*) AS dream FROM emp WHERE emp.cubicle ~= point '(2,1)'; @@ -274,15 +275,27 @@ SELECT name, double_salary(emp) AS dream Notice the use of the syntax $1.salary to select one field of the argument row value. Also notice - how the calling SELECT command uses a table name to denote - the entire current row of that table as a composite value. The table - row can alternatively be referenced like this: + how the calling SELECT command uses * + to select + the entire current row of a table as a composite value. The table + row can alternatively be referenced using just the table name, + like this: -SELECT name, double_salary(emp.*) AS dream +SELECT name, double_salary(emp) AS dream FROM emp WHERE emp.cubicle ~= point '(2,1)'; - which emphasizes its row nature. + but this usage is deprecated since it's easy to get confused. + + + + Sometimes it is handy to construct a composite argument value + on-the-fly. This can be done with the ROW construct. + For example, we could adjust the data being passed to the function: + +SELECT name, double_salary(row(name, salary*1.1, age, cubicle)) AS dream + FROM emp; + diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index b27e86122bc..d44f580be02 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.158 2004/04/01 21:28:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.159 2004/05/10 22:44:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,7 @@ #include "executor/execdebug.h" #include "executor/functions.h" #include "executor/nodeSubplan.h" +#include "funcapi.h" #include "miscadmin.h" #include "optimizer/planmain.h" #include "parser/parse_expr.h" @@ -93,6 +94,9 @@ static Datum ExecEvalCaseTestExpr(ExprState *exprstate, static Datum ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRow(RowExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -2101,6 +2105,54 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, return PointerGetDatum(result); } +/* ---------------------------------------------------------------- + * ExecEvalRow - ROW() expressions + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRow(RowExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + HeapTuple tuple; + Datum *values; + char *nulls; + int nargs; + List *arg; + int i; + + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + + /* Allocate workspace */ + nargs = length(rstate->args); + if (nargs == 0) /* avoid palloc(0) if no fields */ + nargs = 1; + values = (Datum *) palloc(nargs * sizeof(Datum)); + nulls = (char *) palloc(nargs * sizeof(char)); + + /* Evaluate field values */ + i = 0; + foreach(arg, rstate->args) + { + ExprState *e = (ExprState *) lfirst(arg); + bool eisnull; + + values[i] = ExecEvalExpr(e, econtext, &eisnull, NULL); + nulls[i] = eisnull ? 'n' : ' '; + i++; + } + + tuple = heap_formtuple(rstate->tupdesc, values, nulls); + + pfree(values); + pfree(nulls); + + return HeapTupleGetDatum(tuple); +} + /* ---------------------------------------------------------------- * ExecEvalCoalesce * ---------------------------------------------------------------- @@ -2822,6 +2874,39 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) astate; } break; + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + RowExprState *rstate = makeNode(RowExprState); + List *outlist; + List *inlist; + + rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow; + outlist = NIL; + foreach(inlist, rowexpr->args) + { + Expr *e = (Expr *) lfirst(inlist); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->args = outlist; + /* Build tupdesc to describe result tuples */ + if (rowexpr->row_typeid == RECORDOID) + { + /* generic record, use runtime type assignment */ + rstate->tupdesc = ExecTypeFromExprList(rowexpr->args); + rstate->tupdesc = BlessTupleDesc(rstate->tupdesc); + } + else + { + /* it's been cast to a named type, use that */ + rstate->tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); + } + state = (ExprState *) rstate; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index faf910b736f..725b8fea0ff 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.76 2004/04/01 21:28:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.77 2004/05/10 22:44:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -111,6 +111,7 @@ #include "access/heapam.h" #include "catalog/pg_type.h" #include "executor/executor.h" +#include "parser/parse_expr.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -118,6 +119,7 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk); + /* ---------------------------------------------------------------- * tuple table create/delete functions * ---------------------------------------------------------------- @@ -595,6 +597,38 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk) return typeInfo; } +/* + * ExecTypeFromExprList - build a tuple descriptor from a list of Exprs + * + * Here we must make up an arbitrary set of field names. + */ +TupleDesc +ExecTypeFromExprList(List *exprList) +{ + TupleDesc typeInfo; + List *l; + int cur_resno = 1; + char fldname[NAMEDATALEN]; + + typeInfo = CreateTemplateTupleDesc(length(exprList), false); + + foreach(l, exprList) + { + Node *e = lfirst(l); + + sprintf(fldname, "f%d", cur_resno); + + TupleDescInitEntry(typeInfo, + cur_resno++, + fldname, + exprType(e), + exprTypmod(e), + 0); + } + + return typeInfo; +} + /* * BlessTupleDesc - make a completed tuple descriptor useful for SRFs * diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1466be98cbb..91fb3b08343 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.280 2004/05/05 04:48:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.281 2004/05/10 22:44:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -998,6 +998,21 @@ _copyArrayExpr(ArrayExpr *from) return newnode; } +/* + * _copyRowExpr + */ +static RowExpr * +_copyRowExpr(RowExpr *from) +{ + RowExpr *newnode = makeNode(RowExpr); + + COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(row_typeid); + COPY_SCALAR_FIELD(row_format); + + return newnode; +} + /* * _copyCoalesceExpr */ @@ -2674,6 +2689,9 @@ copyObject(void *from) case T_ArrayExpr: retval = _copyArrayExpr(from); break; + case T_RowExpr: + retval = _copyRowExpr(from); + break; case T_CoalesceExpr: retval = _copyCoalesceExpr(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index eab30d122c2..19ffbb1be70 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.219 2004/05/05 04:48:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.220 2004/05/10 22:44:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -423,6 +423,24 @@ _equalArrayExpr(ArrayExpr *a, ArrayExpr *b) return true; } +static bool +_equalRowExpr(RowExpr *a, RowExpr *b) +{ + COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(row_typeid); + + /* + * Special-case COERCE_DONTCARE, so that planner can build coercion + * nodes that are equal() to both explicit and implicit coercions. + */ + if (a->row_format != b->row_format && + a->row_format != COERCE_DONTCARE && + b->row_format != COERCE_DONTCARE) + return false; + + return true; +} + static bool _equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b) { @@ -1748,6 +1766,9 @@ equal(void *a, void *b) case T_ArrayExpr: retval = _equalArrayExpr(a, b); break; + case T_RowExpr: + retval = _equalRowExpr(a, b); + break; case T_CoalesceExpr: retval = _equalCoalesceExpr(a, b); break; diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 230910aca0f..8842bd4aa3b 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -9,12 +9,13 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.42 2003/11/29 19:51:49 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.43 2004/05/10 22:44:44 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "utils/lsyscache.h" @@ -170,6 +171,17 @@ makeNullConst(Oid consttype) typByVal); } +/* + * makeBoolConst - + * creates a Const node representing a boolean value (can be NULL too) + */ +Node * +makeBoolConst(bool value, bool isnull) +{ + /* note that pg_type.h hardwires size of bool as 1 ... duplicate it */ + return (Node *) makeConst(BOOLOID, 1, BoolGetDatum(value), isnull, true); +} + /* * makeBoolExpr - * creates a BoolExpr node diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 9139a9bec54..e919a851940 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.235 2004/05/08 21:21:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.236 2004/05/10 22:44:44 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -828,6 +828,16 @@ _outArrayExpr(StringInfo str, ArrayExpr *node) WRITE_BOOL_FIELD(multidims); } +static void +_outRowExpr(StringInfo str, RowExpr *node) +{ + WRITE_NODE_TYPE("ROW"); + + WRITE_NODE_FIELD(args); + WRITE_OID_FIELD(row_typeid); + WRITE_ENUM_FIELD(row_format, CoercionForm); +} + static void _outCoalesceExpr(StringInfo str, CoalesceExpr *node) { @@ -1719,6 +1729,9 @@ _outNode(StringInfo str, void *obj) case T_ArrayExpr: _outArrayExpr(str, obj); break; + case T_RowExpr: + _outRowExpr(str, obj); + break; case T_CoalesceExpr: _outCoalesceExpr(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index ebd3c636c29..253b5a3eafa 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.168 2004/05/08 21:21:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.169 2004/05/10 22:44:44 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -628,6 +628,21 @@ _readArrayExpr(void) READ_DONE(); } +/* + * _readRowExpr + */ +static RowExpr * +_readRowExpr(void) +{ + READ_LOCALS(RowExpr); + + READ_NODE_FIELD(args); + READ_OID_FIELD(row_typeid); + READ_ENUM_FIELD(row_format, CoercionForm); + + READ_DONE(); +} + /* * _readCoalesceExpr */ @@ -978,6 +993,8 @@ parseNodeString(void) return_value = _readCaseTestExpr(); else if (MATCH("ARRAY", 5)) return_value = _readArrayExpr(); + else if (MATCH("ROW", 3)) + return_value = _readRowExpr(); else if (MATCH("COALESCE", 8)) return_value = _readCoalesceExpr(); else if (MATCH("NULLIFEXPR", 10)) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 5d92fc4c5d1..217f06a6b2b 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.113 2004/04/25 18:23:56 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.114 2004/05/10 22:44:44 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -57,9 +57,10 @@ static void compare_tlist_datatypes(List *tlist, List *colTypes, bool *differentTypes); static bool qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual, bool *differentTypes); -static void subquery_push_qual(Query *subquery, Index rti, Node *qual); +static void subquery_push_qual(Query *subquery, + RangeTblEntry *rte, Index rti, Node *qual); static void recurse_push_qual(Node *setOp, Query *topquery, - Index rti, Node *qual); + RangeTblEntry *rte, Index rti, Node *qual); /* @@ -375,7 +376,7 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel, if (qual_is_pushdown_safe(subquery, rti, clause, differentTypes)) { /* Push it down */ - subquery_push_qual(subquery, rti, clause); + subquery_push_qual(subquery, rte, rti, clause); } else { @@ -778,12 +779,12 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual, * subquery_push_qual - push down a qual that we have determined is safe */ static void -subquery_push_qual(Query *subquery, Index rti, Node *qual) +subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual) { if (subquery->setOperations != NULL) { /* Recurse to push it separately to each component query */ - recurse_push_qual(subquery->setOperations, subquery, rti, qual); + recurse_push_qual(subquery->setOperations, subquery, rte, rti, qual); } else { @@ -797,7 +798,7 @@ subquery_push_qual(Query *subquery, Index rti, Node *qual) * This step also ensures that when we are pushing into a setop tree, * each component query gets its own copy of the qual. */ - qual = ResolveNew(qual, rti, 0, + qual = ResolveNew(qual, rti, 0, rte, subquery->targetList, CMD_SELECT, 0); subquery->havingQual = make_and_qual(subquery->havingQual, @@ -816,23 +817,23 @@ subquery_push_qual(Query *subquery, Index rti, Node *qual) */ static void recurse_push_qual(Node *setOp, Query *topquery, - Index rti, Node *qual) + RangeTblEntry *rte, Index rti, Node *qual) { if (IsA(setOp, RangeTblRef)) { RangeTblRef *rtr = (RangeTblRef *) setOp; - RangeTblEntry *rte = rt_fetch(rtr->rtindex, topquery->rtable); - Query *subquery = rte->subquery; + RangeTblEntry *subrte = rt_fetch(rtr->rtindex, topquery->rtable); + Query *subquery = subrte->subquery; Assert(subquery != NULL); - subquery_push_qual(subquery, rti, qual); + subquery_push_qual(subquery, rte, rti, qual); } else if (IsA(setOp, SetOperationStmt)) { SetOperationStmt *op = (SetOperationStmt *) setOp; - recurse_push_qual(op->larg, topquery, rti, qual); - recurse_push_qual(op->rarg, topquery, rti, qual); + recurse_push_qual(op->larg, topquery, rte, rti, qual); + recurse_push_qual(op->rarg, topquery, rte, rti, qual); } else { diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 2cd445961e9..96ce94c8fcb 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.64 2004/01/05 16:44:40 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.65 2004/05/10 22:44:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,11 +27,6 @@ #include "utils/selfuncs.h" -/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */ -#define MAKEBOOLCONST(val,isnull) \ - ((Node *) makeConst(BOOLOID, 1, (Datum) (val), (isnull), true)) - - /* * Data structure for accumulating info about possible range-query * clause pairs in clauselist_selectivity. @@ -486,7 +481,7 @@ clause_selectivity(Query *root, s1 = restriction_selectivity(root, BooleanEqualOperator, makeList2(var, - MAKEBOOLCONST(true, + makeBoolConst(true, false)), varRelid); } diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 033aaba494e..0ddf1ccd67f 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -16,7 +16,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.16 2004/01/10 18:13:53 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.17 2004/05/10 22:44:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -45,7 +45,8 @@ typedef struct reduce_outer_joins_state static bool is_simple_subquery(Query *subquery); static bool has_nullable_targetlist(Query *subquery); -static void resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist); +static void resolvenew_in_jointree(Node *jtnode, int varno, + RangeTblEntry *rte, List *subtlist); static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode); static void reduce_outer_joins_pass2(Node *jtnode, reduce_outer_joins_state *state, @@ -154,14 +155,10 @@ pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join) * such expressions; we'd have to figure out how to get the pseudo- * variables evaluated at the right place in the modified plan * tree. Fix it someday. - * - * Note: even if the subquery itself is simple enough, we can't pull - * it up if there is a reference to its whole tuple result. - * Perhaps a pseudo-variable is the answer here too. */ - if (rte->rtekind == RTE_SUBQUERY && is_simple_subquery(subquery) && - (!below_outer_join || has_nullable_targetlist(subquery)) && - !contain_whole_tuple_var((Node *) parse, varno, 0)) + if (rte->rtekind == RTE_SUBQUERY && + is_simple_subquery(subquery) && + (!below_outer_join || has_nullable_targetlist(subquery))) { int rtoffset; List *subtlist; @@ -206,8 +203,7 @@ pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join) * the one above. */ if (is_simple_subquery(subquery) && - (!below_outer_join || has_nullable_targetlist(subquery)) && - !contain_whole_tuple_var((Node *) parse, varno, 0)) + (!below_outer_join || has_nullable_targetlist(subquery))) { /* good to go */ } @@ -247,24 +243,25 @@ pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join) subtlist = subquery->targetList; parse->targetList = (List *) ResolveNew((Node *) parse->targetList, - varno, 0, subtlist, CMD_SELECT, 0); - resolvenew_in_jointree((Node *) parse->jointree, varno, subtlist); + varno, 0, rte, subtlist, CMD_SELECT, 0); + resolvenew_in_jointree((Node *) parse->jointree, varno, + rte, subtlist); Assert(parse->setOperations == NULL); parse->havingQual = ResolveNew(parse->havingQual, - varno, 0, subtlist, CMD_SELECT, 0); + varno, 0, rte, subtlist, CMD_SELECT, 0); parse->in_info_list = (List *) ResolveNew((Node *) parse->in_info_list, - varno, 0, subtlist, CMD_SELECT, 0); + varno, 0, rte, subtlist, CMD_SELECT, 0); foreach(rt, parse->rtable) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); + RangeTblEntry *otherrte = (RangeTblEntry *) lfirst(rt); - if (rte->rtekind == RTE_JOIN) - rte->joinaliasvars = (List *) - ResolveNew((Node *) rte->joinaliasvars, - varno, 0, subtlist, CMD_SELECT, 0); + if (otherrte->rtekind == RTE_JOIN) + otherrte->joinaliasvars = (List *) + ResolveNew((Node *) otherrte->joinaliasvars, + varno, 0, rte, subtlist, CMD_SELECT, 0); } /* @@ -479,7 +476,8 @@ has_nullable_targetlist(Query *subquery) * but there's no other way... */ static void -resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist) +resolvenew_in_jointree(Node *jtnode, int varno, + RangeTblEntry *rte, List *subtlist) { if (jtnode == NULL) return; @@ -493,18 +491,18 @@ resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist) List *l; foreach(l, f->fromlist) - resolvenew_in_jointree(lfirst(l), varno, subtlist); + resolvenew_in_jointree(lfirst(l), varno, rte, subtlist); f->quals = ResolveNew(f->quals, - varno, 0, subtlist, CMD_SELECT, 0); + varno, 0, rte, subtlist, CMD_SELECT, 0); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - resolvenew_in_jointree(j->larg, varno, subtlist); - resolvenew_in_jointree(j->rarg, varno, subtlist); + resolvenew_in_jointree(j->larg, varno, rte, subtlist); + resolvenew_in_jointree(j->rarg, varno, rte, subtlist); j->quals = ResolveNew(j->quals, - varno, 0, subtlist, CMD_SELECT, 0); + varno, 0, rte, subtlist, CMD_SELECT, 0); /* * We don't bother to update the colvars list, since it won't be diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index f653640ee8c..4dabbf50dac 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.169 2004/04/02 23:14:08 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.170 2004/05/10 22:44:45 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -41,10 +41,6 @@ #include "utils/syscache.h" -/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */ -#define MAKEBOOLCONST(val,isnull) \ - ((Node *) makeConst(BOOLOID, 1, (Datum) (val), (isnull), true)) - typedef struct { int nargs; @@ -281,7 +277,7 @@ Expr * make_ands_explicit(List *andclauses) { if (andclauses == NIL) - return (Expr *) MAKEBOOLCONST(true, false); + return (Expr *) makeBoolConst(true, false); else if (lnext(andclauses) == NIL) return (Expr *) lfirst(andclauses); else @@ -484,6 +480,8 @@ expression_returns_set_walker(Node *node, void *context) return false; if (IsA(node, ArrayExpr)) return false; + if (IsA(node, RowExpr)) + return false; if (IsA(node, CoalesceExpr)) return false; if (IsA(node, NullIfExpr)) @@ -778,6 +776,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) if (IsA(node, CaseWhen)) return true; /* NB: ArrayExpr might someday be nonstrict */ + if (IsA(node, RowExpr)) + return true; if (IsA(node, CoalesceExpr)) return true; if (IsA(node, NullIfExpr)) @@ -1030,6 +1030,8 @@ set_coercionform_dontcare_walker(Node *node, void *context) ((FuncExpr *) node)->funcformat = COERCE_DONTCARE; if (IsA(node, RelabelType)) ((RelabelType *) node)->relabelformat = COERCE_DONTCARE; + if (IsA(node, RowExpr)) + ((RowExpr *) node)->row_format = COERCE_DONTCARE; if (IsA(node, CoerceToDomain)) ((CoerceToDomain *) node)->coercionformat = COERCE_DONTCARE; return expression_tree_walker(node, set_coercionform_dontcare_walker, @@ -1197,11 +1199,11 @@ eval_const_expressions_mutator(Node *node, List *active_fns) { /* all nulls? then not distinct */ if (all_null_input) - return MAKEBOOLCONST(false, false); + return makeBoolConst(false, false); /* one null? then distinct */ if (has_null_input) - return MAKEBOOLCONST(true, false); + return makeBoolConst(true, false); /* otherwise try to evaluate the '=' operator */ /* (NOT okay to try to inline it, though!) */ @@ -1272,12 +1274,12 @@ eval_const_expressions_mutator(Node *node, List *active_fns) newargs = simplify_or_arguments(args, &haveNull, &forceTrue); if (forceTrue) - return MAKEBOOLCONST(true, false); + return makeBoolConst(true, false); if (haveNull) - newargs = lappend(newargs, MAKEBOOLCONST(false, true)); + newargs = lappend(newargs, makeBoolConst(false, true)); /* If all the inputs are FALSE, result is FALSE */ if (newargs == NIL) - return MAKEBOOLCONST(false, false); + return makeBoolConst(false, false); /* If only one nonconst-or-NULL input, it's the result */ if (lnext(newargs) == NIL) return (Node *) lfirst(newargs); @@ -1293,12 +1295,12 @@ eval_const_expressions_mutator(Node *node, List *active_fns) newargs = simplify_and_arguments(args, &haveNull, &forceFalse); if (forceFalse) - return MAKEBOOLCONST(false, false); + return makeBoolConst(false, false); if (haveNull) - newargs = lappend(newargs, MAKEBOOLCONST(false, true)); + newargs = lappend(newargs, makeBoolConst(false, true)); /* If all the inputs are TRUE, result is TRUE */ if (newargs == NIL) - return MAKEBOOLCONST(true, false); + return makeBoolConst(true, false); /* If only one nonconst-or-NULL input, it's the result */ if (lnext(newargs) == NIL) return (Node *) lfirst(newargs); @@ -1313,9 +1315,9 @@ eval_const_expressions_mutator(Node *node, List *active_fns) /* NOT NULL => NULL */ if (const_input->constisnull) - return MAKEBOOLCONST(false, true); + return makeBoolConst(false, true); /* otherwise pretty easy */ - return MAKEBOOLCONST(!DatumGetBool(const_input->constvalue), + return makeBoolConst(!DatumGetBool(const_input->constvalue), false); } else if (not_clause((Node *) lfirst(args))) @@ -1387,7 +1389,6 @@ eval_const_expressions_mutator(Node *node, List *active_fns) } if (IsA(node, CaseExpr)) { - /*---------- * CASE expressions can be simplified if there are constant * condition clauses: @@ -1546,22 +1547,38 @@ eval_const_expressions_mutator(Node *node, List *active_fns) * We can optimize field selection from a whole-row Var into a * simple Var. (This case won't be generated directly by the * parser, because ParseComplexProjection short-circuits it. But - * it can arise while simplifying functions.) If the argument - * isn't a whole-row Var, just fall through to do generic - * processing. + * it can arise while simplifying functions.) Also, we can + * optimize field selection from a RowExpr construct. */ FieldSelect *fselect = (FieldSelect *) node; - Var *argvar = (Var *) fselect->arg; + FieldSelect *newfselect; + Node *arg; - if (argvar && IsA(argvar, Var) && - argvar->varattno == InvalidAttrNumber) + arg = eval_const_expressions_mutator((Node *) fselect->arg, + active_fns); + if (arg && IsA(arg, Var) && + ((Var *) arg)->varattno == InvalidAttrNumber) { - return (Node *) makeVar(argvar->varno, + return (Node *) makeVar(((Var *) arg)->varno, fselect->fieldnum, fselect->resulttype, fselect->resulttypmod, - argvar->varlevelsup); + ((Var *) arg)->varlevelsup); } + if (arg && IsA(arg, RowExpr)) + { + RowExpr *rowexpr = (RowExpr *) arg; + + if (fselect->fieldnum > 0 && + fselect->fieldnum <= length(rowexpr->args)) + return (Node *) nth(fselect->fieldnum - 1, rowexpr->args); + } + newfselect = makeNode(FieldSelect); + newfselect->arg = (Expr *) arg; + newfselect->fieldnum = fselect->fieldnum; + newfselect->resulttype = fselect->resulttype; + newfselect->resulttypmod = fselect->resulttypmod; + return (Node *) newfselect; } /* @@ -1759,7 +1776,6 @@ evaluate_function(Oid funcid, Oid result_type, List *args, bool has_null_input = false; List *arg; FuncExpr *newexpr; - char result_typtype; /* * Can't simplify if it returns a set. @@ -1796,15 +1812,6 @@ evaluate_function(Oid funcid, Oid result_type, List *args, has_nonconst_input) return NULL; - /* - * Can't simplify functions returning composite types (mainly because - * datumCopy() doesn't cope; FIXME someday when we have a saner - * representation for whole-tuple results). - */ - result_typtype = get_typtype(funcform->prorettype); - if (result_typtype == 'c') - return NULL; - /* * OK, looks like we can simplify this operator/function. * @@ -1850,7 +1857,6 @@ inline_function(Oid funcid, Oid result_type, List *args, HeapTuple func_tuple, List *active_fns) { Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); - char result_typtype; bool polymorphic = false; Oid argtypes[FUNC_MAX_ARGS]; char *src; @@ -1877,21 +1883,6 @@ inline_function(Oid funcid, Oid result_type, List *args, funcform->pronargs != length(args)) return NULL; - /* - * Forget it if declared return type is not base, domain, or - * polymorphic - */ - result_typtype = get_typtype(funcform->prorettype); - if (result_typtype != 'b' && - result_typtype != 'd') - { - if (funcform->prorettype == ANYARRAYOID || - funcform->prorettype == ANYELEMENTOID) - polymorphic = true; - else - return NULL; - } - /* Check for recursive function, and give up trying to expand if so */ if (oidMember(funcid, active_fns)) return NULL; @@ -1912,6 +1903,10 @@ inline_function(Oid funcid, Oid result_type, List *args, } } + if (funcform->prorettype == ANYARRAYOID || + funcform->prorettype == ANYELEMENTOID) + polymorphic = true; + /* * Setup error traceback support for ereport(). This is so that we * can finger the function that bad information came from. @@ -2483,6 +2478,8 @@ expression_tree_walker(Node *node, break; case T_ArrayExpr: return walker(((ArrayExpr *) node)->elements, context); + case T_RowExpr: + return walker(((RowExpr *) node)->args, context); case T_CoalesceExpr: return walker(((CoalesceExpr *) node)->args, context); case T_NullIfExpr: @@ -2889,6 +2886,16 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + RowExpr *newnode; + + FLATCOPY(newnode, rowexpr, RowExpr); + MUTATE(newnode->args, rowexpr->args, List *); + return (Node *) newnode; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index dac90809de1..47b1fdbf70e 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.55 2003/11/29 19:51:51 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.56 2004/05/10 22:44:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -191,19 +191,6 @@ contain_var_reference_walker(Node *node, } -/* - * contain_whole_tuple_var - * - * Detect whether a parsetree contains any references to the whole - * tuple of a given rtable entry (ie, a Var with varattno = 0). - */ -bool -contain_whole_tuple_var(Node *node, int varno, int levelsup) -{ - return contain_var_reference(node, varno, InvalidAttrNumber, levelsup); -} - - /* * contain_var_clause * Recursively scan a clause to discover whether it contains any Var nodes @@ -486,7 +473,10 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context) * flatten_join_alias_vars * Replace Vars that reference JOIN outputs with references to the original * relation variables instead. This allows quals involving such vars to be - * pushed down. + * pushed down. Whole-row Vars that reference JOIN relations are expanded + * into RowExpr constructs that name the individual output Vars. This + * is necessary since we will not scan the JOIN as a base relation, which + * is the only way that the executor can directly handle whole-row Vars. * * NOTE: this is used on not-yet-planned expressions. We do not expect it * to be applied directly to a Query node. @@ -520,8 +510,39 @@ flatten_join_alias_vars_mutator(Node *node, rte = rt_fetch(var->varno, context->root->rtable); if (rte->rtekind != RTE_JOIN) return node; + if (var->varattno == InvalidAttrNumber) + { + /* Must expand whole-row reference */ + RowExpr *rowexpr; + List *fields = NIL; + List *l; + + foreach(l, rte->joinaliasvars) + { + newvar = (Node *) lfirst(l); + /* + * If we are expanding an alias carried down from an upper + * query, must adjust its varlevelsup fields. + */ + if (context->sublevels_up != 0) + { + newvar = copyObject(newvar); + IncrementVarSublevelsUp(newvar, context->sublevels_up, 0); + } + /* Recurse in case join input is itself a join */ + newvar = flatten_join_alias_vars_mutator(newvar, context); + fields = lappend(fields, newvar); + } + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + + return (Node *) rowexpr; + } + + /* Expand join alias reference */ Assert(var->varattno > 0); - /* Okay, must expand it */ newvar = (Node *) nth(var->varattno - 1, rte->joinaliasvars); /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 64825893bc1..b9becf91dbb 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.453 2004/05/05 04:48:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.454 2004/05/10 22:44:45 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -80,11 +80,9 @@ static Node *makeStringConst(char *str, TypeName *typename); static Node *makeIntConst(int val); static Node *makeFloatConst(char *str); static Node *makeAConst(Value *v); -static Node *makeRowExpr(List *opr, List *largs, List *rargs); -static Node *makeDistinctExpr(List *largs, List *rargs); -static Node *makeRowNullTest(NullTestType test, List *args); +static Node *makeRowNullTest(NullTestType test, RowExpr *row); static DefElem *makeDefElem(char *name, Node *arg); -static A_Const *makeBoolConst(bool state); +static A_Const *makeBoolAConst(bool state); static FuncCall *makeOverlaps(List *largs, List *rargs); static List *extractArgTypes(List *parameters); static SelectStmt *findLeftmostSelect(SelectStmt *node); @@ -277,9 +275,9 @@ static void doNegateFloat(Value *v); %type columnDef %type def_elem %type def_arg columnElem where_clause insert_column_item - a_expr b_expr c_expr r_expr AexprConst + a_expr b_expr c_expr AexprConst in_expr having_clause func_table array_expr -%type row row_descriptor type_list array_expr_list +%type row type_list array_expr_list %type case_expr case_arg when_clause case_default %type when_clause_list %type sub_type @@ -5710,163 +5708,6 @@ opt_interval: * *****************************************************************************/ -/* Expressions using row descriptors - * Define row_descriptor to allow yacc to break the reduce/reduce conflict - * with singleton expressions. Use SQL99's ROW keyword to allow rows of - * one element. - */ -r_expr: row IN_P select_with_parens - { - SubLink *n = makeNode(SubLink); - n->subLinkType = ANY_SUBLINK; - n->lefthand = $1; - n->operName = makeList1(makeString("=")); - n->subselect = $3; - $$ = (Node *)n; - } - | row NOT IN_P select_with_parens - { - /* Make an IN node */ - SubLink *n = makeNode(SubLink); - n->subLinkType = ANY_SUBLINK; - n->lefthand = $1; - n->operName = makeList1(makeString("=")); - n->subselect = $4; - /* Stick a NOT on top */ - $$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, (Node *) n); - } - | row subquery_Op sub_type select_with_parens - %prec Op - { - SubLink *n = makeNode(SubLink); - n->subLinkType = $3; - n->lefthand = $1; - n->operName = $2; - n->subselect = $4; - $$ = (Node *)n; - } - | row subquery_Op select_with_parens - %prec Op - { - SubLink *n = makeNode(SubLink); - n->subLinkType = MULTIEXPR_SUBLINK; - n->lefthand = $1; - n->operName = $2; - n->subselect = $3; - $$ = (Node *)n; - } - | row subquery_Op row - %prec Op - { - $$ = makeRowExpr($2, $1, $3); - } - | row IS NULL_P - { - $$ = makeRowNullTest(IS_NULL, $1); - } - | row IS NOT NULL_P - { - $$ = makeRowNullTest(IS_NOT_NULL, $1); - } - | row OVERLAPS row - { - $$ = (Node *)makeOverlaps($1, $3); - } - | row IS DISTINCT FROM row - %prec IS - { - /* IS DISTINCT FROM has the following rules for non-array types: - * a) the row lengths must be equal - * b) if both rows are zero-length, then they are not distinct - * c) if any element is distinct, the rows are distinct - * The rules for an element being distinct: - * a) if the elements are both NULL, then they are not distinct - * b) if the elements compare to be equal, then they are not distinct - * c) otherwise, they are distinct - */ - List *largs = $1; - List *rargs = $5; - /* lengths don't match? then complain */ - if (length(largs) != length(rargs)) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("unequal number of entries in row expression"))); - } - /* both are zero-length rows? then they are not distinct */ - else if (length(largs) <= 0) - { - $$ = (Node *)makeBoolConst(FALSE); - } - /* otherwise, we need to compare each element */ - else - { - $$ = (Node *)makeDistinctExpr(largs, rargs); - } - } - ; - -/* Explicit row production. - * SQL99 allows an optional ROW keyword, so we can now do single-element productions - * without conflicting with the parenthesized a_expr production. - */ -row: ROW '(' row_descriptor ')' { $$ = $3; } - | ROW '(' a_expr ')' { $$ = makeList1($3); } - | ROW '(' ')' { $$ = NULL; } - | '(' row_descriptor ')' { $$ = $2; } - ; - -row_descriptor: expr_list ',' a_expr { $$ = lappend($1, $3); } - ; - -sub_type: ANY { $$ = ANY_SUBLINK; } - | SOME { $$ = ANY_SUBLINK; } - | ALL { $$ = ALL_SUBLINK; } - ; - -all_Op: Op { $$ = $1; } - | MathOp { $$ = $1; } - ; - -MathOp: '+' { $$ = "+"; } - | '-' { $$ = "-"; } - | '*' { $$ = "*"; } - | '/' { $$ = "/"; } - | '%' { $$ = "%"; } - | '^' { $$ = "^"; } - | '<' { $$ = "<"; } - | '>' { $$ = ">"; } - | '=' { $$ = "="; } - ; - -qual_Op: Op - { $$ = makeList1(makeString($1)); } - | OPERATOR '(' any_operator ')' { $$ = $3; } - ; - -qual_all_Op: - all_Op - { $$ = makeList1(makeString($1)); } - | OPERATOR '(' any_operator ')' { $$ = $3; } - ; - -subquery_Op: - all_Op { $$ = makeList1(makeString($1)); } - | OPERATOR '(' any_operator ')' { $$ = $3; } - | LIKE { $$ = makeList1(makeString("~~")); } - | NOT LIKE { $$ = makeList1(makeString("!~~")); } - | ILIKE { $$ = makeList1(makeString("~~*")); } - | NOT ILIKE { $$ = makeList1(makeString("!~~*")); } -/* cannot put SIMILAR TO here, because SIMILAR TO is a hack. - * the regular expression is preprocessed by a function (similar_escape), - * and the ~ operator for posix regular expressions is used. - * x SIMILAR TO y -> x ~ similar_escape(y) - * this transformation is made on the fly by the parser upwards. - * however the SubLink structure which handles any/some/all stuff - * is not ready for such a thing. - */ - ; - /* * General expressions * This is the heart of the expression syntax. @@ -6046,31 +5887,55 @@ a_expr: c_expr { $$ = $1; } */ | a_expr ISNULL { - NullTest *n = makeNode(NullTest); - n->arg = (Expr *) $1; - n->nulltesttype = IS_NULL; - $$ = (Node *)n; + if (IsA($1, RowExpr)) + $$ = makeRowNullTest(IS_NULL, (RowExpr *) $1); + else + { + NullTest *n = makeNode(NullTest); + n->arg = (Expr *) $1; + n->nulltesttype = IS_NULL; + $$ = (Node *)n; + } } | a_expr IS NULL_P { - NullTest *n = makeNode(NullTest); - n->arg = (Expr *) $1; - n->nulltesttype = IS_NULL; - $$ = (Node *)n; + if (IsA($1, RowExpr)) + $$ = makeRowNullTest(IS_NULL, (RowExpr *) $1); + else + { + NullTest *n = makeNode(NullTest); + n->arg = (Expr *) $1; + n->nulltesttype = IS_NULL; + $$ = (Node *)n; + } } | a_expr NOTNULL { - NullTest *n = makeNode(NullTest); - n->arg = (Expr *) $1; - n->nulltesttype = IS_NOT_NULL; - $$ = (Node *)n; + if (IsA($1, RowExpr)) + $$ = makeRowNullTest(IS_NOT_NULL, (RowExpr *) $1); + else + { + NullTest *n = makeNode(NullTest); + n->arg = (Expr *) $1; + n->nulltesttype = IS_NOT_NULL; + $$ = (Node *)n; + } } | a_expr IS NOT NULL_P { - NullTest *n = makeNode(NullTest); - n->arg = (Expr *) $1; - n->nulltesttype = IS_NOT_NULL; - $$ = (Node *)n; + if (IsA($1, RowExpr)) + $$ = makeRowNullTest(IS_NOT_NULL, (RowExpr *) $1); + else + { + NullTest *n = makeNode(NullTest); + n->arg = (Expr *) $1; + n->nulltesttype = IS_NOT_NULL; + $$ = (Node *)n; + } + } + | row OVERLAPS row + { + $$ = (Node *)makeOverlaps($1, $3); } | a_expr IS TRUE_P { @@ -6115,7 +5980,9 @@ a_expr: c_expr { $$ = $1; } $$ = (Node *)b; } | a_expr IS DISTINCT FROM a_expr %prec IS - { $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5); } + { + $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5); + } | a_expr IS OF '(' type_list ')' %prec IS { $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "=", $1, (Node *) $5); @@ -6143,7 +6010,10 @@ a_expr: c_expr { $$ = $1; } { SubLink *n = (SubLink *)$3; n->subLinkType = ANY_SUBLINK; - n->lefthand = makeList1($1); + if (IsA($1, RowExpr)) + n->lefthand = ((RowExpr *) $1)->args; + else + n->lefthand = makeList1($1); n->operName = makeList1(makeString("=")); $$ = (Node *)n; } @@ -6171,7 +6041,10 @@ a_expr: c_expr { $$ = $1; } /* Make an IN node */ SubLink *n = (SubLink *)$4; n->subLinkType = ANY_SUBLINK; - n->lefthand = makeList1($1); + if (IsA($1, RowExpr)) + n->lefthand = ((RowExpr *) $1)->args; + else + n->lefthand = makeList1($1); n->operName = makeList1(makeString("=")); /* Stick a NOT on top */ $$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, (Node *) n); @@ -6196,7 +6069,10 @@ a_expr: c_expr { $$ = $1; } { SubLink *n = makeNode(SubLink); n->subLinkType = $3; - n->lefthand = makeList1($1); + if (IsA($1, RowExpr)) + n->lefthand = ((RowExpr *) $1)->args; + else + n->lefthand = makeList1($1); n->operName = $2; n->subselect = $4; $$ = (Node *)n; @@ -6223,8 +6099,6 @@ a_expr: c_expr { $$ = $1; } (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("UNIQUE predicate is not yet implemented"))); } - | r_expr - { $$ = $1; } ; /* @@ -6277,7 +6151,9 @@ b_expr: c_expr | b_expr qual_Op %prec POSTFIXOP { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, NULL); } | b_expr IS DISTINCT FROM b_expr %prec IS - { $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5); } + { + $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5); + } | b_expr IS OF '(' type_list ')' %prec IS { $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "=", $1, (Node *) $5); @@ -6819,12 +6695,86 @@ c_expr: columnref { $$ = (Node *) $1; } } | ARRAY array_expr { $$ = $2; } + | row + { + RowExpr *r = makeNode(RowExpr); + r->args = $1; + r->row_typeid = InvalidOid; /* not analyzed yet */ + $$ = (Node *)r; + } ; /* * Supporting nonterminals for expressions. */ +/* Explicit row production. + * + * SQL99 allows an optional ROW keyword, so we can now do single-element rows + * without conflicting with the parenthesized a_expr production. Without the + * ROW keyword, there must be more than one a_expr inside the parens. + */ +row: ROW '(' expr_list ')' { $$ = $3; } + | ROW '(' ')' { $$ = NIL; } + | '(' expr_list ',' a_expr ')' { $$ = lappend($2, $4); } + ; + +sub_type: ANY { $$ = ANY_SUBLINK; } + | SOME { $$ = ANY_SUBLINK; } + | ALL { $$ = ALL_SUBLINK; } + ; + +all_Op: Op { $$ = $1; } + | MathOp { $$ = $1; } + ; + +MathOp: '+' { $$ = "+"; } + | '-' { $$ = "-"; } + | '*' { $$ = "*"; } + | '/' { $$ = "/"; } + | '%' { $$ = "%"; } + | '^' { $$ = "^"; } + | '<' { $$ = "<"; } + | '>' { $$ = ">"; } + | '=' { $$ = "="; } + ; + +qual_Op: Op + { $$ = makeList1(makeString($1)); } + | OPERATOR '(' any_operator ')' + { $$ = $3; } + ; + +qual_all_Op: + all_Op + { $$ = makeList1(makeString($1)); } + | OPERATOR '(' any_operator ')' + { $$ = $3; } + ; + +subquery_Op: + all_Op + { $$ = makeList1(makeString($1)); } + | OPERATOR '(' any_operator ')' + { $$ = $3; } + | LIKE + { $$ = makeList1(makeString("~~")); } + | NOT LIKE + { $$ = makeList1(makeString("!~~")); } + | ILIKE + { $$ = makeList1(makeString("~~*")); } + | NOT ILIKE + { $$ = makeList1(makeString("!~~*")); } +/* cannot put SIMILAR TO here, because SIMILAR TO is a hack. + * the regular expression is preprocessed by a function (similar_escape), + * and the ~ operator for posix regular expressions is used. + * x SIMILAR TO y -> x ~ similar_escape(y) + * this transformation is made on the fly by the parser upwards. + * however the SubLink structure which handles any/some/all stuff + * is not ready for such a thing. + */ + ; + opt_indirection: opt_indirection '[' a_expr ']' { @@ -7358,11 +7308,11 @@ AexprConst: Iconst } | TRUE_P { - $$ = (Node *)makeBoolConst(TRUE); + $$ = (Node *)makeBoolAConst(TRUE); } | FALSE_P { - $$ = (Node *)makeBoolConst(FALSE); + $$ = (Node *)makeBoolAConst(FALSE); } | NULL_P { @@ -7892,11 +7842,11 @@ makeDefElem(char *name, Node *arg) return f; } -/* makeBoolConst() +/* makeBoolAConst() * Create an A_Const node and initialize to a boolean constant. */ static A_Const * -makeBoolConst(bool state) +makeBoolAConst(bool state) { A_Const *n = makeNode(A_Const); n->val.type = T_String; @@ -7905,119 +7855,41 @@ makeBoolConst(bool state) return n; } -/* makeRowExpr() - * Generate separate operator nodes for a single row descriptor expression. - * Perhaps this should go deeper in the parser someday... - * - thomas 1997-12-22 - */ -static Node * -makeRowExpr(List *opr, List *largs, List *rargs) -{ - Node *expr = NULL; - Node *larg, *rarg; - char *oprname; - - if (length(largs) != length(rargs)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("unequal number of entries in row expression"))); - - if (lnext(largs) != NIL) - expr = makeRowExpr(opr, lnext(largs), lnext(rargs)); - - larg = lfirst(largs); - rarg = lfirst(rargs); - - oprname = strVal(llast(opr)); - - if ((strcmp(oprname, "=") == 0) || - (strcmp(oprname, "<") == 0) || - (strcmp(oprname, "<=") == 0) || - (strcmp(oprname, ">") == 0) || - (strcmp(oprname, ">=") == 0)) - { - if (expr == NULL) - expr = (Node *) makeA_Expr(AEXPR_OP, opr, larg, rarg); - else - expr = (Node *) makeA_Expr(AEXPR_AND, NIL, expr, - (Node *) makeA_Expr(AEXPR_OP, opr, - larg, rarg)); - } - else if (strcmp(oprname, "<>") == 0) - { - if (expr == NULL) - expr = (Node *) makeA_Expr(AEXPR_OP, opr, larg, rarg); - else - expr = (Node *) makeA_Expr(AEXPR_OR, NIL, expr, - (Node *) makeA_Expr(AEXPR_OP, opr, - larg, rarg)); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("operator %s is not supported for row expressions", - oprname))); - } - - return expr; -} - -/* makeDistinctExpr() - * Generate separate operator nodes for a single row descriptor expression. - * Same comments as for makeRowExpr(). - */ -static Node * -makeDistinctExpr(List *largs, List *rargs) -{ - Node *expr = NULL; - Node *larg, *rarg; - - if (length(largs) != length(rargs)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("unequal number of entries in row expression"))); - - if (lnext(largs) != NIL) - expr = makeDistinctExpr(lnext(largs), lnext(rargs)); - - larg = lfirst(largs); - rarg = lfirst(rargs); - - if (expr == NULL) - expr = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", larg, rarg); - else - expr = (Node *) makeA_Expr(AEXPR_OR, NIL, expr, - (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", - larg, rarg)); - - return expr; -} - /* makeRowNullTest() * Generate separate operator nodes for a single row descriptor test. + * + * Eventually this should be eliminated in favor of making the NullTest + * node type capable of handling it directly. */ static Node * -makeRowNullTest(NullTestType test, List *args) +makeRowNullTest(NullTestType test, RowExpr *row) { - Node *expr = NULL; - NullTest *n; + Node *result = NULL; + List *arg; - if (lnext(args) != NIL) - expr = makeRowNullTest(test, lnext(args)); + foreach(arg, row->args) + { + NullTest *n; - n = makeNode(NullTest); - n->arg = (Expr *) lfirst(args); - n->nulltesttype = test; + n = makeNode(NullTest); + n->arg = (Expr *) lfirst(arg); + n->nulltesttype = test; - if (expr == NULL) - expr = (Node *) n; - else if (test == IS_NOT_NULL) - expr = (Node *) makeA_Expr(AEXPR_OR, NIL, expr, (Node *)n); - else - expr = (Node *) makeA_Expr(AEXPR_AND, NIL, expr, (Node *)n); + if (result == NULL) + result = (Node *) n; + else if (test == IS_NOT_NULL) + result = (Node *) makeA_Expr(AEXPR_OR, NIL, result, (Node *)n); + else + result = (Node *) makeA_Expr(AEXPR_AND, NIL, result, (Node *)n); + } - return expr; + if (result == NULL) + { + /* zero-length rows? Generate constant TRUE or FALSE */ + result = (Node *) makeBoolAConst(test == IS_NULL); + } + + return result; } /* makeOverlaps() diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index a5bff31c89f..c7a8c3c83bb 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.114 2004/03/15 01:13:40 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.115 2004/05/10 22:44:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,19 +19,26 @@ #include "nodes/makefuncs.h" #include "nodes/params.h" #include "optimizer/clauses.h" +#include "parser/parsetree.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" +#include "parser/parse_relation.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/typcache.h" static Node *coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, CoercionForm cformat, bool isExplicit); +static Node *coerce_record_to_complex(ParseState *pstate, Node *node, + Oid targetTypeId, + CoercionContext ccontext, + CoercionForm cformat); /* @@ -279,6 +286,13 @@ coerce_type(ParseState *pstate, Node *node, } return result; } + if (inputTypeId == RECORDOID && + ISCOMPLEX(targetTypeId)) + { + /* Coerce a RECORD to a specific complex type */ + return coerce_record_to_complex(pstate, node, targetTypeId, + ccontext, cformat); + } if (typeInheritsFrom(inputTypeId, targetTypeId)) { /* @@ -359,6 +373,14 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids, &funcId)) continue; + /* + * If input is RECORD and target is a composite type, assume + * we can coerce (may need tighter checking here) + */ + if (inputTypeId == RECORDOID && + ISCOMPLEX(targetTypeId)) + continue; + /* * If input is a class type that inherits from target, accept */ @@ -506,6 +528,103 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod, return node; } +/* + * coerce_record_to_complex + * Coerce a RECORD to a specific composite type. + * + * Currently we only support this for inputs that are RowExprs or whole-row + * Vars. + */ +static Node * +coerce_record_to_complex(ParseState *pstate, Node *node, + Oid targetTypeId, + CoercionContext ccontext, + CoercionForm cformat) +{ + RowExpr *rowexpr; + TupleDesc tupdesc; + List *args = NIL; + List *newargs; + int i; + List *arg; + + if (node && IsA(node, RowExpr)) + { + args = ((RowExpr *) node)->args; + } + else if (node && IsA(node, Var) && + ((Var *) node)->varattno == InvalidAttrNumber) + { + RangeTblEntry *rte; + AttrNumber nfields; + AttrNumber nf; + + rte = GetRTEByRangeTablePosn(pstate, + ((Var *) node)->varno, + ((Var *) node)->varlevelsup); + nfields = length(rte->eref->colnames); + for (nf = 1; nf <= nfields; nf++) + { + Oid vartype; + int32 vartypmod; + + get_rte_attribute_type(rte, nf, &vartype, &vartypmod); + args = lappend(args, + makeVar(((Var *) node)->varno, + nf, + vartype, + vartypmod, + ((Var *) node)->varlevelsup)); + } + } + else + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)))); + + tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1); + if (length(args) != tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Input has wrong number of columns."))); + newargs = NIL; + i = 0; + foreach(arg, args) + { + Node *expr = (Node *) lfirst(arg); + Oid exprtype = exprType(expr); + + expr = coerce_to_target_type(pstate, + expr, exprtype, + tupdesc->attrs[i]->atttypid, + tupdesc->attrs[i]->atttypmod, + ccontext, + COERCE_IMPLICIT_CAST); + if (expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(RECORDOID), + format_type_be(targetTypeId)), + errdetail("Cannot cast type %s to %s in column %d.", + format_type_be(exprtype), + format_type_be(tupdesc->attrs[i]->atttypid), + i + 1))); + newargs = lappend(newargs, expr); + i++; + } + + rowexpr = makeNode(RowExpr); + rowexpr->args = newargs; + rowexpr->row_typeid = targetTypeId; + rowexpr->row_format = cformat; + return (Node *) rowexpr; +} /* coerce_to_boolean() * Coerce an argument of a construct that requires boolean input diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 2747ec3ed43..d4a8cdcc8b6 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.169 2004/04/18 18:12:58 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.170 2004/05/10 22:44:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,13 +37,19 @@ bool Transform_null_equals = false; -static Node *typecast_expression(ParseState *pstate, Node *expr, - TypeName *typename); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); static Node *transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname); static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); +static Node *typecast_expression(ParseState *pstate, Node *expr, + TypeName *typename); +static Node *make_row_op(ParseState *pstate, List *opname, + Node *ltree, Node *rtree); +static Node *make_row_distinct_op(ParseState *pstate, List *opname, + Node *ltree, Node *rtree); +static Expr *make_distinct_op(ParseState *pstate, List *opname, + Node *ltree, Node *rtree); /* @@ -202,6 +208,9 @@ transformExpr(ParseState *pstate, Node *expr) { case AEXPR_OP: { + Node *lexpr = a->lexpr; + Node *rexpr = a->rexpr; + /* * Special-case "foo = NULL" and "NULL = foo" * for compatibility with standards-broken @@ -211,27 +220,51 @@ transformExpr(ParseState *pstate, Node *expr) if (Transform_null_equals && length(a->name) == 1 && strcmp(strVal(lfirst(a->name)), "=") == 0 && - (exprIsNullConstant(a->lexpr) || - exprIsNullConstant(a->rexpr))) + (exprIsNullConstant(lexpr) || + exprIsNullConstant(rexpr))) { NullTest *n = makeNode(NullTest); n->nulltesttype = IS_NULL; - if (exprIsNullConstant(a->lexpr)) - n->arg = (Expr *) a->rexpr; + if (exprIsNullConstant(lexpr)) + n->arg = (Expr *) rexpr; else - n->arg = (Expr *) a->lexpr; + n->arg = (Expr *) lexpr; result = transformExpr(pstate, (Node *) n); } + else if (lexpr && IsA(lexpr, RowExpr) && + rexpr && IsA(rexpr, SubLink) && + ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK) + { + /* + * Convert "row op subselect" into a + * MULTIEXPR 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->operName = a->name; + result = transformExpr(pstate, (Node *) s); + } + else if (lexpr && IsA(lexpr, RowExpr) && + rexpr && IsA(rexpr, RowExpr)) + { + /* "row op row" */ + result = make_row_op(pstate, a->name, + lexpr, rexpr); + } else { - Node *lexpr = transformExpr(pstate, - a->lexpr); - Node *rexpr = transformExpr(pstate, - a->rexpr); + /* Ordinary scalar operator */ + lexpr = transformExpr(pstate, lexpr); + rexpr = transformExpr(pstate, rexpr); result = (Node *) make_op(pstate, a->name, @@ -311,25 +344,27 @@ transformExpr(ParseState *pstate, Node *expr) break; case AEXPR_DISTINCT: { - Node *lexpr = transformExpr(pstate, - a->lexpr); - Node *rexpr = transformExpr(pstate, - a->rexpr); + Node *lexpr = a->lexpr; + Node *rexpr = a->rexpr; - result = (Node *) make_op(pstate, - a->name, - lexpr, - rexpr); - if (((OpExpr *) result)->opresulttype != BOOLOID) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("IS DISTINCT FROM requires = operator to yield boolean"))); + if (lexpr && IsA(lexpr, RowExpr) && + rexpr && IsA(rexpr, RowExpr)) + { + /* "row op row" */ + result = make_row_distinct_op(pstate, a->name, + lexpr, rexpr); + } + else + { + /* Ordinary scalar operator */ + lexpr = transformExpr(pstate, lexpr); + rexpr = transformExpr(pstate, rexpr); - /* - * We rely on DistinctExpr and OpExpr being - * same struct - */ - NodeSetTag(result, T_DistinctExpr); + result = (Node *) make_distinct_op(pstate, + a->name, + lexpr, + rexpr); + } } break; case AEXPR_NULLIF: @@ -787,6 +822,32 @@ transformExpr(ParseState *pstate, Node *expr) break; } + case T_RowExpr: + { + RowExpr *r = (RowExpr *) expr; + RowExpr *newr = makeNode(RowExpr); + List *newargs = NIL; + List *arg; + + /* Transform the field expressions */ + foreach(arg, r->args) + { + Node *e = (Node *) lfirst(arg); + Node *newe; + + newe = transformExpr(pstate, e); + newargs = lappend(newargs, newe); + } + newr->args = newargs; + + /* Barring later casting, we consider the type RECORD */ + newr->row_typeid = RECORDOID; + newr->row_format = COERCE_IMPLICIT_CAST; + + result = (Node *) newr; + break; + } + case T_CoalesceExpr: { CoalesceExpr *c = (CoalesceExpr *) expr; @@ -1113,14 +1174,11 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) /* * Construct a whole-row reference to represent the notation "relation.*". * - * In simple cases, this will be a Var with varno set to the correct range + * A whole-row reference is a Var with varno set to the correct range * table entry, and varattno == 0 to signal that it references the whole * tuple. (Use of zero here is unclean, since it could easily be confused * with error cases, but it's not worth changing now.) The vartype indicates * a rowtype; either a named composite type, or RECORD. - * - * We also need the ability to build a row-constructor expression, but the - * infrastructure for that doesn't exist just yet. */ static Node * transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname) @@ -1185,12 +1243,10 @@ transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname) break; default: /* - * RTE is a join or subselect. For the moment we represent this - * as a whole-row Var of RECORD type, but this will not actually - * work; need a row-constructor expression instead. - * - * XXX after fixing, be sure that unknown_attribute still - * does the right thing. + * RTE is a join or subselect. We represent this as a whole-row + * Var of RECORD type. (Note that in most cases the Var will + * be expanded to a RowExpr during planning, but that is not + * our concern here.) */ result = (Node *) makeVar(vnum, InvalidAttrNumber, @@ -1266,8 +1322,8 @@ exprType(Node *expr) if (sublink->subLinkType == EXPR_SUBLINK) type = tent->resdom->restype; else -/* ARRAY_SUBLINK */ { + /* ARRAY_SUBLINK */ type = get_array_type(tent->resdom->restype); if (!OidIsValid(type)) ereport(ERROR, @@ -1305,8 +1361,8 @@ exprType(Node *expr) if (subplan->subLinkType == EXPR_SUBLINK) type = tent->resdom->restype; else -/* ARRAY_SUBLINK */ { + /* ARRAY_SUBLINK */ type = get_array_type(tent->resdom->restype); if (!OidIsValid(type)) ereport(ERROR, @@ -1340,6 +1396,9 @@ exprType(Node *expr) case T_ArrayExpr: type = ((ArrayExpr *) expr)->array_typeid; break; + case T_RowExpr: + type = ((RowExpr *) expr)->row_typeid; + break; case T_CoalesceExpr: type = ((CoalesceExpr *) expr)->coalescetype; break; @@ -1573,3 +1632,166 @@ typecast_expression(ParseState *pstate, Node *expr, TypeName *typename) return expr; } + +/* + * Transform a "row op row" construct + */ +static Node * +make_row_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree) +{ + Node *result = NULL; + RowExpr *lrow, + *rrow; + List *largs, + *rargs; + List *largl, + *rargl; + char *oprname; + BoolExprType boolop; + + /* Inputs are untransformed RowExprs */ + lrow = (RowExpr *) transformExpr(pstate, ltree); + rrow = (RowExpr *) transformExpr(pstate, rtree); + Assert(IsA(lrow, RowExpr)); + Assert(IsA(rrow, RowExpr)); + largs = lrow->args; + rargs = rrow->args; + + if (length(largs) != length(rargs)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unequal number of entries in row expression"))); + + /* + * 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 ... + */ + 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 + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("operator %s is not supported for row expressions", + oprname))); + boolop = 0; /* keep compiler quiet */ + } + + /* XXX use forboth */ + rargl = rargs; + foreach(largl, largs) + { + Node *larg = (Node *) lfirst(largl); + Node *rarg = (Node *) lfirst(rargl); + Node *cmp; + + rargl = lnext(rargl); + cmp = (Node *) make_op(pstate, opname, larg, rarg); + cmp = coerce_to_boolean(pstate, cmp, "row comparison"); + if (result == NULL) + result = cmp; + else + result = (Node *) makeBoolExpr(boolop, + makeList2(result, cmp)); + } + + if (result == NULL) + { + /* zero-length rows? Generate constant TRUE or FALSE */ + if (boolop == AND_EXPR) + result = makeBoolConst(true, false); + else + result = makeBoolConst(false, false); + } + + return result; +} + +/* + * Transform a "row IS DISTINCT FROM row" construct + */ +static Node * +make_row_distinct_op(ParseState *pstate, List *opname, + Node *ltree, Node *rtree) +{ + Node *result = NULL; + RowExpr *lrow, + *rrow; + List *largs, + *rargs; + List *largl, + *rargl; + + /* Inputs are untransformed RowExprs */ + lrow = (RowExpr *) transformExpr(pstate, ltree); + rrow = (RowExpr *) transformExpr(pstate, rtree); + Assert(IsA(lrow, RowExpr)); + Assert(IsA(rrow, RowExpr)); + largs = lrow->args; + rargs = rrow->args; + + if (length(largs) != length(rargs)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unequal number of entries in row expression"))); + + /* XXX use forboth */ + rargl = rargs; + foreach(largl, largs) + { + Node *larg = (Node *) lfirst(largl); + Node *rarg = (Node *) lfirst(rargl); + Node *cmp; + + rargl = lnext(rargl); + cmp = (Node *) make_distinct_op(pstate, opname, larg, rarg); + if (result == NULL) + result = cmp; + else + result = (Node *) makeBoolExpr(OR_EXPR, + makeList2(result, cmp)); + } + + if (result == NULL) + { + /* zero-length rows? Generate constant FALSE */ + result = makeBoolConst(false, false); + } + + return result; +} + +/* + * make the node for an IS DISTINCT FROM operator + */ +static Expr * +make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree) +{ + Expr *result; + + result = make_op(pstate, opname, ltree, rtree); + if (((OpExpr *) result)->opresulttype != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("IS DISTINCT FROM requires = operator to yield boolean"))); + /* + * We rely on DistinctExpr and OpExpr being + * same struct + */ + NodeSetTag(result, T_DistinctExpr); + + return result; +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index c9c44ac2389..fbee22b37d3 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.116 2004/04/02 19:06:58 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.117 2004/05/10 22:44:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -643,6 +643,10 @@ FigureColnameInternal(Node *node, char **name) /* make ARRAY[] act like a function */ *name = "array"; return 2; + case T_RowExpr: + /* make ROW() act like a function */ + *name = "row"; + return 2; case T_CoalesceExpr: /* make coalesce() act like a regular function */ *name = "coalesce"; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index d83e8ac4580..3b898b973d8 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.134 2004/04/01 21:28:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.135 2004/05/10 22:44:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -212,6 +212,8 @@ rewriteRuleAction(Query *parsetree, sub_action = (Query *) ResolveNew((Node *) sub_action, new_varno, 0, + rt_fetch(new_varno, + sub_action->rtable), parsetree->targetList, event, current_varno); @@ -947,6 +949,7 @@ CopyAndAddInvertedQual(Query *parsetree, new_qual = ResolveNew(new_qual, PRS2_NEW_VARNO, 0, + rt_fetch(rt_index, parsetree->rtable), parsetree->targetList, event, rt_index); diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 5210b99fd7e..45b60715966 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.81 2003/11/29 19:51:55 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.82 2004/05/10 22:44:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -850,6 +850,10 @@ AddInvertedQual(Query *parsetree, Node *qual) * If not, we either change the unmatched Var's varno to update_varno * (when event == CMD_UPDATE) or replace it with a constant NULL. * + * The caller must also provide target_rte, the RTE describing the target + * relation. This is needed to handle whole-row Vars referencing the target. + * We expand such Vars into RowExpr constructs. + * * Note: the business with inserted_sublink is needed to update hasSubLinks * in subqueries when the replacement adds a subquery inside a subquery. * Messy, isn't it? We do not need to do similar pushups for hasAggs, @@ -861,12 +865,52 @@ typedef struct { int target_varno; int sublevels_up; + RangeTblEntry *target_rte; List *targetlist; int event; int update_varno; bool inserted_sublink; } ResolveNew_context; +static Node * +resolve_one_var(Var *var, ResolveNew_context *context) +{ + TargetEntry *tle; + + tle = get_tle_by_resno(context->targetlist, var->varattno); + + if (tle == NULL) + { + /* Failed to find column in insert/update tlist */ + if (context->event == CMD_UPDATE) + { + /* For update, just change unmatched var's varno */ + var = (Var *) copyObject(var); + var->varno = context->update_varno; + var->varnoold = context->update_varno; + return (Node *) var; + } + else + { + /* Otherwise replace unmatched var with a null */ + return (Node *) makeNullConst(var->vartype); + } + } + else + { + /* Make a copy of the tlist item to return */ + Node *n = copyObject(tle->expr); + + /* Adjust varlevelsup if tlist item is from higher query */ + if (var->varlevelsup > 0) + IncrementVarSublevelsUp(n, var->varlevelsup, 0); + /* Report it if we are adding a sublink to query */ + if (!context->inserted_sublink) + context->inserted_sublink = checkExprHasSubLink(n); + return n; + } +} + static Node * ResolveNew_mutator(Node *node, ResolveNew_context *context) { @@ -881,45 +925,41 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context) if (this_varno == context->target_varno && this_varlevelsup == context->sublevels_up) { - TargetEntry *tle; - - /* band-aid: don't do the wrong thing with a whole-tuple Var */ if (var->varattno == InvalidAttrNumber) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot handle whole-row reference"))); - - tle = get_tle_by_resno(context->targetlist, var->varattno); - - if (tle == NULL) { - if (context->event == CMD_UPDATE) - { - /* For update, just change unmatched var's varno */ - var = (Var *) copyObject(node); - var->varno = context->update_varno; - var->varnoold = context->update_varno; - return (Node *) var; - } - else - { - /* Otherwise replace unmatched var with a null */ - return (Node *) makeNullConst(var->vartype); - } - } - else - { - /* Make a copy of the tlist item to return */ - Node *n = copyObject(tle->expr); + /* Must expand whole-tuple reference into RowExpr */ + RangeTblEntry *rte = context->target_rte; + RowExpr *rowexpr; + List *fields = NIL; + AttrNumber nfields = length(rte->eref->colnames); + AttrNumber nf; - /* Adjust varlevelsup if tlist item is from higher query */ - if (this_varlevelsup > 0) - IncrementVarSublevelsUp(n, this_varlevelsup, 0); - /* Report it if we are adding a sublink to query */ - if (!context->inserted_sublink) - context->inserted_sublink = checkExprHasSubLink(n); - return n; + for (nf = 1; nf <= nfields; nf++) + { + Oid vartype; + int32 vartypmod; + Var *newvar; + + get_rte_attribute_type(rte, nf, &vartype, &vartypmod); + newvar = makeVar(this_varno, + nf, + vartype, + vartypmod, + this_varlevelsup); + fields = lappend(fields, + resolve_one_var(newvar, context)); + } + + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + + return (Node *) rowexpr; } + + /* Normal case for scalar variable */ + return resolve_one_var(var, context); } /* otherwise fall through to copy the var normally */ } @@ -948,12 +988,14 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context) Node * ResolveNew(Node *node, int target_varno, int sublevels_up, + RangeTblEntry *target_rte, List *targetlist, int event, int update_varno) { ResolveNew_context context; context.target_varno = target_varno; context.sublevels_up = sublevels_up; + context.target_rte = target_rte; context.targetlist = targetlist; context.event = event; context.update_varno = update_varno; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9cb90444a87..95e457c261f 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.165 2004/05/07 03:19:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.166 2004/05/10 22:44:46 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -2432,6 +2432,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_ArrayRef: case T_ArrayExpr: + case T_RowExpr: case T_CoalesceExpr: case T_NullIfExpr: case T_Aggref: @@ -2528,6 +2529,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_BoolExpr: /* lower precedence */ case T_ArrayRef: /* other separators */ case T_ArrayExpr: /* other separators */ + case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ case T_NullIfExpr: /* other separators */ case T_Aggref: /* own parentheses */ @@ -2574,6 +2576,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) } case T_ArrayRef: /* other separators */ case T_ArrayExpr: /* other separators */ + case T_RowExpr: /* other separators */ case T_CoalesceExpr: /* own parentheses */ case T_NullIfExpr: /* other separators */ case T_Aggref: /* own parentheses */ @@ -2942,11 +2945,9 @@ get_rule_expr(Node *node, deparse_context *context, * arg.fieldname, but most cases where FieldSelect is used * are *not* simple. So, always use parenthesized syntax. */ - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, '('); + appendStringInfoChar(buf, '('); get_rule_expr_paren((Node *) fselect->arg, context, true, node); - if (!PRETTY_PAREN(context)) - appendStringInfoChar(buf, ')'); + appendStringInfoChar(buf, ')'); appendStringInfo(buf, ".%s", quote_identifier(fieldname)); } break; @@ -3051,6 +3052,33 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + List *arg; + char *sep; + + /* + * SQL99 allows "ROW" to be omitted when length(args) > 1, + * but for simplicity we always print it. + */ + appendStringInfo(buf, "ROW("); + sep = ""; + foreach(arg, rowexpr->args) + { + Node *e = (Node *) lfirst(arg); + + appendStringInfo(buf, sep); + get_rule_expr(e, context, true); + sep = ", "; + } + appendStringInfo(buf, ")"); + if (rowexpr->row_format == COERCE_EXPLICIT_CAST) + appendStringInfo(buf, "::%s", + format_type_with_typemod(rowexpr->row_typeid, -1)); + } + break; + case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 46a8a2d06f3..0e957db82ca 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.228 2004/05/08 21:21:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.229 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200405081 +#define CATALOG_VERSION_NO 200405101 #endif diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index a3088ca7f62..52873e86469 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.109 2004/04/01 21:28:46 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.110 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -175,6 +175,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate, TupleDesc tupType); extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid); extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid); +extern TupleDesc ExecTypeFromExprList(List *exprList); extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg); typedef struct TupOutputState diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 8a0fbf7be0f..325bf876800 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.115 2004/04/01 21:28:46 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.116 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -613,6 +613,17 @@ typedef struct ArrayExprState char elemalign; /* typalign of the element type */ } ArrayExprState; +/* ---------------- + * RowExprState node + * ---------------- + */ +typedef struct RowExprState +{ + ExprState xprstate; + List *args; /* the arguments */ + TupleDesc tupdesc; /* descriptor for result tuples */ +} RowExprState; + /* ---------------- * CoalesceExprState node * ---------------- diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 7562e1a82b6..085e76d03bf 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.48 2003/11/29 22:41:06 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.49 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -45,6 +45,8 @@ extern Const *makeConst(Oid consttype, extern Const *makeNullConst(Oid consttype); +extern Node *makeBoolConst(bool value, bool isnull); + extern Expr *makeBoolExpr(BoolExprType boolop, List *args); extern Alias *makeAlias(const char *aliasname, List *colnames); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index a776607ec66..20c1b08f5c5 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.153 2004/05/05 04:48:47 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.154 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -115,6 +115,7 @@ typedef enum NodeTag T_CaseWhen, T_CaseTestExpr, T_ArrayExpr, + T_RowExpr, T_CoalesceExpr, T_NullIfExpr, T_NullTest, @@ -145,6 +146,7 @@ typedef enum NodeTag T_CaseExprState, T_CaseWhenState, T_ArrayExprState, + T_RowExprState, T_CoalesceExprState, T_CoerceToDomainState, T_DomainConstraintState, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 567310fa1c3..d5819eab7bb 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.97 2004/04/01 21:28:46 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.98 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -632,6 +632,23 @@ typedef struct ArrayExpr bool multidims; /* true if elements are sub-arrays */ } ArrayExpr; +/* + * RowExpr - a ROW() expression + */ +typedef struct RowExpr +{ + Expr xpr; + List *args; /* the fields */ + Oid row_typeid; /* RECORDOID or a composite type's ID */ + /* + * Note: we deliberately do NOT store a typmod. Although a typmod + * will be associated with specific RECORD types at runtime, it will + * differ for different backends, and so cannot safely be stored in + * stored parsetrees. We must assume typmod -1 for a RowExpr node. + */ + CoercionForm row_format; /* how to display this node */ +} RowExpr; + /* * CoalesceExpr - a COALESCE expression */ diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h index 087489a0a75..0f0920d0639 100644 --- a/src/include/optimizer/var.h +++ b/src/include/optimizer/var.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/var.h,v 1.29 2003/11/29 22:41:07 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/var.h,v 1.30 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,6 @@ extern Relids pull_varnos(Node *node); extern bool contain_var_reference(Node *node, int varno, int varattno, int levelsup); -extern bool contain_whole_tuple_var(Node *node, int varno, int levelsup); extern bool contain_var_clause(Node *node); extern bool contain_vars_of_level(Node *node, int levelsup); extern bool contain_vars_above_level(Node *node, int levelsup); diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h index d2c694bb4b2..ce4702de35b 100644 --- a/src/include/rewrite/rewriteManip.h +++ b/src/include/rewrite/rewriteManip.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.34 2003/11/29 22:41:11 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.35 2004/05/10 22:44:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,6 +38,7 @@ extern bool checkExprHasAggs(Node *node); extern bool checkExprHasSubLink(Node *node); extern Node *ResolveNew(Node *node, int target_varno, int sublevels_up, - List *targetlist, int event, int update_varno); + RangeTblEntry *target_rte, + List *targetlist, int event, int update_varno); #endif /* REWRITEMANIP_H */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index b71a71bbaac..3a90b464851 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.99 2004/04/01 21:28:46 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.100 2004/05/10 22:44:49 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -3752,6 +3752,16 @@ exec_simple_check_node(Node *node) return TRUE; } + case T_RowExpr: + { + RowExpr *expr = (RowExpr *) node; + + if (!exec_simple_check_node((Node *) expr->args)) + return FALSE; + + return TRUE; + } + case T_CoalesceExpr: { CoalesceExpr *expr = (CoalesceExpr *) node; diff --git a/src/test/regress/input/misc.source b/src/test/regress/input/misc.source index ebf13626c7f..cfaaea95131 100644 --- a/src/test/regress/input/misc.source +++ b/src/test/regress/input/misc.source @@ -218,6 +218,17 @@ SELECT hobbies_by_name('basketball'); SELECT name, overpaid(emp.*) FROM emp; +-- +-- Try a few cases with SQL-spec row constructor expressions +-- +SELECT * FROM equipment(ROW('skywalking', 'mer')); + +SELECT name(equipment(ROW('skywalking', 'mer'))); + +SELECT *, name(equipment(h.*)) FROM hobbies_r h; + +SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h; + -- -- check that old-style C functions work properly with TOASTed values -- diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index 52ecabbd908..2237e0c5966 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -45,9 +45,9 @@ SELECT '' AS four, * FROM DEFAULTEXPR_TBL; -- syntax errors -- test for extraneous comma CREATE TABLE error_tbl (i int DEFAULT (100, )); -ERROR: syntax error at or near "," at character 43 +ERROR: syntax error at or near ")" at character 45 LINE 1: CREATE TABLE error_tbl (i int DEFAULT (100, )); - ^ + ^ -- this will fail because gram.y uses b_expr not a_expr for defaults, -- to avoid a shift/reduce conflict that arises from NOT NULL being -- part of the column definition syntax: diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source index 3173f718c6c..0c1ed5deaf4 100644 --- a/src/test/regress/output/misc.source +++ b/src/test/regress/output/misc.source @@ -686,6 +686,45 @@ SELECT name, overpaid(emp.*) FROM emp; linda | f (6 rows) +-- +-- Try a few cases with SQL-spec row constructor expressions +-- +SELECT * FROM equipment(ROW('skywalking', 'mer')); + name | hobby +------+------------ + guts | skywalking +(1 row) + +SELECT name(equipment(ROW('skywalking', 'mer'))); + name +------ + guts +(1 row) + +SELECT *, name(equipment(h.*)) FROM hobbies_r h; + name | person | name +-------------+--------+--------------- + posthacking | mike | advil + posthacking | mike | peet's coffee + posthacking | jeff | advil + posthacking | jeff | peet's coffee + basketball | joe | hightops + basketball | sally | hightops + skywalking | | guts +(7 rows) + +SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h; + name | person | name +-------------+--------+--------------- + posthacking | mike | advil + posthacking | mike | peet's coffee + posthacking | jeff | advil + posthacking | jeff | peet's coffee + basketball | joe | hightops + basketball | sally | hightops + skywalking | | guts +(7 rows) + -- -- check that old-style C functions work properly with TOASTed values --