/*------------------------------------------------------------------------- * * parse_target.c * handle target lists * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/parser/parse_target.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/namespace.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parse_type.h" #include "parser/parsetree.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle, Var *var, int levelsup); static Node *transformAssignmentSubscripts(ParseState *pstate, Node *basenode, const char *targetName, Oid targetTypeId, int32 targetTypMod, Oid targetCollation, List *subscripts, List *indirection, ListCell *next_indirection, Node *rhs, CoercionContext ccontext, int location); static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, bool make_target_entry); static List *ExpandAllTables(ParseState *pstate, int location); static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, bool make_target_entry, ParseExprKind exprKind); static List *ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem, int sublevels_up, int location, bool make_target_entry); static List *ExpandRowReference(ParseState *pstate, Node *expr, bool make_target_entry); static int FigureColnameInternal(Node *node, char **name); /* * transformTargetEntry() * Transform any ordinary "expression-type" node into a targetlist entry. * This is exported so that parse_clause.c can generate targetlist entries * for ORDER/GROUP BY items that are not already in the targetlist. * * node the (untransformed) parse tree for the value expression. * expr the transformed expression, or NULL if caller didn't do it yet. * exprKind expression kind (EXPR_KIND_SELECT_TARGET, etc) * colname the column name to be assigned, or NULL if none yet set. * resjunk true if the target should be marked resjunk, ie, it is not * wanted in the final projected tuple. */ TargetEntry * transformTargetEntry(ParseState *pstate, Node *node, Node *expr, ParseExprKind exprKind, char *colname, bool resjunk) { /* Transform the node if caller didn't do it already */ if (expr == NULL) { /* * If it's a SetToDefault node and we should allow that, pass it * through unmodified. (transformExpr will throw the appropriate * error if we're disallowing it.) */ if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault)) expr = node; else expr = transformExpr(pstate, node, exprKind); } if (colname == NULL && !resjunk) { /* * Generate a suitable column name for a column without any explicit * 'AS ColumnName' clause. */ colname = FigureColname(node); } return makeTargetEntry((Expr *) expr, (AttrNumber) pstate->p_next_resno++, colname, resjunk); } /* * transformTargetList() * Turns a list of ResTarget's into a list of TargetEntry's. * * This code acts mostly the same for SELECT, UPDATE, or RETURNING lists; * the main thing is to transform the given expressions (the "val" fields). * The exprKind parameter distinguishes these cases when necessary. */ List * transformTargetList(ParseState *pstate, List *targetlist, ParseExprKind exprKind) { List *p_target = NIL; bool expand_star; ListCell *o_target; /* Shouldn't have any leftover multiassign items at start */ Assert(pstate->p_multiassign_exprs == NIL); /* Expand "something.*" in SELECT and RETURNING, but not UPDATE */ expand_star = (exprKind != EXPR_KIND_UPDATE_SOURCE); foreach(o_target, targetlist) { ResTarget *res = (ResTarget *) lfirst(o_target); /* * Check for "something.*". Depending on the complexity of the * "something", the star could appear as the last field in ColumnRef, * or as the last indirection item in A_Indirection. */ if (expand_star) { if (IsA(res->val, ColumnRef)) { ColumnRef *cref = (ColumnRef *) res->val; if (IsA(llast(cref->fields), A_Star)) { /* It is something.*, expand into multiple items */ p_target = list_concat(p_target, ExpandColumnRefStar(pstate, cref, true)); continue; } } else if (IsA(res->val, A_Indirection)) { A_Indirection *ind = (A_Indirection *) res->val; if (IsA(llast(ind->indirection), A_Star)) { /* It is something.*, expand into multiple items */ p_target = list_concat(p_target, ExpandIndirectionStar(pstate, ind, true, exprKind)); continue; } } } /* * Not "something.*", or we want to treat that as a plain whole-row * variable, so transform as a single expression */ p_target = lappend(p_target, transformTargetEntry(pstate, res->val, NULL, exprKind, res->name, false)); } /* * If any multiassign resjunk items were created, attach them to the end * of the targetlist. This should only happen in an UPDATE tlist. We * don't need to worry about numbering of these items; transformUpdateStmt * will set their resnos. */ if (pstate->p_multiassign_exprs) { Assert(exprKind == EXPR_KIND_UPDATE_SOURCE); p_target = list_concat(p_target, pstate->p_multiassign_exprs); pstate->p_multiassign_exprs = NIL; } return p_target; } /* * transformExpressionList() * * This is the identical transformation to transformTargetList, except that * the input list elements are bare expressions without ResTarget decoration, * and the output elements are likewise just expressions without TargetEntry * decoration. Also, we don't expect any multiassign constructs within the * list, so there's nothing to do for that. We use this for ROW() and * VALUES() constructs. * * exprKind is not enough to tell us whether to allow SetToDefault, so * an additional flag is needed for that. */ List * transformExpressionList(ParseState *pstate, List *exprlist, ParseExprKind exprKind, bool allowDefault) { List *result = NIL; ListCell *lc; foreach(lc, exprlist) { Node *e = (Node *) lfirst(lc); /* * Check for "something.*". Depending on the complexity of the * "something", the star could appear as the last field in ColumnRef, * or as the last indirection item in A_Indirection. */ if (IsA(e, ColumnRef)) { ColumnRef *cref = (ColumnRef *) e; if (IsA(llast(cref->fields), A_Star)) { /* It is something.*, expand into multiple items */ result = list_concat(result, ExpandColumnRefStar(pstate, cref, false)); continue; } } else if (IsA(e, A_Indirection)) { A_Indirection *ind = (A_Indirection *) e; if (IsA(llast(ind->indirection), A_Star)) { /* It is something.*, expand into multiple items */ result = list_concat(result, ExpandIndirectionStar(pstate, ind, false, exprKind)); continue; } } /* * Not "something.*", so transform as a single expression. If it's a * SetToDefault node and we should allow that, pass it through * unmodified. (transformExpr will throw the appropriate error if * we're disallowing it.) */ if (allowDefault && IsA(e, SetToDefault)) /* do nothing */ ; else e = transformExpr(pstate, e, exprKind); result = lappend(result, e); } return result; } /* * resolveTargetListUnknowns() * Convert any unknown-type targetlist entries to type TEXT. * * We do this after we've exhausted all other ways of identifying the output * column types of a query. */ void resolveTargetListUnknowns(ParseState *pstate, List *targetlist) { ListCell *l; foreach(l, targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); Oid restype = exprType((Node *) tle->expr); if (restype == UNKNOWNOID) { tle->expr = (Expr *) coerce_type(pstate, (Node *) tle->expr, restype, TEXTOID, -1, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, -1); } } } /* * markTargetListOrigins() * Mark targetlist columns that are simple Vars with the source * table's OID and column number. * * Currently, this is done only for SELECT targetlists and RETURNING lists, * since we only need the info if we are going to send it to the frontend. */ void markTargetListOrigins(ParseState *pstate, List *targetlist) { ListCell *l; foreach(l, targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); markTargetListOrigin(pstate, tle, (Var *) tle->expr, 0); } } /* * markTargetListOrigin() * If 'var' is a Var of a plain relation, mark 'tle' with its origin * * levelsup is an extra offset to interpret the Var's varlevelsup correctly. * * Note that we do not drill down into views, but report the view as the * column owner. There's also no need to drill down into joins: if we see * a join alias Var, it must be a merged JOIN USING column (or possibly a * whole-row Var); that is not a direct reference to any plain table column, * so we don't report it. */ static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle, Var *var, int levelsup) { int netlevelsup; RangeTblEntry *rte; AttrNumber attnum; if (var == NULL || !IsA(var, Var)) return; netlevelsup = var->varlevelsup + levelsup; rte = GetRTEByRangeTablePosn(pstate, var->varno, netlevelsup); attnum = var->varattno; switch (rte->rtekind) { case RTE_RELATION: /* It's a table or view, report it */ tle->resorigtbl = rte->relid; tle->resorigcol = attnum; break; case RTE_SUBQUERY: /* Subselect-in-FROM: copy up from the subselect */ if (attnum != InvalidAttrNumber) { TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList, attnum); if (ste == NULL || ste->resjunk) elog(ERROR, "subquery %s does not have attribute %d", rte->eref->aliasname, attnum); tle->resorigtbl = ste->resorigtbl; tle->resorigcol = ste->resorigcol; } break; case RTE_JOIN: case RTE_FUNCTION: case RTE_VALUES: case RTE_TABLEFUNC: case RTE_NAMEDTUPLESTORE: case RTE_RESULT: /* not a simple relation, leave it unmarked */ break; case RTE_CTE: /* * CTE reference: copy up from the subquery, if possible. If the * RTE is a recursive self-reference then we can't do anything * because we haven't finished analyzing it yet. However, it's no * big loss because we must be down inside the recursive term of a * recursive CTE, and so any markings on the current targetlist * are not going to affect the results anyway. */ if (attnum != InvalidAttrNumber && !rte->self_reference) { CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup); TargetEntry *ste; List *tl = GetCTETargetList(cte); int extra_cols = 0; /* * RTE for CTE will already have the search and cycle columns * added, but the subquery won't, so skip looking those up. */ if (cte->search_clause) extra_cols += 1; if (cte->cycle_clause) extra_cols += 2; if (extra_cols && attnum > list_length(tl) && attnum <= list_length(tl) + extra_cols) break; ste = get_tle_by_resno(tl, attnum); if (ste == NULL || ste->resjunk) elog(ERROR, "CTE %s does not have attribute %d", rte->eref->aliasname, attnum); tle->resorigtbl = ste->resorigtbl; tle->resorigcol = ste->resorigcol; } break; } } /* * transformAssignedExpr() * This is used in INSERT and UPDATE statements only. It prepares an * expression for assignment to a column of the target table. * This includes coercing the given value to the target column's type * (if necessary), and dealing with any subfield names or subscripts * attached to the target column itself. The input expression has * already been through transformExpr(). * * pstate parse state * expr expression to be modified * exprKind indicates which type of statement we're dealing with * colname target column name (ie, name of attribute to be assigned to) * attrno target attribute number * indirection subscripts/field names for target column, if any * location error cursor position for the target column, or -1 * * Returns the modified expression. * * Note: location points at the target column name (SET target or INSERT * column name list entry), and must therefore be -1 in an INSERT that * omits the column name list. So we should usually prefer to use * exprLocation(expr) for errors that can happen in a default INSERT. */ Expr * transformAssignedExpr(ParseState *pstate, Expr *expr, ParseExprKind exprKind, const char *colname, int attrno, List *indirection, int location) { Relation rd = pstate->p_target_relation; Oid type_id; /* type of value provided */ Oid attrtype; /* type of target column */ int32 attrtypmod; Oid attrcollation; /* collation of target column */ ParseExprKind sv_expr_kind; /* * Save and restore identity of expression type we're parsing. We must * set p_expr_kind here because we can parse subscripts without going * through transformExpr(). */ Assert(exprKind != EXPR_KIND_NONE); sv_expr_kind = pstate->p_expr_kind; pstate->p_expr_kind = exprKind; Assert(rd != NULL); if (attrno <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot assign to system column \"%s\"", colname), parser_errposition(pstate, location))); attrtype = attnumTypeId(rd, attrno); attrtypmod = TupleDescAttr(rd->rd_att, attrno - 1)->atttypmod; attrcollation = TupleDescAttr(rd->rd_att, attrno - 1)->attcollation; /* * If the expression is a DEFAULT placeholder, insert the attribute's * type/typmod/collation into it so that exprType etc will report the * right things. (We expect that the eventually substituted default * expression will in fact have this type and typmod. The collation * likely doesn't matter, but let's set it correctly anyway.) Also, * reject trying to update a subfield or array element with DEFAULT, since * there can't be any default for portions of a column. */ if (expr && IsA(expr, SetToDefault)) { SetToDefault *def = (SetToDefault *) expr; def->typeId = attrtype; def->typeMod = attrtypmod; def->collation = attrcollation; if (indirection) { if (IsA(linitial(indirection), A_Indices)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set an array element to DEFAULT"), parser_errposition(pstate, location))); else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set a subfield to DEFAULT"), parser_errposition(pstate, location))); } } /* Now we can use exprType() safely. */ type_id = exprType((Node *) expr); /* * If there is indirection on the target column, prepare an array or * subfield assignment expression. This will generate a new column value * that the source value has been inserted into, which can then be placed * in the new tuple constructed by INSERT or UPDATE. */ if (indirection) { Node *colVar; if (pstate->p_is_insert) { /* * The command is INSERT INTO table (col.something) ... so there * is not really a source value to work with. Insert a NULL * constant as the source value. */ colVar = (Node *) makeNullConst(attrtype, attrtypmod, attrcollation); } else { /* * Build a Var for the column to be updated. */ Var *var; var = makeVar(pstate->p_target_nsitem->p_rtindex, attrno, attrtype, attrtypmod, attrcollation, 0); var->location = location; colVar = (Node *) var; } expr = (Expr *) transformAssignmentIndirection(pstate, colVar, colname, false, attrtype, attrtypmod, attrcollation, indirection, list_head(indirection), (Node *) expr, COERCION_ASSIGNMENT, location); } else { /* * For normal non-qualified target column, do type checking and * coercion. */ Node *orig_expr = (Node *) expr; expr = (Expr *) coerce_to_target_type(pstate, orig_expr, type_id, attrtype, attrtypmod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST, -1); if (expr == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" is of type %s" " but expression is of type %s", colname, format_type_be(attrtype), format_type_be(type_id)), errhint("You will need to rewrite or cast the expression."), parser_errposition(pstate, exprLocation(orig_expr)))); } pstate->p_expr_kind = sv_expr_kind; return expr; } /* * updateTargetListEntry() * This is used in UPDATE statements (and ON CONFLICT DO UPDATE) * only. It prepares an UPDATE TargetEntry for assignment to a * column of the target table. This includes coercing the given * value to the target column's type (if necessary), and dealing with * any subfield names or subscripts attached to the target column * itself. * * pstate parse state * tle target list entry to be modified * colname target column name (ie, name of attribute to be assigned to) * attrno target attribute number * indirection subscripts/field names for target column, if any * location error cursor position (should point at column name), or -1 */ void updateTargetListEntry(ParseState *pstate, TargetEntry *tle, char *colname, int attrno, List *indirection, int location) { /* Fix up expression as needed */ tle->expr = transformAssignedExpr(pstate, tle->expr, EXPR_KIND_UPDATE_TARGET, colname, attrno, indirection, location); /* * Set the resno to identify the target column --- the rewriter and * planner depend on this. We also set the resname to identify the target * column, but this is only for debugging purposes; it should not be * relied on. (In particular, it might be out of date in a stored rule.) */ tle->resno = (AttrNumber) attrno; tle->resname = colname; } /* * Process indirection (field selection or subscripting) of the target * column in INSERT/UPDATE/assignment. This routine recurses for multiple * levels of indirection --- but note that several adjacent A_Indices nodes * in the indirection list are treated as a single multidimensional subscript * operation. * * In the initial call, basenode is a Var for the target column in UPDATE, * or a null Const of the target's type in INSERT, or a Param for the target * variable in PL/pgSQL assignment. In recursive calls, basenode is NULL, * indicating that a substitute node should be consed up if needed. * * targetName is the name of the field or subfield we're assigning to, and * targetIsSubscripting is true if we're subscripting it. These are just for * error reporting. * * targetTypeId, targetTypMod, targetCollation indicate the datatype and * collation of the object to be assigned to (initially the target column, * later some subobject). * * indirection is the list of indirection nodes, and indirection_cell is the * start of the sublist remaining to process. When it's NULL, we're done * recursing and can just coerce and return the RHS. * * rhs is the already-transformed value to be assigned; note it has not been * coerced to any particular type. * * ccontext is the coercion level to use while coercing the rhs. For * normal statements it'll be COERCION_ASSIGNMENT, but PL/pgSQL uses * a special value. * * location is the cursor error position for any errors. (Note: this points * to the head of the target clause, eg "foo" in "foo.bar[baz]". Later we * might want to decorate indirection cells with their own location info, * in which case the location argument could probably be dropped.) */ Node * transformAssignmentIndirection(ParseState *pstate, Node *basenode, const char *targetName, bool targetIsSubscripting, Oid targetTypeId, int32 targetTypMod, Oid targetCollation, List *indirection, ListCell *indirection_cell, Node *rhs, CoercionContext ccontext, int location) { Node *result; List *subscripts = NIL; ListCell *i; if (indirection_cell && !basenode) { /* * Set up a substitution. We abuse CaseTestExpr for this. It's safe * to do so because the only nodes that will be above the CaseTestExpr * in the finished expression will be FieldStore and SubscriptingRef * nodes. (There could be other stuff in the tree, but it will be * within other child fields of those node types.) */ CaseTestExpr *ctest = makeNode(CaseTestExpr); ctest->typeId = targetTypeId; ctest->typeMod = targetTypMod; ctest->collation = targetCollation; basenode = (Node *) ctest; } /* * We have to split any field-selection operations apart from * subscripting. Adjacent A_Indices nodes have to be treated as a single * multidimensional subscript operation. */ for_each_cell(i, indirection, indirection_cell) { Node *n = lfirst(i); if (IsA(n, A_Indices)) subscripts = lappend(subscripts, n); else if (IsA(n, A_Star)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row expansion via \"*\" is not supported here"), parser_errposition(pstate, location))); } else { FieldStore *fstore; Oid baseTypeId; int32 baseTypeMod; Oid typrelid; AttrNumber attnum; Oid fieldTypeId; int32 fieldTypMod; Oid fieldCollation; Assert(IsA(n, String)); /* process subscripts before this field selection */ if (subscripts) { /* recurse, and then return because we're done */ return transformAssignmentSubscripts(pstate, basenode, targetName, targetTypeId, targetTypMod, targetCollation, subscripts, indirection, i, rhs, ccontext, location); } /* No subscripts, so can process field selection here */ /* * Look up the composite type, accounting for possibility that * what we are given is a domain over composite. */ baseTypeMod = targetTypMod; baseTypeId = getBaseTypeAndTypmod(targetTypeId, &baseTypeMod); typrelid = typeidTypeRelid(baseTypeId); if (!typrelid) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot assign to field \"%s\" of column \"%s\" because its type %s is not a composite type", strVal(n), targetName, format_type_be(targetTypeId)), parser_errposition(pstate, location))); attnum = get_attnum(typrelid, strVal(n)); if (attnum == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("cannot assign to field \"%s\" of column \"%s\" because there is no such column in data type %s", strVal(n), targetName, format_type_be(targetTypeId)), parser_errposition(pstate, location))); if (attnum < 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("cannot assign to system column \"%s\"", strVal(n)), parser_errposition(pstate, location))); get_atttypetypmodcoll(typrelid, attnum, &fieldTypeId, &fieldTypMod, &fieldCollation); /* recurse to create appropriate RHS for field assign */ rhs = transformAssignmentIndirection(pstate, NULL, strVal(n), false, fieldTypeId, fieldTypMod, fieldCollation, indirection, lnext(indirection, i), rhs, ccontext, location); /* and build a FieldStore node */ fstore = makeNode(FieldStore); fstore->arg = (Expr *) basenode; fstore->newvals = list_make1(rhs); fstore->fieldnums = list_make1_int(attnum); fstore->resulttype = baseTypeId; /* * If target is a domain, apply constraints. Notice that this * isn't totally right: the expression tree we build would check * the domain's constraints on a composite value with only this * one field populated or updated, possibly leading to an unwanted * failure. The rewriter will merge together any subfield * assignments to the same table column, resulting in the domain's * constraints being checked only once after we've assigned to all * the fields that the INSERT or UPDATE means to. */ if (baseTypeId != targetTypeId) return coerce_to_domain((Node *) fstore, baseTypeId, baseTypeMod, targetTypeId, COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, location, false); return (Node *) fstore; } } /* process trailing subscripts, if any */ if (subscripts) { /* recurse, and then return because we're done */ return transformAssignmentSubscripts(pstate, basenode, targetName, targetTypeId, targetTypMod, targetCollation, subscripts, indirection, NULL, rhs, ccontext, location); } /* base case: just coerce RHS to match target type ID */ result = coerce_to_target_type(pstate, rhs, exprType(rhs), targetTypeId, targetTypMod, ccontext, COERCE_IMPLICIT_CAST, -1); if (result == NULL) { if (targetIsSubscripting) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subscripted assignment to \"%s\" requires type %s" " but expression is of type %s", targetName, format_type_be(targetTypeId), format_type_be(exprType(rhs))), errhint("You will need to rewrite or cast the expression."), parser_errposition(pstate, location))); else ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("subfield \"%s\" is of type %s" " but expression is of type %s", targetName, format_type_be(targetTypeId), format_type_be(exprType(rhs))), errhint("You will need to rewrite or cast the expression."), parser_errposition(pstate, location))); } return result; } /* * helper for transformAssignmentIndirection: process container assignment */ static Node * transformAssignmentSubscripts(ParseState *pstate, Node *basenode, const char *targetName, Oid targetTypeId, int32 targetTypMod, Oid targetCollation, List *subscripts, List *indirection, ListCell *next_indirection, Node *rhs, CoercionContext ccontext, int location) { Node *result; SubscriptingRef *sbsref; Oid containerType; int32 containerTypMod; Oid typeNeeded; int32 typmodNeeded; Oid collationNeeded; Assert(subscripts != NIL); /* Identify the actual container type involved */ containerType = targetTypeId; containerTypMod = targetTypMod; transformContainerType(&containerType, &containerTypMod); /* Process subscripts and identify required type for RHS */ sbsref = transformContainerSubscripts(pstate, basenode, containerType, containerTypMod, subscripts, true); typeNeeded = sbsref->refrestype; typmodNeeded = sbsref->reftypmod; /* * Container normally has same collation as its elements, but there's an * exception: we might be subscripting a domain over a container type. In * that case use collation of the base type. (This is shaky for arbitrary * subscripting semantics, but it doesn't matter all that much since we * only use this to label the collation of a possible CaseTestExpr.) */ if (containerType == targetTypeId) collationNeeded = targetCollation; else collationNeeded = get_typcollation(containerType); /* recurse to create appropriate RHS for container assign */ rhs = transformAssignmentIndirection(pstate, NULL, targetName, true, typeNeeded, typmodNeeded, collationNeeded, indirection, next_indirection, rhs, ccontext, location); /* * Insert the already-properly-coerced RHS into the SubscriptingRef. Then * set refrestype and reftypmod back to the container type's values. */ sbsref->refassgnexpr = (Expr *) rhs; sbsref->refrestype = containerType; sbsref->reftypmod = containerTypMod; result = (Node *) sbsref; /* * If target was a domain over container, need to coerce up to the domain. * As in transformAssignmentIndirection, this coercion is premature if the * query assigns to multiple elements of the container; but we'll fix that * during query rewrite. */ if (containerType != targetTypeId) { Oid resulttype = exprType(result); result = coerce_to_target_type(pstate, result, resulttype, targetTypeId, targetTypMod, ccontext, COERCE_IMPLICIT_CAST, -1); /* can fail if we had int2vector/oidvector, but not for true domains */ if (result == NULL) ereport(ERROR, (errcode(ERRCODE_CANNOT_COERCE), errmsg("cannot cast type %s to %s", format_type_be(resulttype), format_type_be(targetTypeId)), parser_errposition(pstate, location))); } return result; } /* * checkInsertTargets - * generate a list of INSERT column targets if not supplied, or * test supplied column names to make sure they are in target table. * Also return an integer list of the columns' attribute numbers. */ List * checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) { *attrnos = NIL; if (cols == NIL) { /* * Generate default column list for INSERT. */ int numcol = RelationGetNumberOfAttributes(pstate->p_target_relation); int i; for (i = 0; i < numcol; i++) { ResTarget *col; Form_pg_attribute attr; attr = TupleDescAttr(pstate->p_target_relation->rd_att, i); if (attr->attisdropped) continue; col = makeNode(ResTarget); col->name = pstrdup(NameStr(attr->attname)); col->indirection = NIL; col->val = NULL; col->location = -1; cols = lappend(cols, col); *attrnos = lappend_int(*attrnos, i + 1); } } else { /* * Do initial validation of user-supplied INSERT column list. */ Bitmapset *wholecols = NULL; Bitmapset *partialcols = NULL; ListCell *tl; foreach(tl, cols) { ResTarget *col = (ResTarget *) lfirst(tl); char *name = col->name; int attrno; /* Lookup column name, ereport on failure */ attrno = attnameAttNum(pstate->p_target_relation, name, false); if (attrno == InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", name, RelationGetRelationName(pstate->p_target_relation)), parser_errposition(pstate, col->location))); /* * Check for duplicates, but only of whole columns --- we allow * INSERT INTO foo (col.subcol1, col.subcol2) */ if (col->indirection == NIL) { /* whole column; must not have any other assignment */ if (bms_is_member(attrno, wholecols) || bms_is_member(attrno, partialcols)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("column \"%s\" specified more than once", name), parser_errposition(pstate, col->location))); wholecols = bms_add_member(wholecols, attrno); } else { /* partial column; must not have any whole assignment */ if (bms_is_member(attrno, wholecols)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("column \"%s\" specified more than once", name), parser_errposition(pstate, col->location))); partialcols = bms_add_member(partialcols, attrno); } *attrnos = lappend_int(*attrnos, attrno); } } return cols; } /* * ExpandColumnRefStar() * Transforms foo.* into a list of expressions or targetlist entries. * * This handles the case where '*' appears as the last or only item in a * ColumnRef. The code is shared between the case of foo.* at the top level * in a SELECT target list (where we want TargetEntry nodes in the result) * and foo.* in a ROW() or VALUES() construct (where we want just bare * expressions). * * The referenced columns are marked as requiring SELECT access. */ static List * ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, bool make_target_entry) { List *fields = cref->fields; int numnames = list_length(fields); if (numnames == 1) { /* * Target item is a bare '*', expand all tables * * (e.g., SELECT * FROM emp, dept) * * Since the grammar only accepts bare '*' at top level of SELECT, we * need not handle the make_target_entry==false case here. */ Assert(make_target_entry); return ExpandAllTables(pstate, cref->location); } else { /* * Target item is relation.*, expand that table * * (e.g., SELECT emp.*, dname FROM emp, dept) * * Note: this code is a lot like transformColumnRef; it's tempting to * call that instead and then replace the resulting whole-row Var with * a list of Vars. However, that would leave us with the relation's * selectedCols bitmap showing the whole row as needing select * permission, as well as the individual columns. That would be * incorrect (since columns added later shouldn't need select * permissions). We could try to remove the whole-row permission bit * after the fact, but duplicating code is less messy. */ char *nspname = NULL; char *relname = NULL; ParseNamespaceItem *nsitem = NULL; int levels_up; enum { CRSERR_NO_RTE, CRSERR_WRONG_DB, CRSERR_TOO_MANY } crserr = CRSERR_NO_RTE; /* * Give the PreParseColumnRefHook, if any, first shot. If it returns * non-null then we should use that expression. */ if (pstate->p_pre_columnref_hook != NULL) { Node *node; node = pstate->p_pre_columnref_hook(pstate, cref); if (node != NULL) return ExpandRowReference(pstate, node, make_target_entry); } switch (numnames) { case 2: relname = strVal(linitial(fields)); nsitem = refnameNamespaceItem(pstate, nspname, relname, cref->location, &levels_up); break; case 3: nspname = strVal(linitial(fields)); relname = strVal(lsecond(fields)); nsitem = refnameNamespaceItem(pstate, nspname, relname, cref->location, &levels_up); break; case 4: { char *catname = strVal(linitial(fields)); /* * We check the catalog name and then ignore it. */ if (strcmp(catname, get_database_name(MyDatabaseId)) != 0) { crserr = CRSERR_WRONG_DB; break; } nspname = strVal(lsecond(fields)); relname = strVal(lthird(fields)); nsitem = refnameNamespaceItem(pstate, nspname, relname, cref->location, &levels_up); break; } default: crserr = CRSERR_TOO_MANY; break; } /* * Now give the PostParseColumnRefHook, if any, a chance. We cheat a * bit by passing the RangeTblEntry, not a Var, as the planned * translation. (A single Var wouldn't be strictly correct anyway. * This convention allows hooks that really care to know what is * happening. It might be better to pass the nsitem, but we'd have to * promote that struct to a full-fledged Node type so that callees * could identify its type.) */ if (pstate->p_post_columnref_hook != NULL) { Node *node; node = pstate->p_post_columnref_hook(pstate, cref, (Node *) (nsitem ? nsitem->p_rte : NULL)); if (node != NULL) { if (nsitem != NULL) ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_COLUMN), errmsg("column reference \"%s\" is ambiguous", NameListToString(cref->fields)), parser_errposition(pstate, cref->location))); return ExpandRowReference(pstate, node, make_target_entry); } } /* * Throw error if no translation found. */ if (nsitem == NULL) { switch (crserr) { case CRSERR_NO_RTE: errorMissingRTE(pstate, makeRangeVar(nspname, relname, cref->location)); break; case CRSERR_WRONG_DB: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cross-database references are not implemented: %s", NameListToString(cref->fields)), parser_errposition(pstate, cref->location))); break; case CRSERR_TOO_MANY: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("improper qualified name (too many dotted names): %s", NameListToString(cref->fields)), parser_errposition(pstate, cref->location))); break; } } /* * OK, expand the nsitem into fields. */ return ExpandSingleTable(pstate, nsitem, levels_up, cref->location, make_target_entry); } } /* * ExpandAllTables() * Transforms '*' (in the target list) into a list of targetlist entries. * * tlist entries are generated for each relation visible for unqualified * column name access. We do not consider qualified-name-only entries because * that would include input tables of aliasless JOINs, NEW/OLD pseudo-entries, * etc. * * The referenced relations/columns are marked as requiring SELECT access. */ static List * ExpandAllTables(ParseState *pstate, int location) { List *target = NIL; bool found_table = false; ListCell *l; foreach(l, pstate->p_namespace) { ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l); /* Ignore table-only items */ if (!nsitem->p_cols_visible) continue; /* Should not have any lateral-only items when parsing targetlist */ Assert(!nsitem->p_lateral_only); /* Remember we found a p_cols_visible item */ found_table = true; target = list_concat(target, expandNSItemAttrs(pstate, nsitem, 0, true, location)); } /* * Check for "SELECT *;". We do it this way, rather than checking for * target == NIL, because we want to allow SELECT * FROM a zero_column * table. */ if (!found_table) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("SELECT * with no tables specified is not valid"), parser_errposition(pstate, location))); return target; } /* * ExpandIndirectionStar() * Transforms foo.* into a list of expressions or targetlist entries. * * This handles the case where '*' appears as the last item in A_Indirection. * The code is shared between the case of foo.* at the top level in a SELECT * target list (where we want TargetEntry nodes in the result) and foo.* in * a ROW() or VALUES() construct (where we want just bare expressions). * For robustness, we use a separate "make_target_entry" flag to control * this rather than relying on exprKind. */ static List * ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, bool make_target_entry, ParseExprKind exprKind) { Node *expr; /* Strip off the '*' to create a reference to the rowtype object */ ind = copyObject(ind); ind->indirection = list_truncate(ind->indirection, list_length(ind->indirection) - 1); /* And transform that */ expr = transformExpr(pstate, (Node *) ind, exprKind); /* Expand the rowtype expression into individual fields */ return ExpandRowReference(pstate, expr, make_target_entry); } /* * ExpandSingleTable() * Transforms foo.* into a list of expressions or targetlist entries. * * This handles the case where foo has been determined to be a simple * reference to an RTE, so we can just generate Vars for the expressions. * * The referenced columns are marked as requiring SELECT access. */ static List * ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem, int sublevels_up, int location, bool make_target_entry) { if (make_target_entry) { /* expandNSItemAttrs handles permissions marking */ return expandNSItemAttrs(pstate, nsitem, sublevels_up, true, location); } else { RangeTblEntry *rte = nsitem->p_rte; RTEPermissionInfo *perminfo = nsitem->p_perminfo; List *vars; ListCell *l; vars = expandNSItemVars(pstate, nsitem, sublevels_up, location, NULL); /* * Require read access to the table. This is normally redundant with * the markVarForSelectPriv calls below, but not if the table has zero * columns. We need not do anything if the nsitem is for a join: its * component tables will have been marked ACL_SELECT when they were * added to the rangetable. (This step changes things only for the * target relation of UPDATE/DELETE, which cannot be under a join.) */ if (rte->rtekind == RTE_RELATION) { Assert(perminfo != NULL); perminfo->requiredPerms |= ACL_SELECT; } /* Require read access to each column */ foreach(l, vars) { Var *var = (Var *) lfirst(l); markVarForSelectPriv(pstate, var); } return vars; } } /* * ExpandRowReference() * Transforms foo.* into a list of expressions or targetlist entries. * * This handles the case where foo is an arbitrary expression of composite * type. */ static List * ExpandRowReference(ParseState *pstate, Node *expr, bool make_target_entry) { List *result = NIL; TupleDesc tupleDesc; int numAttrs; int i; /* * If the rowtype expression is a whole-row Var, we can expand the fields * as simple Vars. Note: if the RTE is a relation, this case leaves us * with its RTEPermissionInfo's selectedCols bitmap showing the whole row * as needing select permission, as well as the individual columns. * However, we can only get here for weird notations like (table.*).*, so * it's not worth trying to clean up --- arguably, the permissions marking * is correct anyway for such cases. */ if (IsA(expr, Var) && ((Var *) expr)->varattno == InvalidAttrNumber) { Var *var = (Var *) expr; ParseNamespaceItem *nsitem; nsitem = GetNSItemByRangeTablePosn(pstate, var->varno, var->varlevelsup); return ExpandSingleTable(pstate, nsitem, var->varlevelsup, var->location, make_target_entry); } /* * Otherwise we have to do it the hard way. Our current implementation is * to generate multiple copies of the expression and do FieldSelects. * (This can be pretty inefficient if the expression involves nontrivial * computation :-(.) * * Verify it's a composite type, and get the tupdesc. * get_expr_result_tupdesc() handles this conveniently. * * If it's a Var of type RECORD, we have to work even harder: we have to * find what the Var refers to, and pass that to get_expr_result_tupdesc. * That task is handled by expandRecordVariable(). */ if (IsA(expr, Var) && ((Var *) expr)->vartype == RECORDOID) tupleDesc = expandRecordVariable(pstate, (Var *) expr, 0); else tupleDesc = get_expr_result_tupdesc(expr, false); Assert(tupleDesc); /* Generate a list of references to the individual fields */ numAttrs = tupleDesc->natts; for (i = 0; i < numAttrs; i++) { Form_pg_attribute att = TupleDescAttr(tupleDesc, i); FieldSelect *fselect; if (att->attisdropped) continue; fselect = makeNode(FieldSelect); fselect->arg = (Expr *) copyObject(expr); fselect->fieldnum = i + 1; fselect->resulttype = att->atttypid; fselect->resulttypmod = att->atttypmod; /* save attribute's collation for parse_collate.c */ fselect->resultcollid = att->attcollation; if (make_target_entry) { /* add TargetEntry decoration */ TargetEntry *te; te = makeTargetEntry((Expr *) fselect, (AttrNumber) pstate->p_next_resno++, pstrdup(NameStr(att->attname)), false); result = lappend(result, te); } else result = lappend(result, fselect); } return result; } /* * expandRecordVariable * Get the tuple descriptor for a Var of type RECORD, if possible. * * Since no actual table or view column is allowed to have type RECORD, such * a Var must refer to a JOIN or FUNCTION RTE or to a subquery output. We * drill down to find the ultimate defining expression and attempt to infer * the tupdesc from it. We ereport if we can't determine the tupdesc. * * levelsup is an extra offset to interpret the Var's varlevelsup correctly * when recursing. Outside callers should pass zero. */ TupleDesc expandRecordVariable(ParseState *pstate, Var *var, int levelsup) { TupleDesc tupleDesc; int netlevelsup; RangeTblEntry *rte; AttrNumber attnum; Node *expr; /* Check my caller didn't mess up */ Assert(IsA(var, Var)); Assert(var->vartype == RECORDOID); /* * Note: it's tempting to use GetNSItemByRangeTablePosn here so that we * can use expandNSItemVars instead of expandRTE; but that does not work * for some of the recursion cases below, where we have consed up a * ParseState that lacks p_namespace data. */ netlevelsup = var->varlevelsup + levelsup; rte = GetRTEByRangeTablePosn(pstate, var->varno, netlevelsup); attnum = var->varattno; if (attnum == InvalidAttrNumber) { /* Whole-row reference to an RTE, so expand the known fields */ List *names, *vars; ListCell *lname, *lvar; int i; expandRTE(rte, var->varno, 0, var->location, false, &names, &vars); tupleDesc = CreateTemplateTupleDesc(list_length(vars)); i = 1; forboth(lname, names, lvar, vars) { char *label = strVal(lfirst(lname)); Node *varnode = (Node *) lfirst(lvar); TupleDescInitEntry(tupleDesc, i, label, exprType(varnode), exprTypmod(varnode), 0); TupleDescInitEntryCollation(tupleDesc, i, exprCollation(varnode)); i++; } Assert(lname == NULL && lvar == NULL); /* lists same length? */ return tupleDesc; } expr = (Node *) var; /* default if we can't drill down */ switch (rte->rtekind) { case RTE_RELATION: case RTE_VALUES: case RTE_NAMEDTUPLESTORE: case RTE_RESULT: /* * This case should not occur: a column of a table, values list, * or ENR shouldn't have type RECORD. Fall through and fail (most * likely) at the bottom. */ break; case RTE_SUBQUERY: { /* Subselect-in-FROM: examine sub-select's output expr */ TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList, attnum); if (ste == NULL || ste->resjunk) elog(ERROR, "subquery %s does not have attribute %d", rte->eref->aliasname, attnum); expr = (Node *) ste->expr; if (IsA(expr, Var)) { /* * Recurse into the sub-select to see what its Var refers * to. We have to build an additional level of ParseState * to keep in step with varlevelsup in the subselect; * furthermore, the subquery RTE might be from an outer * query level, in which case the ParseState for the * subselect must have that outer level as parent. */ ParseState mypstate = {0}; Index levelsup; /* this loop must work, since GetRTEByRangeTablePosn did */ for (levelsup = 0; levelsup < netlevelsup; levelsup++) pstate = pstate->parentParseState; mypstate.parentParseState = pstate; mypstate.p_rtable = rte->subquery->rtable; /* don't bother filling the rest of the fake pstate */ return expandRecordVariable(&mypstate, (Var *) expr, 0); } /* else fall through to inspect the expression */ } break; case RTE_JOIN: /* Join RTE --- recursively inspect the alias variable */ Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars)); expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1); Assert(expr != NULL); /* We intentionally don't strip implicit coercions here */ if (IsA(expr, Var)) return expandRecordVariable(pstate, (Var *) expr, netlevelsup); /* else fall through to inspect the expression */ break; case RTE_FUNCTION: /* * We couldn't get here unless a function is declared with one of * its result columns as RECORD, which is not allowed. */ break; case RTE_TABLEFUNC: /* * Table function cannot have columns with RECORD type. */ break; case RTE_CTE: /* CTE reference: examine subquery's output expr */ if (!rte->self_reference) { CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup); TargetEntry *ste; ste = get_tle_by_resno(GetCTETargetList(cte), attnum); if (ste == NULL || ste->resjunk) elog(ERROR, "CTE %s does not have attribute %d", rte->eref->aliasname, attnum); expr = (Node *) ste->expr; if (IsA(expr, Var)) { /* * Recurse into the CTE to see what its Var refers to. We * have to build an additional level of ParseState to keep * in step with varlevelsup in the CTE; furthermore it * could be an outer CTE (compare SUBQUERY case above). */ ParseState mypstate = {0}; Index levelsup; /* this loop must work, since GetCTEForRTE did */ for (levelsup = 0; levelsup < rte->ctelevelsup + netlevelsup; levelsup++) pstate = pstate->parentParseState; mypstate.parentParseState = pstate; mypstate.p_rtable = ((Query *) cte->ctequery)->rtable; /* don't bother filling the rest of the fake pstate */ return expandRecordVariable(&mypstate, (Var *) expr, 0); } /* else fall through to inspect the expression */ } break; } /* * We now have an expression we can't expand any more, so see if * get_expr_result_tupdesc() can do anything with it. */ return get_expr_result_tupdesc(expr, false); } /* * FigureColname - * if the name of the resulting column is not specified in the target * list, we have to guess a suitable name. The SQL spec provides some * guidance, but not much... * * Note that the argument is the *untransformed* parse tree for the target * item. This is a shade easier to work with than the transformed tree. */ char * FigureColname(Node *node) { char *name = NULL; (void) FigureColnameInternal(node, &name); if (name != NULL) return name; /* default result if we can't guess anything */ return "?column?"; } /* * FigureIndexColname - * choose the name for an expression column in an index * * This is actually just like FigureColname, except we return NULL if * we can't pick a good name. */ char * FigureIndexColname(Node *node) { char *name = NULL; (void) FigureColnameInternal(node, &name); return name; } /* * FigureColnameInternal - * internal workhorse for FigureColname * * Return value indicates strength of confidence in result: * 0 - no information * 1 - second-best name choice * 2 - good name choice * The return value is actually only used internally. * If the result isn't zero, *name is set to the chosen name. */ static int FigureColnameInternal(Node *node, char **name) { int strength = 0; if (node == NULL) return strength; switch (nodeTag(node)) { case T_ColumnRef: { char *fname = NULL; ListCell *l; /* find last field name, if any, ignoring "*" */ foreach(l, ((ColumnRef *) node)->fields) { Node *i = lfirst(l); if (IsA(i, String)) fname = strVal(i); } if (fname) { *name = fname; return 2; } } break; case T_A_Indirection: { A_Indirection *ind = (A_Indirection *) node; char *fname = NULL; ListCell *l; /* find last field name, if any, ignoring "*" and subscripts */ foreach(l, ind->indirection) { Node *i = lfirst(l); if (IsA(i, String)) fname = strVal(i); } if (fname) { *name = fname; return 2; } return FigureColnameInternal(ind->arg, name); } break; case T_FuncCall: *name = strVal(llast(((FuncCall *) node)->funcname)); return 2; case T_A_Expr: if (((A_Expr *) node)->kind == AEXPR_NULLIF) { /* make nullif() act like a regular function */ *name = "nullif"; return 2; } break; case T_TypeCast: strength = FigureColnameInternal(((TypeCast *) node)->arg, name); if (strength <= 1) { if (((TypeCast *) node)->typeName != NULL) { *name = strVal(llast(((TypeCast *) node)->typeName->names)); return 1; } } break; case T_CollateClause: return FigureColnameInternal(((CollateClause *) node)->arg, name); case T_GroupingFunc: /* make GROUPING() act like a regular function */ *name = "grouping"; return 2; case T_MergeSupportFunc: /* make MERGE_ACTION() act like a regular function */ *name = "merge_action"; return 2; case T_SubLink: switch (((SubLink *) node)->subLinkType) { case EXISTS_SUBLINK: *name = "exists"; return 2; case ARRAY_SUBLINK: *name = "array"; return 2; case EXPR_SUBLINK: { /* Get column name of the subquery's single target */ SubLink *sublink = (SubLink *) node; Query *query = (Query *) sublink->subselect; /* * The subquery has probably already been transformed, * but let's be careful and check that. (The reason * we can see a transformed subquery here is that * transformSubLink is lazy and modifies the SubLink * node in-place.) */ if (IsA(query, Query)) { TargetEntry *te = (TargetEntry *) linitial(query->targetList); if (te->resname) { *name = te->resname; return 2; } } } break; /* As with other operator-like nodes, these have no names */ case MULTIEXPR_SUBLINK: case ALL_SUBLINK: case ANY_SUBLINK: case ROWCOMPARE_SUBLINK: case CTE_SUBLINK: break; } break; case T_CaseExpr: strength = FigureColnameInternal((Node *) ((CaseExpr *) node)->defresult, name); if (strength <= 1) { *name = "case"; return 1; } break; case T_A_ArrayExpr: /* 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"; return 2; case T_MinMaxExpr: /* make greatest/least act like a regular function */ switch (((MinMaxExpr *) node)->op) { case IS_GREATEST: *name = "greatest"; return 2; case IS_LEAST: *name = "least"; return 2; } break; case T_SQLValueFunction: /* make these act like a function or variable */ switch (((SQLValueFunction *) node)->op) { case SVFOP_CURRENT_DATE: *name = "current_date"; return 2; case SVFOP_CURRENT_TIME: case SVFOP_CURRENT_TIME_N: *name = "current_time"; return 2; case SVFOP_CURRENT_TIMESTAMP: case SVFOP_CURRENT_TIMESTAMP_N: *name = "current_timestamp"; return 2; case SVFOP_LOCALTIME: case SVFOP_LOCALTIME_N: *name = "localtime"; return 2; case SVFOP_LOCALTIMESTAMP: case SVFOP_LOCALTIMESTAMP_N: *name = "localtimestamp"; return 2; case SVFOP_CURRENT_ROLE: *name = "current_role"; return 2; case SVFOP_CURRENT_USER: *name = "current_user"; return 2; case SVFOP_USER: *name = "user"; return 2; case SVFOP_SESSION_USER: *name = "session_user"; return 2; case SVFOP_CURRENT_CATALOG: *name = "current_catalog"; return 2; case SVFOP_CURRENT_SCHEMA: *name = "current_schema"; return 2; } break; case T_XmlExpr: /* make SQL/XML functions act like a regular function */ switch (((XmlExpr *) node)->op) { case IS_XMLCONCAT: *name = "xmlconcat"; return 2; case IS_XMLELEMENT: *name = "xmlelement"; return 2; case IS_XMLFOREST: *name = "xmlforest"; return 2; case IS_XMLPARSE: *name = "xmlparse"; return 2; case IS_XMLPI: *name = "xmlpi"; return 2; case IS_XMLROOT: *name = "xmlroot"; return 2; case IS_XMLSERIALIZE: *name = "xmlserialize"; return 2; case IS_DOCUMENT: /* nothing */ break; } break; case T_XmlSerialize: /* make XMLSERIALIZE act like a regular function */ *name = "xmlserialize"; return 2; case T_JsonParseExpr: /* make JSON act like a regular function */ *name = "json"; return 2; case T_JsonScalarExpr: /* make JSON_SCALAR act like a regular function */ *name = "json_scalar"; return 2; case T_JsonSerializeExpr: /* make JSON_SERIALIZE act like a regular function */ *name = "json_serialize"; return 2; case T_JsonObjectConstructor: /* make JSON_OBJECT act like a regular function */ *name = "json_object"; return 2; case T_JsonArrayConstructor: case T_JsonArrayQueryConstructor: /* make JSON_ARRAY act like a regular function */ *name = "json_array"; return 2; case T_JsonObjectAgg: /* make JSON_OBJECTAGG act like a regular function */ *name = "json_objectagg"; return 2; case T_JsonArrayAgg: /* make JSON_ARRAYAGG act like a regular function */ *name = "json_arrayagg"; return 2; case T_JsonFuncExpr: /* make SQL/JSON functions act like a regular function */ switch (((JsonFuncExpr *) node)->op) { case JSON_EXISTS_OP: *name = "json_exists"; return 2; case JSON_QUERY_OP: *name = "json_query"; return 2; case JSON_VALUE_OP: *name = "json_value"; return 2; default: elog(ERROR, "unrecognized JsonExpr op: %d", (int) ((JsonFuncExpr *) node)->op); } break; default: break; } return strength; }