]> granicus.if.org Git - postgresql/commitdiff
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;