diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2001-02-14 21:35:07 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2001-02-14 21:35:07 +0000 |
commit | 4a66f9dd54694eb4d7ecce2c7e0f0c50dfde88cd (patch) | |
tree | 8810441569d5cf2e29f2a5c2b67ceb91d74deb2d /src/backend/utils/adt/ruleutils.c | |
parent | d42d31e78e2f9db73edb0b0ed35cafb1c409bdbf (diff) | |
download | postgresql-4a66f9dd54694eb4d7ecce2c7e0f0c50dfde88cd.tar.gz postgresql-4a66f9dd54694eb4d7ecce2c7e0f0c50dfde88cd.zip |
Change scoping of table and join refnames to conform to SQL92: a JOIN
clause with an alias is a <subquery> and therefore hides table references
appearing within it, according to the spec. This is the same as the
preliminary patch I posted to pgsql-patches yesterday, plus some really
grotty code in ruleutils.c to reverse-list a query tree with the correct
alias name depending on context. I'd rather not have done that, but unless
we want to force another initdb for 7.1, there's no other way for now.
Diffstat (limited to 'src/backend/utils/adt/ruleutils.c')
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 478 |
1 files changed, 409 insertions, 69 deletions
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3f9264308dd..872b607e87c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3,7 +3,7 @@ * back to source text * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.71 2001/01/03 22:01:05 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.72 2001/02/14 21:35:05 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -35,11 +35,11 @@ * **********************************************************************/ +#include "postgres.h" + #include <unistd.h> #include <fcntl.h> -#include "postgres.h" - #include "catalog/pg_index.h" #include "catalog/pg_operator.h" #include "catalog/pg_shadow.h" @@ -52,6 +52,7 @@ #include "parser/parse_expr.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -59,13 +60,30 @@ * Local data types * ---------- */ + +/* Context info needed for invoking a recursive querytree display routine */ typedef struct { StringInfo buf; /* output buffer to append to */ - List *rangetables; /* List of List of RangeTblEntry */ + List *namespaces; /* List of deparse_namespace nodes */ bool varprefix; /* TRUE to print prefixes on Vars */ } deparse_context; +/* + * Each level of query context around a subtree needs a level of Var namespace. + * The rangetable is the list of actual RTEs, and the namespace indicates + * which parts of the rangetable are accessible (and under what aliases) + * in the expression currently being looked at. A Var having varlevelsup=N + * refers to the N'th item (counting from 0) in the current context's + * namespaces list. + */ +typedef struct +{ + List *rtable; /* List of RangeTblEntry nodes */ + List *namespace; /* List of joinlist items (RangeTblRef and + * JoinExpr nodes) */ +} deparse_namespace; + /* ---------- * Global data @@ -92,7 +110,7 @@ static char *query_getopclass = "SELECT * FROM pg_opclass WHERE oid = $1"; */ static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc); static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc); -static void get_query_def(Query *query, StringInfo buf, List *parentrtables); +static void get_query_def(Query *query, StringInfo buf, List *parentnamespace); static void get_select_query_def(Query *query, deparse_context *context); static void get_insert_query_def(Query *query, deparse_context *context); static void get_update_query_def(Query *query, deparse_context *context); @@ -102,7 +120,14 @@ static void get_basic_select_query(Query *query, deparse_context *context); static void get_setop_query(Node *setOp, Query *query, deparse_context *context, bool toplevel); static bool simple_distinct(List *distinctClause, List *targetList); -static RangeTblEntry *get_rte_for_var(Var *var, deparse_context *context); +static void get_names_for_var(Var *var, deparse_context *context, + char **refname, char **attname); +static bool get_alias_for_case(CaseExpr *caseexpr, deparse_context *context, + char **refname, char **attname); +static bool find_alias_in_namespace(Node *nsnode, Node *expr, + List *rangetable, int levelsup, + char **refname, char **attname); +static bool phony_equal(Node *expr1, Node *expr2, int levelsup); static void get_rule_expr(Node *node, deparse_context *context); static void get_func_expr(Expr *expr, deparse_context *context); static void get_tle_expr(TargetEntry *tle, deparse_context *context); @@ -599,30 +624,24 @@ pg_get_userbyid(PG_FUNCTION_ARGS) * expr is the node tree to be deparsed. It must be a transformed expression * tree (ie, not the raw output of gram.y). * - * rangetables is a List of Lists of RangeTblEntry nodes: first sublist is for - * varlevelsup = 0, next for varlevelsup = 1, etc. In each sublist the first - * item is for varno = 1, next varno = 2, etc. (Each sublist has the same - * format as the rtable list of a parsetree or query.) + * dpcontext is a list of deparse_namespace nodes representing the context + * for interpreting Vars in the node tree. * * forceprefix is TRUE to force all Vars to be prefixed with their table names. - * Otherwise, a prefix is printed only if there's more than one table involved - * (and someday the code might try to print one only if there's ambiguity). * * The result is a palloc'd string. * ---------- */ char * -deparse_expression(Node *expr, List *rangetables, bool forceprefix) +deparse_expression(Node *expr, List *dpcontext, bool forceprefix) { StringInfoData buf; deparse_context context; initStringInfo(&buf); context.buf = &buf; - context.rangetables = rangetables; - context.varprefix = (forceprefix || - length(rangetables) != 1 || - length((List *) lfirst(rangetables)) != 1); + context.namespaces = dpcontext; + context.varprefix = forceprefix; rulename = ""; /* in case of errors */ @@ -632,6 +651,43 @@ deparse_expression(Node *expr, List *rangetables, bool forceprefix) } /* ---------- + * deparse_context_for - Build deparse context for a single relation + * + * Given the name and OID of a relation, build deparsing context for an + * expression referencing only that relation (as varno 1, varlevelsup 0). + * This is presently sufficient for the external uses of deparse_expression. + * ---------- + */ +List * +deparse_context_for(char *relname, Oid relid) +{ + deparse_namespace *dpns; + RangeTblEntry *rte; + RangeTblRef *rtr; + + dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace)); + + /* Build a minimal RTE for the rel */ + rte = makeNode(RangeTblEntry); + rte->relname = relname; + rte->relid = relid; + rte->eref = makeNode(Attr); + rte->eref->relname = relname; + rte->inh = false; + rte->inFromCl = true; + /* Build one-element rtable */ + dpns->rtable = makeList1(rte); + + /* Build a namespace list referencing this RTE only */ + rtr = makeNode(RangeTblRef); + rtr->rtindex = 1; + dpns->namespace = makeList1(rtr); + + /* Return a one-deep namespace stack */ + return makeList1(dpns); +} + +/* ---------- * make_ruledef - reconstruct the CREATE RULE command * for a given pg_rewrite tuple * ---------- @@ -722,6 +778,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc) Node *qual; Query *query; deparse_context context; + deparse_namespace dpns; appendStringInfo(buf, " WHERE "); @@ -729,8 +786,10 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc) query = (Query *) lfirst(actions); context.buf = buf; - context.rangetables = makeList1(query->rtable); + context.namespaces = makeList1(&dpns); context.varprefix = (length(query->rtable) != 1); + dpns.rtable = query->rtable; + dpns.namespace = query->jointree ? query->jointree->fromlist : NIL; get_rule_expr(qual, &context); } @@ -844,14 +903,17 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc) * ---------- */ static void -get_query_def(Query *query, StringInfo buf, List *parentrtables) +get_query_def(Query *query, StringInfo buf, List *parentnamespace) { deparse_context context; + deparse_namespace dpns; context.buf = buf; - context.rangetables = lcons(query->rtable, parentrtables); - context.varprefix = (parentrtables != NIL || + context.namespaces = lcons(&dpns, parentnamespace); + context.varprefix = (parentnamespace != NIL || length(query->rtable) != 1); + dpns.rtable = query->rtable; + dpns.namespace = query->jointree ? query->jointree->fromlist : NIL; switch (query->commandType) { @@ -1025,11 +1087,10 @@ get_basic_select_query(Query *query, deparse_context *context) else { Var *var = (Var *) (tle->expr); - RangeTblEntry *rte; + char *refname; char *attname; - rte = get_rte_for_var(var, context); - attname = get_rte_attribute_name(rte, var->varattno); + get_names_for_var(var, context, &refname, &attname); tell_as = (strcmp(attname, tle->resdom->resname) != 0); } @@ -1088,7 +1149,7 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context, Query *subquery = rte->subquery; Assert(subquery != NULL); - get_query_def(subquery, buf, context->rangetables); + get_query_def(subquery, buf, context->namespaces); } else if (IsA(setOp, SetOperationStmt)) { @@ -1336,20 +1397,277 @@ get_utility_query_def(Query *query, deparse_context *context) /* - * Find the RTE referenced by a (possibly nonlocal) Var. + * Get the relation refname and attname for a (possibly nonlocal) Var. + * + * This is trickier than it ought to be because of the possibility of aliases + * and limited scope of refnames. We have to try to return the correct alias + * with respect to the current namespace given by the context. */ -static RangeTblEntry * -get_rte_for_var(Var *var, deparse_context *context) +static void +get_names_for_var(Var *var, deparse_context *context, + char **refname, char **attname) { - List *rtlist = context->rangetables; + List *nslist = context->namespaces; int sup = var->varlevelsup; + deparse_namespace *dpns; + RangeTblEntry *rte; + + /* Find appropriate nesting depth */ + while (sup-- > 0 && nslist != NIL) + nslist = lnext(nslist); + if (nslist == NIL) + elog(ERROR, "get_names_for_var: bogus varlevelsup %d", + var->varlevelsup); + dpns = (deparse_namespace *) lfirst(nslist); + + /* Scan namespace to see if we can find an alias for the var */ + if (find_alias_in_namespace((Node *) dpns->namespace, (Node *) var, + dpns->rtable, var->varlevelsup, + refname, attname)) + return; + + /* + * Otherwise, fall back on the rangetable entry. This should happen + * only for uses of special RTEs like *NEW* and *OLD*, which won't + * get placed in our namespace. + */ + rte = rt_fetch(var->varno, dpns->rtable); + *refname = rte->eref->relname; + *attname = get_rte_attribute_name(rte, var->varattno); +} + +/* + * Check to see if a CASE expression matches a FULL JOIN's output expression. + * If so, return the refname and alias it should be expressed as. + */ +static bool +get_alias_for_case(CaseExpr *caseexpr, deparse_context *context, + char **refname, char **attname) +{ + List *nslist; + int sup; - while (sup-- > 0) - rtlist = lnext(rtlist); + /* + * This could be done more efficiently if we first groveled through the + * CASE to find varlevelsup values, but it's probably not worth the + * trouble. All this code will go away someday anyway ... + */ - return rt_fetch(var->varno, (List *) lfirst(rtlist)); + sup = 0; + foreach(nslist, context->namespaces) + { + deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist); + + if (find_alias_in_namespace((Node *) dpns->namespace, + (Node *) caseexpr, + dpns->rtable, sup, + refname, attname)) + return true; + sup++; + } + return false; } +/* + * Recursively scan a namespace (same representation as a jointree) to see + * if we can find an alias for the given expression. If so, return the + * correct alias refname and attname. The expression may be either a plain + * Var or a CASE expression (which may be a FULL JOIN reference). + */ +static bool +find_alias_in_namespace(Node *nsnode, Node *expr, + List *rangetable, int levelsup, + char **refname, char **attname) +{ + if (nsnode == NULL) + return false; + if (IsA(nsnode, RangeTblRef)) + { + if (IsA(expr, Var)) + { + Var *var = (Var *) expr; + int rtindex = ((RangeTblRef *) nsnode)->rtindex; + + if (var->varno == rtindex && var->varlevelsup == levelsup) + { + RangeTblEntry *rte = rt_fetch(rtindex, rangetable); + + *refname = rte->eref->relname; + *attname = get_rte_attribute_name(rte, var->varattno); + return true; + } + } + } + else if (IsA(nsnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) nsnode; + + if (j->alias) + { + List *vlist; + List *nlist; + + /* + * Does the expr match any of the output columns of the join? + * + * We can't just use equal() here, because the given expr may + * have nonzero levelsup, whereas the saved expression in the + * JoinExpr should have zero levelsup. + */ + nlist = j->colnames; + foreach(vlist, j->colvars) + { + if (phony_equal(lfirst(vlist), expr, levelsup)) + { + *refname = j->alias->relname; + *attname = strVal(lfirst(nlist)); + return true; + } + nlist = lnext(nlist); + } + /* + * Tables within an aliased join are invisible from outside + * the join, according to the scope rules of SQL92 (the join + * is considered a subquery). So, stop here. + */ + return false; + } + if (find_alias_in_namespace(j->larg, expr, + rangetable, levelsup, + refname, attname)) + return true; + if (find_alias_in_namespace(j->rarg, expr, + rangetable, levelsup, + refname, attname)) + return true; + } + else if (IsA(nsnode, List)) + { + List *l; + + foreach(l, (List *) nsnode) + { + if (find_alias_in_namespace(lfirst(l), expr, + rangetable, levelsup, + refname, attname)) + return true; + } + } + else + elog(ERROR, "find_alias_in_namespace: unexpected node type %d", + nodeTag(nsnode)); + return false; +} + +/* + * Check for equality of two expressions, with the proviso that all Vars in + * expr1 should have varlevelsup = 0, while all Vars in expr2 should have + * varlevelsup = levelsup. + * + * In reality we only need to support equality checks on Vars and the type + * of CASE expression that is used for FULL JOIN outputs, so not all node + * types need be handled here. + * + * Otherwise, this code is a straight ripoff from equalfuncs.c. + */ +static bool +phony_equal(Node *expr1, Node *expr2, int levelsup) +{ + if (expr1 == NULL || expr2 == NULL) + return (expr1 == expr2); + if (nodeTag(expr1) != nodeTag(expr2)) + return false; + if (IsA(expr1, Var)) + { + Var *a = (Var *) expr1; + Var *b = (Var *) expr2; + + if (a->varno != b->varno) + return false; + if (a->varattno != b->varattno) + return false; + if (a->vartype != b->vartype) + return false; + if (a->vartypmod != b->vartypmod) + return false; + if (a->varlevelsup != 0 || b->varlevelsup != levelsup) + return false; + if (a->varnoold != b->varnoold) + return false; + if (a->varoattno != b->varoattno) + return false; + return true; + } + if (IsA(expr1, CaseExpr)) + { + CaseExpr *a = (CaseExpr *) expr1; + CaseExpr *b = (CaseExpr *) expr2; + + if (a->casetype != b->casetype) + return false; + if (!phony_equal(a->arg, b->arg, levelsup)) + return false; + if (!phony_equal((Node *) a->args, (Node *) b->args, levelsup)) + return false; + if (!phony_equal(a->defresult, b->defresult, levelsup)) + return false; + return true; + } + if (IsA(expr1, CaseWhen)) + { + CaseWhen *a = (CaseWhen *) expr1; + CaseWhen *b = (CaseWhen *) expr2; + + if (!phony_equal(a->expr, b->expr, levelsup)) + return false; + if (!phony_equal(a->result, b->result, levelsup)) + return false; + return true; + } + if (IsA(expr1, Expr)) + { + Expr *a = (Expr *) expr1; + Expr *b = (Expr *) expr2; + + if (a->opType != b->opType) + return false; + if (!phony_equal(a->oper, b->oper, levelsup)) + return false; + if (!phony_equal((Node *) a->args, (Node *) b->args, levelsup)) + return false; + return true; + } + if (IsA(expr1, Func)) + { + Func *a = (Func *) expr1; + Func *b = (Func *) expr2; + + if (a->funcid != b->funcid) + return false; + if (a->functype != b->functype) + return false; + return true; + } + if (IsA(expr1, List)) + { + List *la = (List *) expr1; + List *lb = (List *) expr2; + List *l; + + if (length(la) != length(lb)) + return false; + foreach(l, la) + { + if (!phony_equal(lfirst(l), lfirst(lb), levelsup)) + return false; + lb = lnext(lb); + } + return true; + } + /* If we get here, there was something weird in a JOIN's colvars list */ + elog(ERROR, "phony_equal: unexpected node type %d", nodeTag(expr1)); + return false; +} /* ---------- * get_rule_expr - Parse back an expression @@ -1381,21 +1699,21 @@ get_rule_expr(Node *node, deparse_context *context) case T_Var: { Var *var = (Var *) node; - RangeTblEntry *rte = get_rte_for_var(var, context); + char *refname; + char *attname; + get_names_for_var(var, context, &refname, &attname); if (context->varprefix) { - if (strcmp(rte->eref->relname, "*NEW*") == 0) + if (strcmp(refname, "*NEW*") == 0) appendStringInfo(buf, "new."); - else if (strcmp(rte->eref->relname, "*OLD*") == 0) + else if (strcmp(refname, "*OLD*") == 0) appendStringInfo(buf, "old."); else appendStringInfo(buf, "%s.", - quote_identifier(rte->eref->relname)); + quote_identifier(refname)); } - appendStringInfo(buf, "%s", - quote_identifier(get_rte_attribute_name(rte, - var->varattno))); + appendStringInfo(buf, "%s", quote_identifier(attname)); } break; @@ -1606,6 +1924,19 @@ get_rule_expr(Node *node, deparse_context *context) { CaseExpr *caseexpr = (CaseExpr *) node; List *temp; + char *refname; + char *attname; + + /* Hack for providing aliases for FULL JOIN outputs */ + if (get_alias_for_case(caseexpr, context, + &refname, &attname)) + { + if (context->varprefix) + appendStringInfo(buf, "%s.", + quote_identifier(refname)); + appendStringInfo(buf, "%s", quote_identifier(attname)); + break; + } appendStringInfo(buf, "CASE"); foreach(temp, caseexpr->args) @@ -1645,6 +1976,7 @@ get_func_expr(Expr *expr, deparse_context *context) { StringInfo buf = context->buf; Func *func = (Func *) (expr->oper); + Oid funcoid = func->funcid; HeapTuple proctup; Form_pg_proc procStruct; char *proname; @@ -1653,42 +1985,37 @@ get_func_expr(Expr *expr, deparse_context *context) char *sep; /* + * nullvalue() and nonnullvalue() should get turned into special + * syntax + */ + if (funcoid == F_NULLVALUE) + { + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) lfirst(expr->args), context); + appendStringInfo(buf, " ISNULL)"); + return; + } + if (funcoid == F_NONNULLVALUE) + { + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) lfirst(expr->args), context); + appendStringInfo(buf, " NOTNULL)"); + return; + } + + /* * Get the functions pg_proc tuple */ proctup = SearchSysCache(PROCOID, - ObjectIdGetDatum(func->funcid), + ObjectIdGetDatum(funcoid), 0, 0, 0); if (!HeapTupleIsValid(proctup)) - elog(ERROR, "cache lookup for proc %u failed", func->funcid); + elog(ERROR, "cache lookup for proc %u failed", funcoid); procStruct = (Form_pg_proc) GETSTRUCT(proctup); proname = NameStr(procStruct->proname); /* - * nullvalue() and nonnullvalue() should get turned into special - * syntax - */ - if (procStruct->pronargs == 1 && procStruct->proargtypes[0] == InvalidOid) - { - if (strcmp(proname, "nullvalue") == 0) - { - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) lfirst(expr->args), context); - appendStringInfo(buf, " ISNULL)"); - ReleaseSysCache(proctup); - return; - } - if (strcmp(proname, "nonnullvalue") == 0) - { - appendStringInfoChar(buf, '('); - get_rule_expr((Node *) lfirst(expr->args), context); - appendStringInfo(buf, " NOTNULL)"); - ReleaseSysCache(proctup); - return; - } - } - - /* * Check to see if function is a length-coercion function for some * datatype. If so, display the operation as a type cast. */ @@ -1968,7 +2295,7 @@ get_sublink_expr(Node *node, deparse_context *context) if (need_paren) appendStringInfoChar(buf, '('); - get_query_def(query, buf, context->rangetables); + get_query_def(query, buf, context->namespaces); if (need_paren) appendStringInfo(buf, "))"); @@ -2024,6 +2351,16 @@ static void get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) { StringInfo buf = context->buf; + deparse_namespace *dpns; + List *sv_namespace; + + /* + * FROM-clause items have limited visibility of query's namespace. + * Save and restore the outer namespace setting while we munge it. + */ + dpns = (deparse_namespace *) lfirst(context->namespaces); + sv_namespace = dpns->namespace; + dpns->namespace = NIL; if (IsA(jtnode, RangeTblRef)) { @@ -2042,7 +2379,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* Subquery RTE */ Assert(rte->subquery != NULL); appendStringInfoChar(buf, '('); - get_query_def(rte->subquery, buf, context->rangetables); + get_query_def(rte->subquery, buf, context->namespaces); appendStringInfoChar(buf, ')'); } if (rte->alias != NULL) @@ -2053,7 +2390,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) { List *col; - appendStringInfo(buf, " ("); + appendStringInfo(buf, "("); foreach(col, rte->alias->attrs) { if (col != rte->alias->attrs) @@ -2116,6 +2453,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) } else if (j->quals) { + dpns->namespace = makeList2(j->larg, j->rarg); appendStringInfo(buf, " ON ("); get_rule_expr(j->quals, context); appendStringInfoChar(buf, ')'); @@ -2131,7 +2469,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) { List *col; - appendStringInfo(buf, " ("); + appendStringInfo(buf, "("); foreach(col, j->alias->attrs) { if (col != j->alias->attrs) @@ -2146,6 +2484,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) else elog(ERROR, "get_from_clause_item: unexpected node type %d", nodeTag(jtnode)); + + dpns->namespace = sv_namespace; } |