From: Tom Lane Date: Tue, 31 May 2005 03:03:59 +0000 (+0000) Subject: Teach ruleutils to drill down into RECORD-type Vars in the same way X-Git-Tag: REL8_1_0BETA1~685 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6dfe64ee57d42bb48cd7c7c30bd79b3db8ec630f;p=postgresql Teach ruleutils to drill down into RECORD-type Vars in the same way that the parser now can, so that it can reverse-list cases involving FieldSelect from a RECORD Var. --- diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index d10459a9bf..be53f7373d 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.197 2005/05/30 01:57:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.198 2005/05/31 03:03:59 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -55,6 +55,7 @@ #include "catalog/pg_shadow.h" #include "catalog/pg_trigger.h" #include "executor/spi.h" +#include "funcapi.h" #include "lib/stringinfo.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -2421,30 +2422,18 @@ get_utility_query_def(Query *query, deparse_context *context) /* - * Get the schemaname, refname and attname for a (possibly nonlocal) Var. + * Get the RTE referenced by a (possibly nonlocal) Var. * * In some cases (currently only when recursing into an unnamed join) * the Var's varlevelsup has to be interpreted with respect to a context * above the current one; levelsup indicates the offset. - * - * schemaname is usually returned as NULL. It will be non-null only if - * use of the unqualified refname would find the wrong RTE. - * - * refname will be returned as NULL if the Var references an unnamed join. - * In this case the Var *must* be displayed without any qualification. - * - * attname will be returned as NULL if the Var represents a whole tuple - * of the relation. (Typically we'd want to display the Var as "foo.*", - * but it's convenient to return NULL to make it easier for callers to - * distinguish this case.) */ -static void -get_names_for_var(Var *var, int levelsup, deparse_context *context, - char **schemaname, char **refname, char **attname) +static RangeTblEntry * +get_rte_for_var(Var *var, int levelsup, deparse_context *context) { + RangeTblEntry *rte; int netlevelsup; deparse_namespace *dpns; - RangeTblEntry *rte; /* Find appropriate nesting depth */ netlevelsup = var->varlevelsup + levelsup; @@ -2465,6 +2454,36 @@ get_names_for_var(Var *var, int levelsup, deparse_context *context, rte = NULL; if (rte == NULL) elog(ERROR, "bogus varno: %d", var->varno); + return rte; +} + + +/* + * Get the schemaname, refname and attname for a (possibly nonlocal) Var. + * + * In some cases (currently only when recursing into an unnamed join) + * the Var's varlevelsup has to be interpreted with respect to a context + * above the current one; levelsup indicates the offset. + * + * schemaname is usually returned as NULL. It will be non-null only if + * use of the unqualified refname would find the wrong RTE. + * + * refname will be returned as NULL if the Var references an unnamed join. + * In this case the Var *must* be displayed without any qualification. + * + * attname will be returned as NULL if the Var represents a whole tuple + * of the relation. (Typically we'd want to display the Var as "foo.*", + * but it's convenient to return NULL to make it easier for callers to + * distinguish this case.) + */ +static void +get_names_for_var(Var *var, int levelsup, deparse_context *context, + char **schemaname, char **refname, char **attname) +{ + RangeTblEntry *rte; + + /* Find appropriate RTE */ + rte = get_rte_for_var(var, levelsup, context); /* Emit results */ *schemaname = NULL; /* default assumptions */ @@ -2505,7 +2524,8 @@ get_names_for_var(Var *var, int levelsup, deparse_context *context, var->varattno-1); if (IsA(aliasvar, Var)) { - get_names_for_var(aliasvar, netlevelsup, context, + get_names_for_var(aliasvar, + var->varlevelsup + levelsup, context, schemaname, refname, attname); return; } @@ -2521,6 +2541,127 @@ get_names_for_var(Var *var, int levelsup, deparse_context *context, *attname = get_rte_attribute_name(rte, var->varattno); } + +/* + * Get the name of a field of a Var of type RECORD. + * + * 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 field name from it. We ereport if we can't determine the name. + * + * levelsup is an extra offset to interpret the Var's varlevelsup correctly. + * + * Note: this has essentially the same logic as the parser's + * expandRecordVariable() function, but we are dealing with a different + * representation of the input context, and we only need one field name not + * a TupleDesc. + */ +static const char * +get_name_for_var_field(Var *var, int fieldno, + int levelsup, deparse_context *context) +{ + RangeTblEntry *rte; + AttrNumber attnum; + TupleDesc tupleDesc; + Node *expr; + + /* Check my caller didn't mess up */ + Assert(IsA(var, Var)); + Assert(var->vartype == RECORDOID); + + /* Find appropriate RTE */ + rte = get_rte_for_var(var, levelsup, context); + + attnum = var->varattno; + + if (attnum == InvalidAttrNumber) + { + /* Var is whole-row reference to RTE, so select the right field */ + return get_rte_attribute_name(rte, fieldno); + } + + expr = (Node *) var; /* default if we can't drill down */ + + switch (rte->rtekind) + { + case RTE_RELATION: + case RTE_SPECIAL: + /* + * This case should not occur: a column of a table 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 namespace + * to keep in step with varlevelsup in the subselect. + */ + deparse_namespace mydpns; + const char *result; + + mydpns.rtable = rte->subquery->rtable; + mydpns.outer_varno = mydpns.inner_varno = 0; + mydpns.outer_rte = mydpns.inner_rte = NULL; + + context->namespaces = lcons(&mydpns, context->namespaces); + + result = get_name_for_var_field((Var *) expr, fieldno, + 0, context); + + context->namespaces = list_delete_first(context->namespaces); + + return result; + } + /* 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); + if (IsA(expr, Var)) + return get_name_for_var_field((Var *) expr, fieldno, + var->varlevelsup + levelsup, + context); + /* 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; + } + + /* + * We now have an expression we can't expand any more, so see if + * get_expr_result_type() can do anything with it. If not, pass + * to lookup_rowtype_tupdesc() which will probably fail, but will + * give an appropriate error message while failing. + */ + if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + tupleDesc = lookup_rowtype_tupdesc(exprType(expr), exprTypmod(expr)); + + /* Got the tupdesc, so we can extract the field name */ + Assert(fieldno >= 1 && fieldno <= tupleDesc->natts); + return NameStr(tupleDesc->attrs[fieldno - 1]->attname); +} + + /* * find_rte_by_refname - look up an RTE by refname in a deparse context * @@ -3109,19 +3250,11 @@ get_rule_expr(Node *node, deparse_context *context, case T_FieldSelect: { FieldSelect *fselect = (FieldSelect *) node; - Oid argType = exprType((Node *) fselect->arg); - Oid typrelid; - char *fieldname; + Node *arg = (Node *) fselect->arg; + int fno = fselect->fieldnum; + const char *fieldname; bool need_parens; - /* lookup arg type and get the field name */ - typrelid = get_typ_typrelid(argType); - if (!OidIsValid(typrelid)) - elog(ERROR, "argument type %s of FieldSelect is not a tuple type", - format_type_be(argType)); - fieldname = get_relid_attribute_name(typrelid, - fselect->fieldnum); - /* * Parenthesize the argument unless it's an ArrayRef or * another FieldSelect. Note in particular that it would @@ -3129,13 +3262,36 @@ get_rule_expr(Node *node, deparse_context *context, * is not the issue here, having the right number of names * is. */ - need_parens = !IsA(fselect->arg, ArrayRef) && - !IsA(fselect->arg, FieldSelect); + need_parens = !IsA(arg, ArrayRef) && !IsA(arg, FieldSelect); if (need_parens) appendStringInfoChar(buf, '('); - get_rule_expr((Node *) fselect->arg, context, true); + get_rule_expr(arg, context, true); if (need_parens) appendStringInfoChar(buf, ')'); + + /* + * If it's a Var of type RECORD, we have to find what the Var + * refers to; otherwise we can use get_expr_result_type. + * If that fails, we try lookup_rowtype_tupdesc, which will + * probably fail too, but will ereport an acceptable message. + */ + if (IsA(arg, Var) && + ((Var *) arg)->vartype == RECORDOID) + fieldname = get_name_for_var_field((Var *) arg, fno, + 0, context); + else + { + TupleDesc tupdesc; + + if (get_expr_result_type(arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + tupdesc = lookup_rowtype_tupdesc(exprType(arg), + exprTypmod(arg)); + Assert(tupdesc); + /* Got the tupdesc, so we can extract the field name */ + Assert(fno >= 1 && fno <= tupdesc->natts); + fieldname = NameStr(tupdesc->attrs[fno - 1]->attname); + } + appendStringInfo(buf, ".%s", quote_identifier(fieldname)); } break;