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.
This commit is contained in:
Tom Lane 2005-05-31 03:03:59 +00:00
parent 83b72ee286
commit 6dfe64ee57

View 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"
@ -2420,6 +2421,43 @@ get_utility_query_def(Query *query, deparse_context *context)
}
/*
* 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.
*/
static RangeTblEntry *
get_rte_for_var(Var *var, int levelsup, deparse_context *context)
{
RangeTblEntry *rte;
int netlevelsup;
deparse_namespace *dpns;
/* Find appropriate nesting depth */
netlevelsup = var->varlevelsup + levelsup;
if (netlevelsup >= list_length(context->namespaces))
elog(ERROR, "bogus varlevelsup: %d offset %d",
var->varlevelsup, levelsup);
dpns = (deparse_namespace *) list_nth(context->namespaces,
netlevelsup);
/* Find the relevant RTE */
if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
rte = rt_fetch(var->varno, dpns->rtable);
else if (var->varno == dpns->outer_varno)
rte = dpns->outer_rte;
else if (var->varno == dpns->inner_varno)
rte = dpns->inner_rte;
else
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.
*
@ -2442,29 +2480,10 @@ static void
get_names_for_var(Var *var, int levelsup, deparse_context *context,
char **schemaname, char **refname, char **attname)
{
int netlevelsup;
deparse_namespace *dpns;
RangeTblEntry *rte;
/* Find appropriate nesting depth */
netlevelsup = var->varlevelsup + levelsup;
if (netlevelsup >= list_length(context->namespaces))
elog(ERROR, "bogus varlevelsup: %d offset %d",
var->varlevelsup, levelsup);
dpns = (deparse_namespace *) list_nth(context->namespaces,
netlevelsup);
/* Find the relevant RTE */
if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
rte = rt_fetch(var->varno, dpns->rtable);
else if (var->varno == dpns->outer_varno)
rte = dpns->outer_rte;
else if (var->varno == dpns->inner_varno)
rte = dpns->inner_rte;
else
rte = NULL;
if (rte == NULL)
elog(ERROR, "bogus varno: %d", var->varno);
/* 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;