Teach ruleutils to drill down into RECORD-type Vars in the same way
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 31 May 2005 03:03:59 +0000 (03:03 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 31 May 2005 03:03:59 +0000 (03:03 +0000)
that the parser now can, so that it can reverse-list cases involving
FieldSelect from a RECORD Var.

src/backend/utils/adt/ruleutils.c

index d10459a9bfe07b9226136a5595298b0c407e0559..be53f7373d9ce5274941e9a3cd6c53fe4d52a592 100644 (file)
@@ -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;