diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/gram.y | 98 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 192 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 836 | ||||
-rw-r--r-- | src/backend/parser/parse_type.c | 2 | ||||
-rw-r--r-- | src/backend/parser/parse_utilcmd.c | 2 |
5 files changed, 697 insertions, 433 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 11f629118b0..19220971da6 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -406,6 +406,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); a_expr b_expr c_expr AexprConst indirection_el columnref in_expr having_clause func_table array_expr ExclusionWhereClause +%type <list> func_table_item func_table_list opt_col_def_list +%type <boolean> opt_ordinality %type <list> ExclusionConstraintList ExclusionConstraintElem %type <list> func_arg_list %type <node> func_arg_expr @@ -613,6 +615,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME + /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ %left UNION EXCEPT @@ -1926,10 +1929,11 @@ alter_table_cmd: n->subtype = AT_AlterColumnType; n->name = $3; n->def = (Node *) def; - /* We only use these three fields of the ColumnDef node */ + /* We only use these fields of the ColumnDef node */ def->typeName = $6; def->collClause = (CollateClause *) $7; def->raw_default = $8; + def->location = @3; $$ = (Node *)n; } /* ALTER FOREIGN TABLE <name> ALTER [COLUMN] <colname> OPTIONS */ @@ -2354,10 +2358,11 @@ alter_type_cmd: n->name = $3; n->def = (Node *) def; n->behavior = $8; - /* We only use these three fields of the ColumnDef node */ + /* We only use these fields of the ColumnDef node */ def->typeName = $6; def->collClause = (CollateClause *) $7; def->raw_default = NULL; + def->location = @3; $$ = (Node *)n; } ; @@ -2782,6 +2787,7 @@ columnDef: ColId Typename create_generic_options ColQualList n->fdwoptions = $3; SplitColQualList($4, &n->constraints, &n->collClause, yyscanner); + n->location = @1; $$ = (Node *)n; } ; @@ -2801,6 +2807,7 @@ columnOptions: ColId WITH OPTIONS ColQualList n->collOid = InvalidOid; SplitColQualList($4, &n->constraints, &n->collClause, yyscanner); + n->location = @1; $$ = (Node *)n; } ; @@ -9648,44 +9655,19 @@ table_ref: relation_expr opt_alias_clause } | func_table func_alias_clause { - RangeFunction *n = makeNode(RangeFunction); - n->lateral = false; - n->ordinality = false; - n->funccallnode = $1; + RangeFunction *n = (RangeFunction *) $1; n->alias = linitial($2); n->coldeflist = lsecond($2); $$ = (Node *) n; } - | func_table WITH_ORDINALITY func_alias_clause - { - RangeFunction *n = makeNode(RangeFunction); - n->lateral = false; - n->ordinality = true; - n->funccallnode = $1; - n->alias = linitial($3); - n->coldeflist = lsecond($3); - $$ = (Node *) n; - } | LATERAL_P func_table func_alias_clause { - RangeFunction *n = makeNode(RangeFunction); + RangeFunction *n = (RangeFunction *) $2; n->lateral = true; - n->ordinality = false; - n->funccallnode = $2; n->alias = linitial($3); n->coldeflist = lsecond($3); $$ = (Node *) n; } - | LATERAL_P func_table WITH_ORDINALITY func_alias_clause - { - RangeFunction *n = makeNode(RangeFunction); - n->lateral = true; - n->ordinality = true; - n->funccallnode = $2; - n->alias = linitial($4); - n->coldeflist = lsecond($4); - $$ = (Node *) n; - } | select_with_parens opt_alias_clause { RangeSubselect *n = makeNode(RangeSubselect); @@ -9996,7 +9978,54 @@ relation_expr_opt_alias: relation_expr %prec UMINUS } ; -func_table: func_expr_windowless { $$ = $1; } +/* + * func_table represents a function invocation in a FROM list. It can be + * a plain function call, like "foo(...)", or a TABLE expression with + * one or more function calls, "TABLE (foo(...), bar(...))", + * optionally with WITH ORDINALITY attached. + * In the TABLE syntax, a column definition list can be given for each + * function, for example: + * TABLE (foo() AS (foo_res_a text, foo_res_b text), + * bar() AS (bar_res_a text, bar_res_b text)) + * It's also possible to attach a column definition list to the RangeFunction + * as a whole, but that's handled by the table_ref production. + */ +func_table: func_expr_windowless opt_ordinality + { + RangeFunction *n = makeNode(RangeFunction); + n->lateral = false; + n->ordinality = $2; + n->is_table = false; + n->functions = list_make1(list_make2($1, NIL)); + /* alias and coldeflist are set by table_ref production */ + $$ = (Node *) n; + } + | TABLE '(' func_table_list ')' opt_ordinality + { + RangeFunction *n = makeNode(RangeFunction); + n->lateral = false; + n->ordinality = $5; + n->is_table = true; + n->functions = $3; + /* alias and coldeflist are set by table_ref production */ + $$ = (Node *) n; + } + ; + +func_table_item: func_expr_windowless opt_col_def_list + { $$ = list_make2($1, $2); } + ; + +func_table_list: func_table_item { $$ = list_make1($1); } + | func_table_list ',' func_table_item { $$ = lappend($1, $3); } + ; + +opt_col_def_list: AS '(' TableFuncElementList ')' { $$ = $3; } + | /*EMPTY*/ { $$ = NIL; } + ; + +opt_ordinality: WITH_ORDINALITY { $$ = true; } + | /*EMPTY*/ { $$ = false; } ; @@ -10051,6 +10080,7 @@ TableFuncElement: ColId Typename opt_collate_clause n->collClause = (CollateClause *) $3; n->collOid = InvalidOid; n->constraints = NIL; + n->location = @1; $$ = (Node *)n; } ; @@ -11172,11 +11202,11 @@ func_application: func_name '(' ')' /* - * func_expr and its cousin func_expr_windowless is split out from c_expr just + * func_expr and its cousin func_expr_windowless are split out from c_expr just * so that we have classifications for "everything that is a function call or - * looks like one". This isn't very important, but it saves us having to document - * which variants are legal in the backwards-compatible functional-index syntax - * for CREATE INDEX. + * looks like one". This isn't very important, but it saves us having to + * document which variants are legal in places like "FROM function()" or the + * backwards-compatible functional-index syntax for CREATE INDEX. * (Note that many of the special SQL functions wouldn't actually make any * sense as functional index entries, but we ignore that consideration here.) */ diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 7a1261d0fd2..8b4c0ae0d3b 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -24,6 +24,7 @@ #include "optimizer/tlist.h" #include "parser/analyze.h" #include "parser/parsetree.h" +#include "parser/parser.h" #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" @@ -515,24 +516,18 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) static RangeTblEntry * transformRangeFunction(ParseState *pstate, RangeFunction *r) { - Node *funcexpr; - char *funcname; + List *funcexprs = NIL; + List *funcnames = NIL; + List *coldeflists = NIL; bool is_lateral; RangeTblEntry *rte; - - /* - * Get function name for possible use as alias. We use the same - * transformation rules as for a SELECT output expression. For a FuncCall - * node, the result will be the function name, but it is possible for the - * grammar to hand back other node types. - */ - funcname = FigureColname(r->funccallnode); + ListCell *lc; /* * We make lateral_only names of this level visible, whether or not the - * function is explicitly marked LATERAL. This is needed for SQL spec - * compliance in the case of UNNEST(), and seems useful on convenience - * grounds for all functions in FROM. + * RangeFunction is explicitly marked LATERAL. This is needed for SQL + * spec compliance in the case of UNNEST(), and seems useful on + * convenience grounds for all functions in FROM. * * (LATERAL can't nest within a single pstate level, so we don't need * save/restore logic here.) @@ -541,46 +536,171 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) pstate->p_lateral_active = true; /* - * Transform the raw expression. + * Transform the raw expressions. + * + * While transforming, also save function names for possible use as alias + * and column names. We use the same transformation rules as for a SELECT + * output expression. For a FuncCall node, the result will be the + * function name, but it is possible for the grammar to hand back other + * node types. + * + * We have to get this info now, because FigureColname only works on raw + * parsetrees. Actually deciding what to do with the names is left up to + * addRangeTableEntryForFunction. + * + * Likewise, collect column definition lists if there were any. But + * complain if we find one here and the RangeFunction has one too. */ - funcexpr = transformExpr(pstate, r->funccallnode, EXPR_KIND_FROM_FUNCTION); + foreach(lc, r->functions) + { + List *pair = (List *) lfirst(lc); + Node *fexpr; + List *coldeflist; + + /* Disassemble the function-call/column-def-list pairs */ + Assert(list_length(pair) == 2); + fexpr = (Node *) linitial(pair); + coldeflist = (List *) lsecond(pair); + + /* + * If we find a function call unnest() with more than one argument and + * no special decoration, transform it into separate unnest() calls on + * each argument. This is a kluge, for sure, but it's less nasty than + * other ways of implementing the SQL-standard UNNEST() syntax. + * + * If there is any decoration (including a coldeflist), we don't + * transform, which probably means a no-such-function error later. We + * could alternatively throw an error right now, but that doesn't seem + * tremendously helpful. If someone is using any such decoration, + * then they're not using the SQL-standard syntax, and they're more + * likely expecting an un-tweaked function call. + * + * Note: the transformation changes a non-schema-qualified unnest() + * function name into schema-qualified pg_catalog.unnest(). This + * choice is also a bit debatable, but it seems reasonable to force + * use of built-in unnest() when we make this transformation. + */ + if (IsA(fexpr, FuncCall)) + { + FuncCall *fc = (FuncCall *) fexpr; + + if (list_length(fc->funcname) == 1 && + strcmp(strVal(linitial(fc->funcname)), "unnest") == 0 && + list_length(fc->args) > 1 && + fc->agg_order == NIL && + fc->agg_filter == NULL && + !fc->agg_star && + !fc->agg_distinct && + !fc->func_variadic && + fc->over == NULL && + coldeflist == NIL) + { + ListCell *lc; + + foreach(lc, fc->args) + { + Node *arg = (Node *) lfirst(lc); + FuncCall *newfc; + + newfc = makeFuncCall(SystemFuncName("unnest"), + list_make1(arg), + fc->location); + + funcexprs = lappend(funcexprs, + transformExpr(pstate, (Node *) newfc, + EXPR_KIND_FROM_FUNCTION)); + + funcnames = lappend(funcnames, + FigureColname((Node *) newfc)); + + /* coldeflist is empty, so no error is possible */ + + coldeflists = lappend(coldeflists, coldeflist); + } + continue; /* done with this function item */ + } + } + + /* normal case ... */ + funcexprs = lappend(funcexprs, + transformExpr(pstate, fexpr, + EXPR_KIND_FROM_FUNCTION)); + + funcnames = lappend(funcnames, + FigureColname(fexpr)); + + if (coldeflist && r->coldeflist) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple column definition lists are not allowed for the same function"), + parser_errposition(pstate, + exprLocation((Node *) r->coldeflist)))); + + coldeflists = lappend(coldeflists, coldeflist); + } pstate->p_lateral_active = false; /* - * We must assign collations now so that we can fill funccolcollations. + * We must assign collations now so that the RTE exposes correct collation + * info for Vars created from it. */ - assign_expr_collations(pstate, funcexpr); + assign_list_collations(pstate, funcexprs); + + /* + * Install the top-level coldeflist if there was one (we already checked + * that there was no conflicting per-function coldeflist). + * + * We only allow this when there's a single function (even after UNNEST + * expansion) and no WITH ORDINALITY. The reason for the latter + * restriction is that it's not real clear whether the ordinality column + * should be in the coldeflist, and users are too likely to make mistakes + * in one direction or the other. Putting the coldeflist inside TABLE() + * is much clearer in this case. + */ + if (r->coldeflist) + { + if (list_length(funcexprs) != 1) + { + if (r->is_table) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("TABLE() with multiple functions cannot have a column definition list"), + errhint("Put a separate column definition list for each function inside TABLE()."), + parser_errposition(pstate, + exprLocation((Node *) r->coldeflist)))); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("UNNEST() with multiple arguments cannot have a column definition list"), + errhint("Use separate UNNEST() calls inside TABLE(), and attach a column definition list to each one."), + parser_errposition(pstate, + exprLocation((Node *) r->coldeflist)))); + } + if (r->ordinality) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("WITH ORDINALITY cannot be used with a column definition list"), + errhint("Put the column definition list inside TABLE()."), + parser_errposition(pstate, + exprLocation((Node *) r->coldeflist)))); + + coldeflists = list_make1(r->coldeflist); + } /* * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if * there are any lateral cross-references in it. */ - is_lateral = r->lateral || contain_vars_of_level(funcexpr, 0); + is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0); /* * OK, build an RTE for the function. */ - rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr, + rte = addRangeTableEntryForFunction(pstate, + funcnames, funcexprs, coldeflists, r, is_lateral, true); - /* - * If a coldeflist was supplied, ensure it defines a legal set of names - * (no duplicates) and datatypes (no pseudo-types, for instance). - * addRangeTableEntryForFunction looked up the type names but didn't check - * them further than that. - */ - if (r->coldeflist) - { - TupleDesc tupdesc; - - tupdesc = BuildDescFromLists(rte->eref->colnames, - rte->funccoltypes, - rte->funccoltypmods, - rte->funccolcollations); - CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false); - } - return rte; } diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 0052d21ad62..cd8d75e23d9 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -44,6 +44,7 @@ static void expandRelation(Oid relid, Alias *eref, int location, bool include_dropped, List **colnames, List **colvars); static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, + int count, int offset, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars); @@ -807,25 +808,20 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte) /* * buildRelationAliases * Construct the eref column name list for a relation RTE. - * This code is also used for the case of a function RTE returning - * a named composite type or a registered RECORD type. + * This code is also used for function RTEs. * * tupdesc: the physical column information * alias: the user-supplied alias, or NULL if none * eref: the eref Alias to store column names in - * ordinality: true if an ordinality column is to be added * * eref->colnames is filled in. Also, alias->colnames is rebuilt to insert * empty strings for any dropped columns, so that it will be one-to-one with * physical column numbers. * - * If we add an ordinality column, its colname comes from the alias if there - * is one, otherwise we default it. (We don't add it to alias->colnames.) - * * It is an error for there to be more aliases present than required. */ static void -buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality) +buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) { int maxattrs = tupdesc->natts; ListCell *aliaslc; @@ -877,98 +873,56 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinali eref->colnames = lappend(eref->colnames, attrname); } - /* tack on the ordinality column at the end */ - if (ordinality) - { - Value *attrname; - - if (aliaslc) - { - attrname = (Value *) lfirst(aliaslc); - aliaslc = lnext(aliaslc); - alias->colnames = lappend(alias->colnames, attrname); - } - else - { - attrname = makeString(pstrdup("ordinality")); - } - - eref->colnames = lappend(eref->colnames, attrname); - } - /* Too many user-supplied aliases? */ if (aliaslc) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("table \"%s\" has %d columns available but %d columns specified", - eref->aliasname, - maxattrs - numdropped + (ordinality ? 1 : 0), - numaliases))); + eref->aliasname, maxattrs - numdropped, numaliases))); } /* - * buildScalarFunctionAlias - * Construct the eref column name list for a function RTE, + * chooseScalarFunctionAlias + * Select the column alias for a function in a function RTE, * when the function returns a scalar type (not composite or RECORD). * * funcexpr: transformed expression tree for the function call - * funcname: function name (used only for error message) - * alias: the user-supplied alias, or NULL if none - * eref: the eref Alias to store column names in - * ordinality: whether to add an ordinality column - * - * eref->colnames is filled in. + * funcname: function name (as determined by FigureColname) + * alias: the user-supplied alias for the RTE, or NULL if none + * nfuncs: the number of functions appearing in the function RTE * - * The caller must have previously filled in eref->aliasname, which will - * be used as the result column name if no alias is given. - * - * A user-supplied Alias can contain up to two column alias names; one for - * the function result, and one for the ordinality column; it is an error - * to specify more aliases than required. + * Note that the name we choose might be overridden later, if the user-given + * alias includes column alias names. That's of no concern here. */ -static void -buildScalarFunctionAlias(Node *funcexpr, char *funcname, - Alias *alias, Alias *eref, bool ordinality) +static char * +chooseScalarFunctionAlias(Node *funcexpr, char *funcname, + Alias *alias, int nfuncs) { - Assert(eref->colnames == NIL); + char *pname; - /* Use user-specified column alias if there is one. */ - if (alias && alias->colnames != NIL) - { - if (list_length(alias->colnames) > (ordinality ? 2 : 1)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), - errmsg("too many column aliases specified for function %s", - funcname))); - - eref->colnames = copyObject(alias->colnames); - } - else + /* + * If the expression is a simple function call, and the function has a + * single OUT parameter that is named, use the parameter's name. + */ + if (funcexpr && IsA(funcexpr, FuncExpr)) { - char *pname = NULL; - - /* - * If the expression is a simple function call, and the function has a - * single OUT parameter that is named, use the parameter's name. - */ - if (funcexpr && IsA(funcexpr, FuncExpr)) - pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid); - - /* - * Otherwise, use the previously-determined alias name provided by the - * caller (which is not necessarily the function name!) - */ - if (!pname) - pname = eref->aliasname; - - eref->colnames = list_make1(makeString(pname)); + pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid); + if (pname) + return pname; } - /* If we don't have a name for the ordinality column yet, supply a default. */ - if (ordinality && list_length(eref->colnames) < 2) - eref->colnames = lappend(eref->colnames, makeString(pstrdup("ordinality"))); + /* + * If there's just one function in the RTE, and the user gave an RTE alias + * name, use that name. (This makes FROM func() AS foo use "foo" as the + * column name as well as the table alias.) + */ + if (nfuncs == 1 && alias) + return alias->aliasname; - return; + /* + * Otherwise use the function name. + */ + return funcname; } /* @@ -1064,7 +1018,7 @@ addRangeTableEntry(ParseState *pstate, * and/or actual column names. */ rte->eref = makeAlias(refname, NIL); - buildRelationAliases(rel->rd_att, alias, rte->eref, false); + buildRelationAliases(rel->rd_att, alias, rte->eref); /* * Drop the rel refcount, but keep the access lock till end of transaction @@ -1124,7 +1078,7 @@ addRangeTableEntryForRelation(ParseState *pstate, * and/or actual column names. */ rte->eref = makeAlias(refname, NIL); - buildRelationAliases(rel->rd_att, alias, rte->eref, false); + buildRelationAliases(rel->rd_att, alias, rte->eref); /* * Set flags and access permissions. @@ -1230,122 +1184,233 @@ addRangeTableEntryForSubquery(ParseState *pstate, } /* - * Add an entry for a function to the pstate's range table (p_rtable). + * Add an entry for a function (or functions) to the pstate's range table + * (p_rtable). * * This is just like addRangeTableEntry() except that it makes a function RTE. */ RangeTblEntry * addRangeTableEntryForFunction(ParseState *pstate, - char *funcname, - Node *funcexpr, + List *funcnames, + List *funcexprs, + List *coldeflists, RangeFunction *rangefunc, bool lateral, bool inFromCl) { RangeTblEntry *rte = makeNode(RangeTblEntry); - TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; Alias *alias = rangefunc->alias; - List *coldeflist = rangefunc->coldeflist; Alias *eref; + char *aliasname; + int nfuncs = list_length(funcexprs); + TupleDesc *functupdescs; + TupleDesc tupdesc; + ListCell *lc1, + *lc2, + *lc3; + int i; + int j; + int funcno; + int natts, + totalatts; rte->rtekind = RTE_FUNCTION; rte->relid = InvalidOid; rte->subquery = NULL; - rte->funcexpr = funcexpr; - rte->funccoltypes = NIL; - rte->funccoltypmods = NIL; - rte->funccolcollations = NIL; + rte->functions = NIL; /* we'll fill this list below */ + rte->funcordinality = rangefunc->ordinality; rte->alias = alias; - eref = makeAlias(alias ? alias->aliasname : funcname, NIL); - rte->eref = eref; - - /* - * Now determine if the function returns a simple or composite type. - */ - functypclass = get_expr_result_type(funcexpr, - &funcrettype, - &tupdesc); - /* - * A coldeflist is required if the function returns RECORD and hasn't got - * a predetermined record type, and is prohibited otherwise. + * Choose the RTE alias name. We default to using the first function's + * name even when there's more than one; which is maybe arguable but beats + * using something constant like "table". */ - if (coldeflist != NIL) - { - if (functypclass != TYPEFUNC_RECORD) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a column definition list is only allowed for functions returning \"record\""), - parser_errposition(pstate, exprLocation(funcexpr)))); - } + if (alias) + aliasname = alias->aliasname; else + aliasname = linitial(funcnames); + + eref = makeAlias(aliasname, NIL); + rte->eref = eref; + + /* Process each function ... */ + functupdescs = (TupleDesc *) palloc(nfuncs * sizeof(TupleDesc)); + + totalatts = 0; + funcno = 0; + forthree(lc1, funcexprs, lc2, funcnames, lc3, coldeflists) { - if (functypclass == TYPEFUNC_RECORD) + Node *funcexpr = (Node *) lfirst(lc1); + char *funcname = (char *) lfirst(lc2); + List *coldeflist = (List *) lfirst(lc3); + RangeTblFunction *rtfunc = makeNode(RangeTblFunction); + TypeFuncClass functypclass; + Oid funcrettype; + + /* Initialize RangeTblFunction node */ + rtfunc->funcexpr = funcexpr; + rtfunc->funccolnames = NIL; + rtfunc->funccoltypes = NIL; + rtfunc->funccoltypmods = NIL; + rtfunc->funccolcollations = NIL; + rtfunc->funcparams = NULL; /* not set until planning */ + + /* + * Now determine if the function returns a simple or composite type. + */ + functypclass = get_expr_result_type(funcexpr, + &funcrettype, + &tupdesc); + + /* + * A coldeflist is required if the function returns RECORD and hasn't + * got a predetermined record type, and is prohibited otherwise. + */ + if (coldeflist != NIL) + { + if (functypclass != TYPEFUNC_RECORD) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is only allowed for functions returning \"record\""), + parser_errposition(pstate, + exprLocation((Node *) coldeflist)))); + } + else + { + if (functypclass == TYPEFUNC_RECORD) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is required for functions returning \"record\""), + parser_errposition(pstate, exprLocation(funcexpr)))); + } + + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + chooseScalarFunctionAlias(funcexpr, funcname, + alias, nfuncs), + funcrettype, + -1, + 0); + } + else if (functypclass == TYPEFUNC_RECORD) + { + ListCell *col; + + /* + * Use the column definition list to construct a tupdesc and fill + * in the RangeTblFunction's lists. + */ + tupdesc = CreateTemplateTupleDesc(list_length(coldeflist), false); + i = 1; + foreach(col, coldeflist) + { + ColumnDef *n = (ColumnDef *) lfirst(col); + char *attrname; + Oid attrtype; + int32 attrtypmod; + Oid attrcollation; + + attrname = n->colname; + if (n->typeName->setof) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" cannot be declared SETOF", + attrname), + parser_errposition(pstate, n->location))); + typenameTypeIdAndMod(pstate, n->typeName, + &attrtype, &attrtypmod); + attrcollation = GetColumnDefCollation(pstate, n, attrtype); + TupleDescInitEntry(tupdesc, + (AttrNumber) i, + attrname, + attrtype, + attrtypmod, + 0); + TupleDescInitEntryCollation(tupdesc, + (AttrNumber) i, + attrcollation); + rtfunc->funccolnames = lappend(rtfunc->funccolnames, + makeString(pstrdup(attrname))); + rtfunc->funccoltypes = lappend_oid(rtfunc->funccoltypes, + attrtype); + rtfunc->funccoltypmods = lappend_int(rtfunc->funccoltypmods, + attrtypmod); + rtfunc->funccolcollations = lappend_oid(rtfunc->funccolcollations, + attrcollation); + + i++; + } + + /* + * Ensure that the coldeflist defines a legal set of names (no + * duplicates) and datatypes (no pseudo-types, for instance). + */ + CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false); + } + else ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a column definition list is required for functions returning \"record\""), + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function \"%s\" in FROM has unsupported return type %s", + funcname, format_type_be(funcrettype)), parser_errposition(pstate, exprLocation(funcexpr)))); - } - if (functypclass == TYPEFUNC_COMPOSITE) - { - /* Composite data type, e.g. a table's row type */ - Assert(tupdesc); - /* Build the column alias list */ - buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality); - } - else if (functypclass == TYPEFUNC_SCALAR) - { - /* Base data type, i.e. scalar */ - buildScalarFunctionAlias(funcexpr, funcname, alias, eref, rangefunc->ordinality); + /* Finish off the RangeTblFunction and add it to the RTE's list */ + rtfunc->funccolcount = tupdesc->natts; + rte->functions = lappend(rte->functions, rtfunc); + + /* Save the tupdesc for use below */ + functupdescs[funcno] = tupdesc; + totalatts += tupdesc->natts; + funcno++; } - else if (functypclass == TYPEFUNC_RECORD) - { - ListCell *col; + /* + * If there's more than one function, or we want an ordinality column, we + * have to produce a merged tupdesc. + */ + if (nfuncs > 1 || rangefunc->ordinality) + { if (rangefunc->ordinality) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("WITH ORDINALITY is not supported for functions returning \"record\""), - parser_errposition(pstate, exprLocation(funcexpr)))); + totalatts++; - /* - * Use the column definition list to form the alias list and - * funccoltypes/funccoltypmods/funccolcollations lists. - */ - foreach(col, coldeflist) + /* Merge the tuple descs of each function into a composite one */ + tupdesc = CreateTemplateTupleDesc(totalatts, false); + natts = 0; + for (i = 0; i < nfuncs; i++) { - ColumnDef *n = (ColumnDef *) lfirst(col); - char *attrname; - Oid attrtype; - int32 attrtypmod; - Oid attrcollation; - - attrname = pstrdup(n->colname); - if (n->typeName->setof) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("column \"%s\" cannot be declared SETOF", - attrname), - parser_errposition(pstate, n->typeName->location))); - typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod); - attrcollation = GetColumnDefCollation(pstate, n, attrtype); - eref->colnames = lappend(eref->colnames, makeString(attrname)); - rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype); - rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod); - rte->funccolcollations = lappend_oid(rte->funccolcollations, - attrcollation); + for (j = 1; j <= functupdescs[i]->natts; j++) + TupleDescCopyEntry(tupdesc, ++natts, functupdescs[i], j); } + + /* Add the ordinality column if needed */ + if (rangefunc->ordinality) + TupleDescInitEntry(tupdesc, + (AttrNumber) ++natts, + "ordinality", + INT8OID, + -1, + 0); + + Assert(natts == totalatts); } else - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function \"%s\" in FROM has unsupported return type %s", - funcname, format_type_be(funcrettype)), - parser_errposition(pstate, exprLocation(funcexpr)))); + { + /* We can just use the single function's tupdesc as-is */ + tupdesc = functupdescs[0]; + } + + /* Use the tupdesc while assigning column aliases for the RTE */ + buildRelationAliases(tupdesc, alias, eref); /* * Set flags and access permissions. @@ -1354,7 +1419,6 @@ addRangeTableEntryForFunction(ParseState *pstate, * permissions mechanism). */ rte->lateral = lateral; - rte->funcordinality = rangefunc->ordinality; rte->inh = false; /* never true for functions */ rte->inFromCl = inFromCl; @@ -1710,11 +1774,6 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte, * The output lists go into *colnames and *colvars. * If only one of the two kinds of output list is needed, pass NULL for the * output pointer for the unwanted one. - * - * For function RTEs with ORDINALITY, this expansion includes the - * ordinal column, whose type (bigint) had better match the type assumed in the - * executor. The colname for the ordinality column must have been set up already - * in the RTE; it is always last. */ void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, @@ -1780,107 +1839,115 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, case RTE_FUNCTION: { /* Function RTE */ - TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; - int ordinality_attno = 0; - - functypclass = get_expr_result_type(rte->funcexpr, - &funcrettype, - &tupdesc); - if (functypclass == TYPEFUNC_COMPOSITE) - { - /* Composite data type, e.g. a table's row type */ - Assert(tupdesc); + int atts_done = 0; + ListCell *lc; - /* - * we rely here on the fact that expandTupleDesc doesn't - * care about being passed more aliases than it needs. - */ - expandTupleDesc(tupdesc, rte->eref, - rtindex, sublevels_up, location, - include_dropped, colnames, colvars); - - ordinality_attno = tupdesc->natts + 1; - } - else if (functypclass == TYPEFUNC_SCALAR) + foreach(lc, rte->functions) { - /* Base data type, i.e. scalar */ - if (colnames) - *colnames = lappend(*colnames, - linitial(rte->eref->colnames)); - - if (colvars) + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + + functypclass = get_expr_result_type(rtfunc->funcexpr, + &funcrettype, + &tupdesc); + if (functypclass == TYPEFUNC_COMPOSITE) { - Var *varnode; - - varnode = makeVar(rtindex, 1, - funcrettype, -1, - exprCollation(rte->funcexpr), - sublevels_up); - varnode->location = location; - - *colvars = lappend(*colvars, varnode); + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + expandTupleDesc(tupdesc, rte->eref, + rtfunc->funccolcount, atts_done, + rtindex, sublevels_up, location, + include_dropped, colnames, colvars); } - - ordinality_attno = 2; - } - else if (functypclass == TYPEFUNC_RECORD) - { - if (colnames) - *colnames = copyObject(rte->eref->colnames); - if (colvars) + else if (functypclass == TYPEFUNC_SCALAR) { - ListCell *l1; - ListCell *l2; - ListCell *l3; - int attnum = 0; - - forthree(l1, rte->funccoltypes, - l2, rte->funccoltypmods, - l3, rte->funccolcollations) + /* Base data type, i.e. scalar */ + if (colnames) + *colnames = lappend(*colnames, + list_nth(rte->eref->colnames, + atts_done)); + + if (colvars) { - Oid attrtype = lfirst_oid(l1); - int32 attrtypmod = lfirst_int(l2); - Oid attrcollation = lfirst_oid(l3); Var *varnode; - attnum++; - varnode = makeVar(rtindex, - attnum, - attrtype, - attrtypmod, - attrcollation, + varnode = makeVar(rtindex, atts_done + 1, + funcrettype, -1, + exprCollation(rtfunc->funcexpr), sublevels_up); varnode->location = location; + *colvars = lappend(*colvars, varnode); } } + else if (functypclass == TYPEFUNC_RECORD) + { + if (colnames) + { + List *namelist; + + /* extract appropriate subset of column list */ + namelist = list_copy_tail(rte->eref->colnames, + atts_done); + namelist = list_truncate(namelist, + rtfunc->funccolcount); + *colnames = list_concat(*colnames, namelist); + } - /* note, ordinality is not allowed in this case */ - } - else - { - /* addRangeTableEntryForFunction should've caught this */ - elog(ERROR, "function in FROM has unsupported return type"); + if (colvars) + { + ListCell *l1; + ListCell *l2; + ListCell *l3; + int attnum = atts_done; + + forthree(l1, rtfunc->funccoltypes, + l2, rtfunc->funccoltypmods, + l3, rtfunc->funccolcollations) + { + Oid attrtype = lfirst_oid(l1); + int32 attrtypmod = lfirst_int(l2); + Oid attrcollation = lfirst_oid(l3); + Var *varnode; + + attnum++; + varnode = makeVar(rtindex, + attnum, + attrtype, + attrtypmod, + attrcollation, + sublevels_up); + varnode->location = location; + *colvars = lappend(*colvars, varnode); + } + } + } + else + { + /* addRangeTableEntryForFunction should've caught this */ + elog(ERROR, "function in FROM has unsupported return type"); + } + atts_done += rtfunc->funccolcount; } - /* tack on the extra ordinality column if present */ + /* Append the ordinality column if any */ if (rte->funcordinality) { - Assert(ordinality_attno > 0); - if (colnames) - *colnames = lappend(*colnames, llast(rte->eref->colnames)); + *colnames = lappend(*colnames, + llast(rte->eref->colnames)); if (colvars) { - Var *varnode = makeVar(rtindex, - ordinality_attno, - INT8OID, - -1, - InvalidOid, - sublevels_up); + Var *varnode = makeVar(rtindex, + atts_done + 1, + INT8OID, + -1, + InvalidOid, + sublevels_up); + *colvars = lappend(*colvars, varnode); } } @@ -2051,7 +2118,8 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, /* Get the tupledesc and turn it over to expandTupleDesc */ rel = relation_open(relid, AccessShareLock); - expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up, + expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0, + rtindex, sublevels_up, location, include_dropped, colnames, colvars); relation_close(rel, AccessShareLock); @@ -2060,20 +2128,34 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, /* * expandTupleDesc -- expandRTE subroutine * - * Only the required number of column names are used from the Alias; - * it is not an error to supply too many. (ordinality depends on this) + * Generate names and/or Vars for the first "count" attributes of the tupdesc, + * and append them to colnames/colvars. "offset" is added to the varattno + * that each Var would otherwise have, and we also skip the first "offset" + * entries in eref->colnames. (These provisions allow use of this code for + * an individual composite-returning function in an RTE_FUNCTION RTE.) */ static void -expandTupleDesc(TupleDesc tupdesc, Alias *eref, +expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars) { - int maxattrs = tupdesc->natts; - int numaliases = list_length(eref->colnames); + ListCell *aliascell = list_head(eref->colnames); int varattno; - for (varattno = 0; varattno < maxattrs; varattno++) + if (colnames) + { + int i; + + for (i = 0; i < offset; i++) + { + if (aliascell) + aliascell = lnext(aliascell); + } + } + + Assert(count <= tupdesc->natts); + for (varattno = 0; varattno < count; varattno++) { Form_pg_attribute attr = tupdesc->attrs[varattno]; @@ -2093,6 +2175,8 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, makeNullConst(INT4OID, -1, InvalidOid)); } } + if (aliascell) + aliascell = lnext(aliascell); continue; } @@ -2100,10 +2184,16 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, { char *label; - if (varattno < numaliases) - label = strVal(list_nth(eref->colnames, varattno)); + if (aliascell) + { + label = strVal(lfirst(aliascell)); + aliascell = lnext(aliascell); + } else + { + /* If we run out of aliases, use the underlying name */ label = NameStr(attr->attname); + } *colnames = lappend(*colnames, makeString(pstrdup(label))); } @@ -2111,7 +2201,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, { Var *varnode; - varnode = makeVar(rtindex, attr->attnum, + varnode = makeVar(rtindex, varattno + offset + 1, attr->atttypid, attr->atttypmod, attr->attcollation, sublevels_up); @@ -2221,9 +2311,6 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum) /* * get_rte_attribute_type * Get attribute type/typmod/collation information from a RangeTblEntry - * - * Once again, for function RTEs we may have to synthesize the - * ordinality column with the correct type. */ void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, @@ -2278,79 +2365,93 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, case RTE_FUNCTION: { /* Function RTE */ - TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; - - /* - * if ordinality, then a reference to the last column - * in the name list must be referring to the - * ordinality column - */ - if (rte->funcordinality - && attnum == list_length(rte->eref->colnames)) - { - *vartype = INT8OID; - *vartypmod = -1; - *varcollid = InvalidOid; - break; - } - - functypclass = get_expr_result_type(rte->funcexpr, - &funcrettype, - &tupdesc); + ListCell *lc; + int atts_done = 0; - if (functypclass == TYPEFUNC_COMPOSITE) + /* Identify which function covers the requested column */ + foreach(lc, rte->functions) { - /* Composite data type, e.g. a table's row type */ - Form_pg_attribute att_tup; + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); - Assert(tupdesc); - - /* this is probably a can't-happen case */ - if (attnum < 1 || attnum > tupdesc->natts) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column %d of relation \"%s\" does not exist", - attnum, - rte->eref->aliasname))); + if (attnum > atts_done && + attnum <= atts_done + rtfunc->funccolcount) + { + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; - att_tup = tupdesc->attrs[attnum - 1]; + attnum -= atts_done; /* now relative to this func */ + functypclass = get_expr_result_type(rtfunc->funcexpr, + &funcrettype, + &tupdesc); - /* - * If dropped column, pretend it ain't there. See notes - * in scanRTEForColumn. - */ - if (att_tup->attisdropped) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - NameStr(att_tup->attname), - rte->eref->aliasname))); - *vartype = att_tup->atttypid; - *vartypmod = att_tup->atttypmod; - *varcollid = att_tup->attcollation; + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Form_pg_attribute att_tup; + + Assert(tupdesc); + Assert(attnum <= tupdesc->natts); + att_tup = tupdesc->attrs[attnum - 1]; + + /* + * If dropped column, pretend it ain't there. See + * notes in scanRTEForColumn. + */ + if (att_tup->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + NameStr(att_tup->attname), + rte->eref->aliasname))); + *vartype = att_tup->atttypid; + *vartypmod = att_tup->atttypmod; + *varcollid = att_tup->attcollation; + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + *vartype = funcrettype; + *vartypmod = -1; + *varcollid = exprCollation(rtfunc->funcexpr); + } + else if (functypclass == TYPEFUNC_RECORD) + { + *vartype = list_nth_oid(rtfunc->funccoltypes, + attnum - 1); + *vartypmod = list_nth_int(rtfunc->funccoltypmods, + attnum - 1); + *varcollid = list_nth_oid(rtfunc->funccolcollations, + attnum - 1); + } + else + { + /* + * addRangeTableEntryForFunction should've caught + * this + */ + elog(ERROR, "function in FROM has unsupported return type"); + } + return; + } + atts_done += rtfunc->funccolcount; } - else if (functypclass == TYPEFUNC_SCALAR) - { - Assert(attnum == 1); - /* Base data type, i.e. scalar */ - *vartype = funcrettype; - *vartypmod = -1; - *varcollid = exprCollation(rte->funcexpr); - } - else if (functypclass == TYPEFUNC_RECORD) + /* If we get here, must be looking for the ordinality column */ + if (rte->funcordinality && attnum == atts_done + 1) { - *vartype = list_nth_oid(rte->funccoltypes, attnum - 1); - *vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1); - *varcollid = list_nth_oid(rte->funccolcollations, attnum - 1); - } - else - { - /* addRangeTableEntryForFunction should've caught this */ - elog(ERROR, "function in FROM has unsupported return type"); + *vartype = INT8OID; + *vartypmod = -1; + *varcollid = InvalidOid; + return; } + + /* this probably can't happen ... */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, + rte->eref->aliasname))); } break; case RTE_VALUES: @@ -2456,46 +2557,57 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) case RTE_FUNCTION: { /* Function RTE */ - Oid funcrettype = exprType(rte->funcexpr); - Oid funcrelid = typeidTypeRelid(funcrettype); + ListCell *lc; + int atts_done = 0; /* - * if ordinality, then a reference to the last column - * in the name list must be referring to the - * ordinality column, which is not dropped + * Dropped attributes are only possible with functions that + * return named composite types. In such a case we have to + * look up the result type to see if it currently has this + * column dropped. So first, loop over the funcs until we + * find the one that covers the requested column. */ - if (rte->funcordinality - && attnum == list_length(rte->eref->colnames)) + foreach(lc, rte->functions) { - result = false; - } - else if (OidIsValid(funcrelid)) - { - /* - * Composite data type, i.e. a table's row type - * - * Same as ordinary relation RTE - */ - HeapTuple tp; - Form_pg_attribute att_tup; - - tp = SearchSysCache2(ATTNUM, - ObjectIdGetDatum(funcrelid), - Int16GetDatum(attnum)); - if (!HeapTupleIsValid(tp)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute %d of relation %u", - attnum, funcrelid); - att_tup = (Form_pg_attribute) GETSTRUCT(tp); - result = att_tup->attisdropped; - ReleaseSysCache(tp); - } - else - { - /* - * Must be a base data type, i.e. scalar - */ - result = false; + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + + if (attnum > atts_done && + attnum <= atts_done + rtfunc->funccolcount) + { + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + + functypclass = get_expr_result_type(rtfunc->funcexpr, + &funcrettype, + &tupdesc); + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Form_pg_attribute att_tup; + + Assert(tupdesc); + Assert(attnum - atts_done <= tupdesc->natts); + att_tup = tupdesc->attrs[attnum - atts_done - 1]; + return att_tup->attisdropped; + } + /* Otherwise, it can't have any dropped columns */ + return false; + } + atts_done += rtfunc->funccolcount; } + + /* If we get here, must be looking for the ordinality column */ + if (rte->funcordinality && attnum == atts_done + 1) + return false; + + /* this probably can't happen ... */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, + rte->eref->aliasname))); + result = false; /* keep compiler quiet */ } break; default: diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index 07fce8a0112..ee6802a6558 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -472,7 +472,7 @@ GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid) { Oid result; Oid typcollation = get_typcollation(typeOid); - int location = -1; + int location = coldef->location; if (coldef->collClause) { diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 19d19e5f396..ae2206a1233 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -754,6 +754,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla def->collClause = NULL; def->collOid = attribute->attcollation; def->constraints = NIL; + def->location = -1; /* * Add to column list @@ -969,6 +970,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) n->collClause = NULL; n->collOid = attr->attcollation; n->constraints = NIL; + n->location = -1; cxt->columns = lappend(cxt->columns, n); } DecrTupleDescRefCount(tupdesc); |