postgresql/src/backend/utils/adt/ruleutils.c
2001-10-25 05:50:21 +00:00

2722 lines
68 KiB
C

/**********************************************************************
* ruleutils.c - Functions to convert stored expressions/querytrees
* back to source text
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.86 2001/10/25 05:49:45 momjian Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
* The author hereby grants permission to use, copy, modify,
* distribute, and license this software and its documentation
* for any purpose, provided that existing copyright notices are
* retained in all copies and that this notice is included
* verbatim in any distributions. No written agreement, license,
* or royalty fee is required for any of the authorized uses.
* Modifications to this software may be copyrighted by their
* author and need not follow the licensing terms described
* here, provided that the new terms are clearly indicated on
* the first page of each file where they apply.
*
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
* PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
* CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
* SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
* IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
* AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO
* OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
* ENHANCEMENTS, OR MODIFICATIONS.
*
**********************************************************************/
#include "postgres.h"
#include <unistd.h>
#include <fcntl.h>
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/pg_index.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_shadow.h"
#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "parser/keywords.h"
#include "parser/parse_expr.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/lsyscache.h"
/* ----------
* Local data types
* ----------
*/
/* Context info needed for invoking a recursive querytree display routine */
typedef struct
{
StringInfo buf; /* output buffer to append to */
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
* ----------
*/
static char *rulename = NULL;
static void *plan_getrule = NULL;
static char *query_getrule = "SELECT * FROM pg_rewrite WHERE rulename = $1";
static void *plan_getview = NULL;
static char *query_getview = "SELECT * FROM pg_rewrite WHERE rulename = $1";
/* ----------
* Local functions
*
* Most of these functions used to use fixed-size buffers to build their
* results. Now, they take an (already initialized) StringInfo object
* as a parameter, and append their text output to its contents.
* ----------
*/
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 *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);
static void get_delete_query_def(Query *query, deparse_context *context);
static void get_utility_query_def(Query *query, deparse_context *context);
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 void get_rule_sortgroupclause(SortClause *srt, List *tlist,
bool force_colno,
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 Node *strip_type_coercion(Node *expr, Oid resultType);
static void get_tle_expr(TargetEntry *tle, deparse_context *context);
static void get_const_expr(Const *constval, deparse_context *context);
static void get_sublink_expr(Node *node, deparse_context *context);
static void get_from_clause(Query *query, deparse_context *context);
static void get_from_clause_item(Node *jtnode, Query *query,
deparse_context *context);
static void get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf);
static bool tleIsArrayAssign(TargetEntry *tle);
static char *quote_identifier(char *ident);
static char *get_relation_name(Oid relid);
static char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
/* ----------
* get_ruledef - Do it all and return a text
* that could be used as a statement
* to recreate the rule
* ----------
*/
Datum
pg_get_ruledef(PG_FUNCTION_ARGS)
{
Name rname = PG_GETARG_NAME(0);
text *ruledef;
Datum args[1];
char nulls[2];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
int len;
/*
* We need the rules name somewhere deep down: rulename is global
*/
rulename = pstrdup(NameStr(*rname));
/*
* Connect to SPI manager
*/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "get_ruledef: cannot connect to SPI manager");
/*
* On the first call prepare the plan to lookup pg_proc. We read
* pg_proc over the SPI manager instead of using the syscache to be
* checked for read access on pg_proc.
*/
if (plan_getrule == NULL)
{
Oid argtypes[1];
void *plan;
argtypes[0] = NAMEOID;
plan = SPI_prepare(query_getrule, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getrule);
plan_getrule = SPI_saveplan(plan);
}
/*
* Get the pg_rewrite tuple for this rule
*/
args[0] = PointerGetDatum(rulename);
nulls[0] = (rulename == NULL) ? 'n' : ' ';
nulls[1] = '\0';
spirc = SPI_execp(plan_getrule, args, nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for %s", rulename);
if (SPI_processed != 1)
{
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "get_ruledef: SPI_finish() failed");
ruledef = palloc(VARHDRSZ + 1);
VARATT_SIZEP(ruledef) = VARHDRSZ + 1;
VARDATA(ruledef)[0] = '-';
PG_RETURN_TEXT_P(ruledef);
}
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
/*
* Get the rules definition and put it into executors memory
*/
initStringInfo(&buf);
make_ruledef(&buf, ruletup, rulettc);
len = buf.len + VARHDRSZ;
ruledef = SPI_palloc(len);
VARATT_SIZEP(ruledef) = len;
memcpy(VARDATA(ruledef), buf.data, buf.len);
pfree(buf.data);
/*
* Disconnect from SPI manager
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "get_ruledef: SPI_finish() failed");
/*
* Easy - isn't it?
*/
PG_RETURN_TEXT_P(ruledef);
}
/* ----------
* get_viewdef - Mainly the same thing, but we
* only return the SELECT part of a view
* ----------
*/
Datum
pg_get_viewdef(PG_FUNCTION_ARGS)
{
Name vname = PG_GETARG_NAME(0);
text *ruledef;
Datum args[1];
char nulls[1];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
int len;
char *name;
/*
* We need the view name somewhere deep down
*/
rulename = pstrdup(NameStr(*vname));
/*
* Connect to SPI manager
*/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "get_viewdef: cannot connect to SPI manager");
/*
* On the first call prepare the plan to lookup pg_proc. We read
* pg_proc over the SPI manager instead of using the syscache to be
* checked for read access on pg_proc.
*/
if (plan_getview == NULL)
{
Oid argtypes[1];
void *plan;
argtypes[0] = NAMEOID;
plan = SPI_prepare(query_getview, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getview);
plan_getview = SPI_saveplan(plan);
}
/*
* Get the pg_rewrite tuple for this rule: rulename is actually
* viewname here
*/
name = MakeRetrieveViewRuleName(rulename);
args[0] = PointerGetDatum(name);
nulls[0] = ' ';
spirc = SPI_execp(plan_getview, args, nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename);
initStringInfo(&buf);
if (SPI_processed != 1)
appendStringInfo(&buf, "Not a view");
else
{
/*
* Get the rules definition and put it into executors memory
*/
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
make_viewdef(&buf, ruletup, rulettc);
}
len = buf.len + VARHDRSZ;
ruledef = SPI_palloc(len);
VARATT_SIZEP(ruledef) = len;
memcpy(VARDATA(ruledef), buf.data, buf.len);
pfree(buf.data);
pfree(name);
/*
* Disconnect from SPI manager
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "get_viewdef: SPI_finish() failed");
/*
* Easy - isn't it?
*/
PG_RETURN_TEXT_P(ruledef);
}
/* ----------
* get_indexdef - Get the definition of an index
* ----------
*/
Datum
pg_get_indexdef(PG_FUNCTION_ARGS)
{
Oid indexrelid = PG_GETARG_OID(0);
text *indexdef;
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_indrel;
Form_pg_index idxrec;
Form_pg_class idxrelrec;
Form_pg_class indrelrec;
Form_pg_am amrec;
int len;
int keyno;
StringInfoData buf;
StringInfoData keybuf;
char *sep;
/*
* Fetch the pg_index tuple by the Oid of the index
*/
ht_idx = SearchSysCache(INDEXRELID,
ObjectIdGetDatum(indexrelid),
0, 0, 0);
if (!HeapTupleIsValid(ht_idx))
elog(ERROR, "syscache lookup for index %u failed", indexrelid);
idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
/*
* Fetch the pg_class tuple of the index relation
*/
ht_idxrel = SearchSysCache(RELOID,
ObjectIdGetDatum(idxrec->indexrelid),
0, 0, 0);
if (!HeapTupleIsValid(ht_idxrel))
elog(ERROR, "syscache lookup for relid %u failed", idxrec->indexrelid);
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
/*
* Fetch the pg_class tuple of the indexed relation
*/
ht_indrel = SearchSysCache(RELOID,
ObjectIdGetDatum(idxrec->indrelid),
0, 0, 0);
if (!HeapTupleIsValid(ht_indrel))
elog(ERROR, "syscache lookup for relid %u failed", idxrec->indrelid);
indrelrec = (Form_pg_class) GETSTRUCT(ht_indrel);
/*
* Fetch the pg_am tuple of the index' access method
*
* There is no syscache for this, so use index.c subroutine.
*/
amrec = AccessMethodObjectIdGetForm(idxrelrec->relam,
CurrentMemoryContext);
if (!amrec)
elog(ERROR, "lookup for AM %u failed", idxrelrec->relam);
/*
* Start the index definition
*/
initStringInfo(&buf);
appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
quote_identifier(NameStr(indrelrec->relname)),
quote_identifier(NameStr(amrec->amname)));
/*
* Collect the indexed attributes in keybuf
*/
initStringInfo(&keybuf);
sep = "";
for (keyno = 0; keyno < INDEX_MAX_KEYS; keyno++)
{
AttrNumber attnum = idxrec->indkey[keyno];
if (attnum == InvalidAttrNumber)
break;
appendStringInfo(&keybuf, sep);
sep = ", ";
/*
* Add the indexed field name
*/
appendStringInfo(&keybuf, "%s",
quote_identifier(get_relid_attribute_name(idxrec->indrelid,
attnum)));
/*
* If not a functional index, add the operator class name
*/
if (idxrec->indproc == InvalidOid)
get_opclass_name(idxrec->indclass[keyno],
get_atttype(idxrec->indrelid, attnum),
&keybuf);
}
if (idxrec->indproc != InvalidOid)
{
/*
* For functional index say 'func (attrs) opclass'
*/
HeapTuple proctup;
Form_pg_proc procStruct;
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(idxrec->indproc),
0, 0, 0);
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup for proc %u failed", idxrec->indproc);
procStruct = (Form_pg_proc) GETSTRUCT(proctup);
appendStringInfo(&buf, "%s(%s)",
quote_identifier(NameStr(procStruct->proname)),
keybuf.data);
get_opclass_name(idxrec->indclass[0], procStruct->prorettype, &buf);
ReleaseSysCache(proctup);
}
else
{
/*
* Otherwise say 'attr opclass [, ...]'
*/
appendStringInfo(&buf, "%s", keybuf.data);
}
appendStringInfo(&buf, ")");
/*
* If it's a partial index, decompile and append the predicate
*/
if (VARSIZE(&idxrec->indpred) > VARHDRSZ)
{
Node *node;
List *context;
char *exprstr;
char *str;
/* Convert TEXT object to C string */
exprstr = DatumGetCString(DirectFunctionCall1(textout,
PointerGetDatum(&idxrec->indpred)));
/* Convert expression to node tree */
node = (Node *) stringToNode(exprstr);
/*
* If top level is a List, assume it is an implicit-AND structure,
* and convert to explicit AND. This is needed for partial index
* predicates.
*/
if (node && IsA(node, List))
node = (Node *) make_ands_explicit((List *) node);
/* Deparse */
context = deparse_context_for(NameStr(indrelrec->relname),
idxrec->indrelid);
str = deparse_expression(node, context, false);
appendStringInfo(&buf, " WHERE %s", str);
}
/*
* Create the result as a TEXT datum, and free working data
*/
len = buf.len + VARHDRSZ;
indexdef = (text *) palloc(len);
VARATT_SIZEP(indexdef) = len;
memcpy(VARDATA(indexdef), buf.data, buf.len);
pfree(buf.data);
pfree(keybuf.data);
pfree(amrec);
ReleaseSysCache(ht_idx);
ReleaseSysCache(ht_idxrel);
ReleaseSysCache(ht_indrel);
PG_RETURN_TEXT_P(indexdef);
}
/* ----------
* get_expr - Decompile an expression tree
*
* Input: an expression tree in nodeToString form, and a relation OID
*
* Output: reverse-listed expression
*
* Currently, the expression can only refer to a single relation, namely
* the one specified by the second parameter. This is sufficient for
* partial indexes, column default expressions, etc.
* ----------
*/
Datum
pg_get_expr(PG_FUNCTION_ARGS)
{
text *expr = PG_GETARG_TEXT_P(0);
Oid relid = PG_GETARG_OID(1);
text *result;
Node *node;
List *context;
char *exprstr;
char *relname;
char *str;
/* Get the name for the relation */
relname = get_rel_name(relid);
if (relname == NULL)
PG_RETURN_NULL(); /* should we raise an error? */
/* Convert input TEXT object to C string */
exprstr = DatumGetCString(DirectFunctionCall1(textout,
PointerGetDatum(expr)));
/* Convert expression to node tree */
node = (Node *) stringToNode(exprstr);
/*
* If top level is a List, assume it is an implicit-AND structure, and
* convert to explicit AND. This is needed for partial index
* predicates.
*/
if (node && IsA(node, List))
node = (Node *) make_ands_explicit((List *) node);
/* Deparse */
context = deparse_context_for(relname, relid);
str = deparse_expression(node, context, false);
/* Pass the result back as TEXT */
result = DatumGetTextP(DirectFunctionCall1(textin,
CStringGetDatum(str)));
PG_RETURN_TEXT_P(result);
}
/* ----------
* get_userbyid - Get a user name by usesysid and
* fallback to 'unknown (UID=n)'
* ----------
*/
Datum
pg_get_userbyid(PG_FUNCTION_ARGS)
{
int32 uid = PG_GETARG_INT32(0);
Name result;
HeapTuple usertup;
Form_pg_shadow user_rec;
/*
* Allocate space for the result
*/
result = (Name) palloc(NAMEDATALEN);
memset(NameStr(*result), 0, NAMEDATALEN);
/*
* Get the pg_shadow entry and print the result
*/
usertup = SearchSysCache(SHADOWSYSID,
ObjectIdGetDatum(uid),
0, 0, 0);
if (HeapTupleIsValid(usertup))
{
user_rec = (Form_pg_shadow) GETSTRUCT(usertup);
StrNCpy(NameStr(*result), NameStr(user_rec->usename), NAMEDATALEN);
ReleaseSysCache(usertup);
}
else
sprintf(NameStr(*result), "unknown (UID=%d)", uid);
PG_RETURN_NAME(result);
}
/* ----------
* deparse_expression - General utility for deparsing expressions
*
* expr is the node tree to be deparsed. It must be a transformed expression
* tree (ie, not the raw output of gram.y).
*
* 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.
*
* The result is a palloc'd string.
* ----------
*/
char *
deparse_expression(Node *expr, List *dpcontext, bool forceprefix)
{
StringInfoData buf;
deparse_context context;
initStringInfo(&buf);
context.buf = &buf;
context.namespaces = dpcontext;
context.varprefix = forceprefix;
rulename = ""; /* in case of errors */
get_rule_expr(expr, &context);
return buf.data;
}
/* ----------
* 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
* ----------
*/
static void
make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
{
char ev_type;
Oid ev_class;
int2 ev_attr;
bool is_instead;
char *ev_qual;
char *ev_action;
List *actions = NIL;
int fno;
bool isnull;
/*
* Get the attribute values from the rules tuple
*/
fno = SPI_fnumber(rulettc, "ev_type");
ev_type = (char) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_class");
ev_class = (Oid) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_attr");
ev_attr = (int2) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "is_instead");
is_instead = (bool) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_qual");
ev_qual = SPI_getvalue(ruletup, rulettc, fno);
fno = SPI_fnumber(rulettc, "ev_action");
ev_action = SPI_getvalue(ruletup, rulettc, fno);
if (ev_action != NULL)
actions = (List *) stringToNode(ev_action);
/*
* Build the rules definition text
*/
appendStringInfo(buf, "CREATE RULE %s AS ON ",
quote_identifier(rulename));
/* The event the rule is fired for */
switch (ev_type)
{
case '1':
appendStringInfo(buf, "SELECT");
break;
case '2':
appendStringInfo(buf, "UPDATE");
break;
case '3':
appendStringInfo(buf, "INSERT");
break;
case '4':
appendStringInfo(buf, "DELETE");
break;
default:
elog(ERROR, "get_ruledef: rule %s has unsupported event type %d",
rulename, ev_type);
break;
}
/* The relation the rule is fired on */
appendStringInfo(buf, " TO %s",
quote_identifier(get_relation_name(ev_class)));
if (ev_attr > 0)
appendStringInfo(buf, ".%s",
quote_identifier(get_relid_attribute_name(ev_class,
ev_attr)));
/* If the rule has an event qualification, add it */
if (ev_qual == NULL)
ev_qual = "";
if (strlen(ev_qual) > 0 && strcmp(ev_qual, "<>") != 0)
{
Node *qual;
Query *query;
deparse_context context;
deparse_namespace dpns;
appendStringInfo(buf, " WHERE ");
qual = stringToNode(ev_qual);
query = (Query *) lfirst(actions);
context.buf = buf;
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);
}
appendStringInfo(buf, " DO ");
/* The INSTEAD keyword (if so) */
if (is_instead)
appendStringInfo(buf, "INSTEAD ");
/* Finally the rules actions */
if (length(actions) > 1)
{
List *action;
Query *query;
appendStringInfo(buf, "(");
foreach(action, actions)
{
query = (Query *) lfirst(action);
get_query_def(query, buf, NIL);
appendStringInfo(buf, "; ");
}
appendStringInfo(buf, ");");
}
else
{
if (length(actions) == 0)
{
appendStringInfo(buf, "NOTHING;");
}
else
{
Query *query;
query = (Query *) lfirst(actions);
get_query_def(query, buf, NIL);
appendStringInfo(buf, ";");
}
}
}
/* ----------
* make_viewdef - reconstruct the SELECT part of a
* view rewrite rule
* ----------
*/
static void
make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
{
Query *query;
char ev_type;
Oid ev_class;
int2 ev_attr;
bool is_instead;
char *ev_qual;
char *ev_action;
List *actions = NIL;
int fno;
bool isnull;
/*
* Get the attribute values from the rules tuple
*/
fno = SPI_fnumber(rulettc, "ev_type");
ev_type = (char) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_class");
ev_class = (Oid) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_attr");
ev_attr = (int2) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "is_instead");
is_instead = (bool) SPI_getbinval(ruletup, rulettc, fno, &isnull);
fno = SPI_fnumber(rulettc, "ev_qual");
ev_qual = SPI_getvalue(ruletup, rulettc, fno);
fno = SPI_fnumber(rulettc, "ev_action");
ev_action = SPI_getvalue(ruletup, rulettc, fno);
if (ev_action != NULL)
actions = (List *) stringToNode(ev_action);
if (length(actions) != 1)
{
appendStringInfo(buf, "Not a view");
return;
}
query = (Query *) lfirst(actions);
if (ev_type != '1' || ev_attr >= 0 || !is_instead ||
strcmp(ev_qual, "<>") != 0)
{
appendStringInfo(buf, "Not a view");
return;
}
get_query_def(query, buf, NIL);
appendStringInfo(buf, ";");
}
/* ----------
* get_query_def - Parse back one action from
* the parsetree in the actions
* list
* ----------
*/
static void
get_query_def(Query *query, StringInfo buf, List *parentnamespace)
{
deparse_context context;
deparse_namespace dpns;
context.buf = buf;
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)
{
case CMD_SELECT:
get_select_query_def(query, &context);
break;
case CMD_UPDATE:
get_update_query_def(query, &context);
break;
case CMD_INSERT:
get_insert_query_def(query, &context);
break;
case CMD_DELETE:
get_delete_query_def(query, &context);
break;
case CMD_NOTHING:
appendStringInfo(buf, "NOTHING");
break;
case CMD_UTILITY:
get_utility_query_def(query, &context);
break;
default:
elog(ERROR, "get_ruledef of %s: query command type %d not implemented yet",
rulename, query->commandType);
break;
}
}
/* ----------
* get_select_query_def - Parse back a SELECT parsetree
* ----------
*/
static void
get_select_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
bool force_colno;
char *sep;
List *l;
/*
* If the Query node has a setOperations tree, then it's the top level
* of a UNION/INTERSECT/EXCEPT query; only the ORDER BY and LIMIT
* fields are interesting in the top query itself.
*/
if (query->setOperations)
{
get_setop_query(query->setOperations, query, context, true);
/* ORDER BY clauses must be simple in this case */
force_colno = true;
}
else
{
get_basic_select_query(query, context);
force_colno = false;
}
/* Add the ORDER BY clause if given */
if (query->sortClause != NIL)
{
appendStringInfo(buf, " ORDER BY ");
sep = "";
foreach(l, query->sortClause)
{
SortClause *srt = (SortClause *) lfirst(l);
char *opname;
appendStringInfo(buf, sep);
get_rule_sortgroupclause(srt, query->targetList,
force_colno, context);
opname = get_opname(srt->sortop);
if (strcmp(opname, "<") != 0)
{
if (strcmp(opname, ">") == 0)
appendStringInfo(buf, " DESC");
else
appendStringInfo(buf, " USING %s", opname);
}
sep = ", ";
}
}
/* Add the LIMIT clause if given */
if (query->limitOffset != NULL)
{
appendStringInfo(buf, " OFFSET ");
get_rule_expr(query->limitOffset, context);
}
if (query->limitCount != NULL)
{
appendStringInfo(buf, " LIMIT ");
if (IsA(query->limitCount, Const) &&
((Const *) query->limitCount)->constisnull)
appendStringInfo(buf, "ALL");
else
get_rule_expr(query->limitCount, context);
}
}
static void
get_basic_select_query(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
List *l;
/*
* Build up the query string - first we say SELECT
*/
appendStringInfo(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
{
if (has_distinct_on_clause(query))
{
appendStringInfo(buf, " DISTINCT ON (");
sep = "";
foreach(l, query->distinctClause)
{
SortClause *srt = (SortClause *) lfirst(l);
appendStringInfo(buf, sep);
get_rule_sortgroupclause(srt, query->targetList,
false, context);
sep = ", ";
}
appendStringInfo(buf, ")");
}
else
appendStringInfo(buf, " DISTINCT");
}
/* Then we tell what to select (the targetlist) */
sep = " ";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
bool tell_as = false;
if (tle->resdom->resjunk)
continue; /* ignore junk entries */
appendStringInfo(buf, sep);
sep = ", ";
/* Do NOT use get_tle_expr here; see its comments! */
get_rule_expr(tle->expr, context);
/* Check if we must say AS ... */
if (!IsA(tle->expr, Var))
tell_as = (strcmp(tle->resdom->resname, "?column?") != 0);
else
{
Var *var = (Var *) (tle->expr);
char *refname;
char *attname;
get_names_for_var(var, context, &refname, &attname);
tell_as = (attname == NULL ||
strcmp(attname, tle->resdom->resname) != 0);
}
/* and do if so */
if (tell_as)
appendStringInfo(buf, " AS %s",
quote_identifier(tle->resdom->resname));
}
/* Add the FROM clause if needed */
get_from_clause(query, context);
/* Add the WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendStringInfo(buf, " WHERE ");
get_rule_expr(query->jointree->quals, context);
}
/* Add the GROUP BY clause if given */
if (query->groupClause != NULL)
{
appendStringInfo(buf, " GROUP BY ");
sep = "";
foreach(l, query->groupClause)
{
GroupClause *grp = (GroupClause *) lfirst(l);
appendStringInfo(buf, sep);
get_rule_sortgroupclause(grp, query->targetList,
false, context);
sep = ", ";
}
}
/* Add the HAVING clause if given */
if (query->havingQual != NULL)
{
appendStringInfo(buf, " HAVING ");
get_rule_expr(query->havingQual, context);
}
}
static void
get_setop_query(Node *setOp, Query *query, deparse_context *context,
bool toplevel)
{
StringInfo buf = context->buf;
if (IsA(setOp, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) setOp;
RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
Query *subquery = rte->subquery;
Assert(subquery != NULL);
get_query_def(subquery, buf, context->namespaces);
}
else if (IsA(setOp, SetOperationStmt))
{
SetOperationStmt *op = (SetOperationStmt *) setOp;
/*
* Must suppress parens at top level of a setop tree because of
* grammar limitations...
*/
if (!toplevel)
appendStringInfo(buf, "(");
get_setop_query(op->larg, query, context, false);
switch (op->op)
{
case SETOP_UNION:
appendStringInfo(buf, " UNION ");
break;
case SETOP_INTERSECT:
appendStringInfo(buf, " INTERSECT ");
break;
case SETOP_EXCEPT:
appendStringInfo(buf, " EXCEPT ");
break;
default:
elog(ERROR, "get_setop_query: unexpected set op %d",
(int) op->op);
}
if (op->all)
appendStringInfo(buf, "ALL ");
get_setop_query(op->rarg, query, context, false);
if (!toplevel)
appendStringInfo(buf, ")");
}
else
{
elog(ERROR, "get_setop_query: unexpected node %d",
(int) nodeTag(setOp));
}
}
/*
* Display a sort/group clause.
*/
static void
get_rule_sortgroupclause(SortClause *srt, List *tlist, bool force_colno,
deparse_context *context)
{
StringInfo buf = context->buf;
TargetEntry *tle;
Node *expr;
tle = get_sortgroupclause_tle(srt, tlist);
expr = tle->expr;
/*
* Use column-number form if requested by caller or if expression is a
* constant --- a constant is ambiguous (and will be misinterpreted by
* findTargetlistEntry()) if we dump it explicitly.
*/
if (force_colno || (expr && IsA(expr, Const)))
{
Assert(!tle->resdom->resjunk);
appendStringInfo(buf, "%d", tle->resdom->resno);
}
else
get_rule_expr(expr, context);
}
/* ----------
* get_insert_query_def - Parse back an INSERT parsetree
* ----------
*/
static void
get_insert_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
RangeTblEntry *select_rte = NULL;
RangeTblEntry *rte;
char *sep;
List *l;
/*
* If it's an INSERT ... SELECT there will be a single subquery RTE
* for the SELECT.
*/
foreach(l, query->rtable)
{
rte = (RangeTblEntry *) lfirst(l);
if (rte->subquery == NULL)
continue;
if (select_rte)
elog(ERROR, "get_insert_query_def: too many RTEs in INSERT!");
select_rte = rte;
}
/*
* Start the query with INSERT INTO relname
*/
rte = rt_fetch(query->resultRelation, query->rtable);
appendStringInfo(buf, "INSERT INTO %s",
quote_identifier(rte->relname));
/* Add the insert-column-names list */
sep = " (";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resdom->resjunk)
continue; /* ignore junk entries */
appendStringInfo(buf, sep);
sep = ", ";
appendStringInfo(buf, "%s", quote_identifier(tle->resdom->resname));
}
appendStringInfo(buf, ") ");
/* Add the VALUES or the SELECT */
if (select_rte == NULL)
{
appendStringInfo(buf, "VALUES (");
sep = "";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resdom->resjunk)
continue; /* ignore junk entries */
appendStringInfo(buf, sep);
sep = ", ";
get_tle_expr(tle, context);
}
appendStringInfoChar(buf, ')');
}
else
get_query_def(select_rte->subquery, buf, NIL);
}
/* ----------
* get_update_query_def - Parse back an UPDATE parsetree
* ----------
*/
static void
get_update_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
RangeTblEntry *rte;
List *l;
/*
* Start the query with UPDATE relname SET
*/
rte = rt_fetch(query->resultRelation, query->rtable);
appendStringInfo(buf, "UPDATE %s%s SET ",
only_marker(rte),
quote_identifier(rte->relname));
/* Add the comma separated list of 'attname = value' */
sep = "";
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resdom->resjunk)
continue; /* ignore junk entries */
appendStringInfo(buf, sep);
sep = ", ";
/*
* If the update expression is an array assignment, we mustn't put
* out "attname =" here; it will come out of the display of the
* ArrayRef node instead.
*/
if (!tleIsArrayAssign(tle))
appendStringInfo(buf, "%s = ",
quote_identifier(tle->resdom->resname));
get_tle_expr(tle, context);
}
/* Add the FROM clause if needed */
get_from_clause(query, context);
/* Finally add a WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendStringInfo(buf, " WHERE ");
get_rule_expr(query->jointree->quals, context);
}
}
/* ----------
* get_delete_query_def - Parse back a DELETE parsetree
* ----------
*/
static void
get_delete_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
RangeTblEntry *rte;
/*
* Start the query with DELETE FROM relname
*/
rte = rt_fetch(query->resultRelation, query->rtable);
appendStringInfo(buf, "DELETE FROM %s%s",
only_marker(rte),
quote_identifier(rte->relname));
/* Add a WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendStringInfo(buf, " WHERE ");
get_rule_expr(query->jointree->quals, context);
}
}
/* ----------
* get_utility_query_def - Parse back a UTILITY parsetree
* ----------
*/
static void
get_utility_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
{
NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt;
appendStringInfo(buf, "NOTIFY %s", quote_identifier(stmt->relname));
}
else
elog(ERROR, "get_utility_query_def: unexpected statement type");
}
/*
* Get the relation refname and attname for a (possibly nonlocal) Var.
*
* attname will be returned as NULL if the Var represents a whole tuple
* of the relation.
*
* 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 void
get_names_for_var(Var *var, deparse_context *context,
char **refname, char **attname)
{
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;
if (var->varattno == InvalidAttrNumber)
*attname = NULL;
else
*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;
/*
* 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 ...
*/
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;
if (var->varattno == InvalidAttrNumber)
*attname = NULL;
else
*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
* ----------
*/
static void
get_rule_expr(Node *node, deparse_context *context)
{
StringInfo buf = context->buf;
if (node == NULL)
return;
/*
* Each level of get_rule_expr must emit an indivisible term
* (parenthesized if necessary) to ensure result is reparsed into the
* same expression tree.
*
* There might be some work left here to support additional node types.
* Can we ever see Param nodes here?
*/
switch (nodeTag(node))
{
case T_Const:
get_const_expr((Const *) node, context);
break;
case T_Var:
{
Var *var = (Var *) node;
char *refname;
char *attname;
get_names_for_var(var, context, &refname, &attname);
if (context->varprefix || attname == NULL)
{
if (strcmp(refname, "*NEW*") == 0)
appendStringInfo(buf, "new");
else if (strcmp(refname, "*OLD*") == 0)
appendStringInfo(buf, "old");
else
appendStringInfo(buf, "%s",
quote_identifier(refname));
if (attname)
appendStringInfoChar(buf, '.');
}
if (attname)
appendStringInfo(buf, "%s", quote_identifier(attname));
}
break;
case T_Expr:
{
Expr *expr = (Expr *) node;
List *args = expr->args;
/*
* Expr nodes have to be handled a bit detailed
*/
switch (expr->opType)
{
case OP_EXPR:
appendStringInfoChar(buf, '(');
if (length(args) == 2)
{
/* binary operator */
get_rule_expr((Node *) lfirst(args), context);
appendStringInfo(buf, " %s ",
get_opname(((Oper *) expr->oper)->opno));
get_rule_expr((Node *) lsecond(args), context);
}
else
{
/* unary operator --- but which side? */
Oid opno = ((Oper *) expr->oper)->opno;
HeapTuple tp;
Form_pg_operator optup;
tp = SearchSysCache(OPEROID,
ObjectIdGetDatum(opno),
0, 0, 0);
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup for operator %u failed", opno);
optup = (Form_pg_operator) GETSTRUCT(tp);
switch (optup->oprkind)
{
case 'l':
appendStringInfo(buf, "%s ",
get_opname(opno));
get_rule_expr((Node *) lfirst(args),
context);
break;
case 'r':
get_rule_expr((Node *) lfirst(args),
context);
appendStringInfo(buf, " %s",
get_opname(opno));
break;
default:
elog(ERROR, "get_rule_expr: bogus oprkind");
}
ReleaseSysCache(tp);
}
appendStringInfoChar(buf, ')');
break;
case OR_EXPR:
appendStringInfoChar(buf, '(');
get_rule_expr((Node *) lfirst(args), context);
while ((args = lnext(args)) != NIL)
{
appendStringInfo(buf, " OR ");
get_rule_expr((Node *) lfirst(args), context);
}
appendStringInfoChar(buf, ')');
break;
case AND_EXPR:
appendStringInfoChar(buf, '(');
get_rule_expr((Node *) lfirst(args), context);
while ((args = lnext(args)) != NIL)
{
appendStringInfo(buf, " AND ");
get_rule_expr((Node *) lfirst(args), context);
}
appendStringInfoChar(buf, ')');
break;
case NOT_EXPR:
appendStringInfo(buf, "(NOT ");
get_rule_expr((Node *) lfirst(args), context);
appendStringInfoChar(buf, ')');
break;
case FUNC_EXPR:
get_func_expr((Expr *) node, context);
break;
default:
elog(ERROR, "get_rule_expr: expr opType %d not supported",
expr->opType);
}
}
break;
case T_Aggref:
{
Aggref *aggref = (Aggref *) node;
appendStringInfo(buf, "%s(%s",
quote_identifier(aggref->aggname),
aggref->aggdistinct ? "DISTINCT " : "");
if (aggref->aggstar)
appendStringInfo(buf, "*");
else
get_rule_expr(aggref->target, context);
appendStringInfoChar(buf, ')');
}
break;
case T_Iter:
get_rule_expr(((Iter *) node)->iterexpr, context);
break;
case T_ArrayRef:
{
ArrayRef *aref = (ArrayRef *) node;
bool savevarprefix = context->varprefix;
List *lowlist;
List *uplist;
/*
* If we are doing UPDATE array[n] = expr, we need to
* suppress any prefix on the array name. Currently, that
* is the only context in which we will see a non-null
* refassgnexpr --- but someday a smarter test may be
* needed.
*/
if (aref->refassgnexpr)
context->varprefix = false;
get_rule_expr(aref->refexpr, context);
context->varprefix = savevarprefix;
lowlist = aref->reflowerindexpr;
foreach(uplist, aref->refupperindexpr)
{
appendStringInfo(buf, "[");
if (lowlist)
{
get_rule_expr((Node *) lfirst(lowlist), context);
appendStringInfo(buf, ":");
lowlist = lnext(lowlist);
}
get_rule_expr((Node *) lfirst(uplist), context);
appendStringInfo(buf, "]");
}
if (aref->refassgnexpr)
{
appendStringInfo(buf, " = ");
get_rule_expr(aref->refassgnexpr, context);
}
}
break;
case T_FieldSelect:
{
FieldSelect *fselect = (FieldSelect *) node;
HeapTuple typetup;
Form_pg_type typeStruct;
Oid typrelid;
char *fieldname;
/* we do NOT parenthesize the arg expression, for now */
get_rule_expr(fselect->arg, context);
typetup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(exprType(fselect->arg)),
0, 0, 0);
if (!HeapTupleIsValid(typetup))
elog(ERROR, "cache lookup of type %u failed",
exprType(fselect->arg));
typeStruct = (Form_pg_type) GETSTRUCT(typetup);
typrelid = typeStruct->typrelid;
if (!OidIsValid(typrelid))
elog(ERROR, "Argument type %s of FieldSelect is not a tuple type",
NameStr(typeStruct->typname));
fieldname = get_relid_attribute_name(typrelid,
fselect->fieldnum);
appendStringInfo(buf, ".%s", quote_identifier(fieldname));
ReleaseSysCache(typetup);
}
break;
case T_RelabelType:
{
RelabelType *relabel = (RelabelType *) node;
HeapTuple typetup;
Form_pg_type typeStruct;
char *extval;
appendStringInfoChar(buf, '(');
get_rule_expr(relabel->arg, context);
typetup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(relabel->resulttype),
0, 0, 0);
if (!HeapTupleIsValid(typetup))
elog(ERROR, "cache lookup of type %u failed",
relabel->resulttype);
typeStruct = (Form_pg_type) GETSTRUCT(typetup);
extval = pstrdup(NameStr(typeStruct->typname));
appendStringInfo(buf, ")::%s", quote_identifier(extval));
pfree(extval);
ReleaseSysCache(typetup);
}
break;
case T_CaseExpr:
{
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)
{
CaseWhen *when = (CaseWhen *) lfirst(temp);
appendStringInfo(buf, " WHEN ");
get_rule_expr(when->expr, context);
appendStringInfo(buf, " THEN ");
get_rule_expr(when->result, context);
}
appendStringInfo(buf, " ELSE ");
get_rule_expr(caseexpr->defresult, context);
appendStringInfo(buf, " END");
}
break;
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
appendStringInfo(buf, "(");
get_rule_expr(ntest->arg, context);
switch (ntest->nulltesttype)
{
case IS_NULL:
appendStringInfo(buf, " IS NULL)");
break;
case IS_NOT_NULL:
appendStringInfo(buf, " IS NOT NULL)");
break;
default:
elog(ERROR, "get_rule_expr: unexpected nulltesttype %d",
(int) ntest->nulltesttype);
}
}
break;
case T_BooleanTest:
{
BooleanTest *btest = (BooleanTest *) node;
appendStringInfo(buf, "(");
get_rule_expr(btest->arg, context);
switch (btest->booltesttype)
{
case IS_TRUE:
appendStringInfo(buf, " IS TRUE)");
break;
case IS_NOT_TRUE:
appendStringInfo(buf, " IS NOT TRUE)");
break;
case IS_FALSE:
appendStringInfo(buf, " IS FALSE)");
break;
case IS_NOT_FALSE:
appendStringInfo(buf, " IS NOT FALSE)");
break;
case IS_UNKNOWN:
appendStringInfo(buf, " IS UNKNOWN)");
break;
case IS_NOT_UNKNOWN:
appendStringInfo(buf, " IS NOT UNKNOWN)");
break;
default:
elog(ERROR, "get_rule_expr: unexpected booltesttype %d",
(int) btest->booltesttype);
}
}
break;
case T_SubLink:
get_sublink_expr(node, context);
break;
default:
printf("\n%s\n", nodeToString(node));
elog(ERROR, "get_ruledef of %s: unknown node type %d in get_rule_expr()",
rulename, nodeTag(node));
break;
}
}
/* ----------
* get_func_expr - Parse back a Func node
* ----------
*/
static void
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;
int32 coercedTypmod;
List *l;
char *sep;
/*
* Get the functions pg_proc tuple
*/
proctup = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcoid),
0, 0, 0);
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup for proc %u failed", funcoid);
procStruct = (Form_pg_proc) GETSTRUCT(proctup);
proname = NameStr(procStruct->proname);
/*
* Check to see if function is a length-coercion function for some
* datatype. If so, display the operation as a type cast.
*/
if (exprIsLengthCoercion((Node *) expr, &coercedTypmod))
{
Node *arg = lfirst(expr->args);
char *typdesc;
/*
* Strip off any type coercions on the input, so we don't print
* redundancies like x::bpchar::character(8).
*
* XXX Are there any cases where this is a bad idea?
*/
arg = strip_type_coercion(arg, procStruct->prorettype);
appendStringInfoChar(buf, '(');
get_rule_expr(arg, context);
/*
* Show typename with appropriate length decoration. Note that
* since exprIsLengthCoercion succeeded, the function's output
* type is the right thing to use.
*
* XXX In general it is incorrect to quote the result of
* format_type_with_typemod, but are there any special cases where
* we should do so?
*/
typdesc = format_type_with_typemod(procStruct->prorettype,
coercedTypmod);
appendStringInfo(buf, ")::%s", typdesc);
pfree(typdesc);
ReleaseSysCache(proctup);
return;
}
/*
* Normal function: display as proname(args)
*/
appendStringInfo(buf, "%s(", quote_identifier(proname));
sep = "";
foreach(l, expr->args)
{
appendStringInfo(buf, sep);
sep = ", ";
get_rule_expr((Node *) lfirst(l), context);
}
appendStringInfoChar(buf, ')');
ReleaseSysCache(proctup);
}
/*
* strip_type_coercion
* Strip any type coercions at the top of the given expression tree,
* as long as they are coercions to the given datatype.
*
* A RelabelType node is always a type coercion. A function call is also
* considered a type coercion if it has one argument and the function name
* is the same as the (internal) name of its result type.
*
* XXX It'd be better if the parsetree retained some explicit indication
* of the coercion, so we didn't need these heuristics.
*/
static Node *
strip_type_coercion(Node *expr, Oid resultType)
{
if (expr == NULL || exprType(expr) != resultType)
return expr;
if (IsA(expr, RelabelType))
return strip_type_coercion(((RelabelType *) expr)->arg, resultType);
if (IsA(expr, Expr) &&((Expr *) expr)->opType == FUNC_EXPR)
{
Func *func;
HeapTuple procTuple;
HeapTuple typeTuple;
Form_pg_proc procStruct;
Form_pg_type typeStruct;
func = (Func *) (((Expr *) expr)->oper);
Assert(IsA(func, Func));
if (length(((Expr *) expr)->args) != 1)
return expr;
/* Lookup the function in pg_proc */
procTuple = SearchSysCache(PROCOID,
ObjectIdGetDatum(func->funcid),
0, 0, 0);
if (!HeapTupleIsValid(procTuple))
elog(ERROR, "cache lookup for proc %u failed", func->funcid);
procStruct = (Form_pg_proc) GETSTRUCT(procTuple);
/* Double-check func has one arg and correct result type */
if (procStruct->pronargs != 1 ||
procStruct->prorettype != resultType)
{
ReleaseSysCache(procTuple);
return expr;
}
/* See if function has same name as its result type */
typeTuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(procStruct->prorettype),
0, 0, 0);
if (!HeapTupleIsValid(typeTuple))
elog(ERROR, "cache lookup for type %u failed",
procStruct->prorettype);
typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
if (strncmp(NameStr(procStruct->proname),
NameStr(typeStruct->typname),
NAMEDATALEN) != 0)
{
ReleaseSysCache(procTuple);
ReleaseSysCache(typeTuple);
return expr;
}
/* Okay, it is indeed a type-coercion function */
ReleaseSysCache(procTuple);
ReleaseSysCache(typeTuple);
return strip_type_coercion(lfirst(((Expr *) expr)->args), resultType);
}
return expr;
}
/* ----------
* get_tle_expr
*
* In an INSERT or UPDATE targetlist item, the parser may have inserted
* a length-coercion function call to coerce the value to the right
* length for the target column. We want to suppress the output of
* that function call, otherwise dump/reload/dump... would blow up the
* expression by adding more and more layers of length-coercion calls.
*
* As of 7.0, this hack is no longer absolutely essential, because the parser
* is now smart enough not to add a redundant length coercion function call.
* But we still suppress the function call just for neatness of displayed
* rules.
*
* Note that this hack must NOT be applied to SELECT targetlist items;
* any length coercion appearing there is something the user actually wrote.
* ----------
*/
static void
get_tle_expr(TargetEntry *tle, deparse_context *context)
{
Expr *expr = (Expr *) (tle->expr);
int32 coercedTypmod;
/*
* If top level is a length coercion to the correct length, suppress
* it; else dump the expression normally.
*/
if (tle->resdom->restypmod >= 0 &&
exprIsLengthCoercion((Node *) expr, &coercedTypmod) &&
coercedTypmod == tle->resdom->restypmod)
get_rule_expr((Node *) lfirst(expr->args), context);
else
get_rule_expr(tle->expr, context);
}
/* ----------
* get_const_expr
*
* Make a string representation of a Const
* ----------
*/
static void
get_const_expr(Const *constval, deparse_context *context)
{
StringInfo buf = context->buf;
HeapTuple typetup;
Form_pg_type typeStruct;
char *extval;
char *valptr;
typetup = SearchSysCache(TYPEOID,
ObjectIdGetDatum(constval->consttype),
0, 0, 0);
if (!HeapTupleIsValid(typetup))
elog(ERROR, "cache lookup of type %u failed", constval->consttype);
typeStruct = (Form_pg_type) GETSTRUCT(typetup);
if (constval->constisnull)
{
/*
* Always label the type of a NULL constant. This not only
* prevents misdecisions about the type, but it ensures that our
* output is a valid b_expr.
*/
extval = pstrdup(NameStr(typeStruct->typname));
appendStringInfo(buf, "NULL::%s", quote_identifier(extval));
pfree(extval);
ReleaseSysCache(typetup);
return;
}
extval = DatumGetCString(OidFunctionCall3(typeStruct->typoutput,
constval->constvalue,
ObjectIdGetDatum(typeStruct->typelem),
Int32GetDatum(-1)));
switch (constval->consttype)
{
case INT2OID:
case INT4OID:
case OIDOID: /* int types */
case FLOAT4OID:
case FLOAT8OID: /* float types */
/* These types are printed without quotes */
appendStringInfo(buf, extval);
break;
default:
/*
* We must quote any funny characters in the constant's
* representation. XXX Any MULTIBYTE considerations here?
*/
appendStringInfoChar(buf, '\'');
for (valptr = extval; *valptr; valptr++)
{
char ch = *valptr;
if (ch == '\'' || ch == '\\')
{
appendStringInfoChar(buf, '\\');
appendStringInfoChar(buf, ch);
}
else if (((unsigned char) ch) < ((unsigned char) ' '))
appendStringInfo(buf, "\\%03o", (int) ch);
else
appendStringInfoChar(buf, ch);
}
appendStringInfoChar(buf, '\'');
break;
}
pfree(extval);
switch (constval->consttype)
{
case INT4OID:
case FLOAT8OID:
case UNKNOWNOID:
/* These types can be left unlabeled */
break;
default:
extval = pstrdup(NameStr(typeStruct->typname));
appendStringInfo(buf, "::%s", quote_identifier(extval));
pfree(extval);
break;
}
ReleaseSysCache(typetup);
}
/* ----------
* get_sublink_expr - Parse back a sublink
* ----------
*/
static void
get_sublink_expr(Node *node, deparse_context *context)
{
StringInfo buf = context->buf;
SubLink *sublink = (SubLink *) node;
Query *query = (Query *) (sublink->subselect);
List *l;
char *sep;
Oper *oper;
bool need_paren;
appendStringInfoChar(buf, '(');
if (sublink->lefthand != NIL)
{
need_paren = (length(sublink->lefthand) > 1);
if (need_paren)
appendStringInfoChar(buf, '(');
sep = "";
foreach(l, sublink->lefthand)
{
appendStringInfo(buf, sep);
sep = ", ";
get_rule_expr((Node *) lfirst(l), context);
}
if (need_paren)
appendStringInfo(buf, ") ");
else
appendStringInfoChar(buf, ' ');
}
need_paren = true;
switch (sublink->subLinkType)
{
case EXISTS_SUBLINK:
appendStringInfo(buf, "EXISTS ");
break;
case ANY_SUBLINK:
oper = (Oper *) lfirst(sublink->oper);
appendStringInfo(buf, "%s ANY ", get_opname(oper->opno));
break;
case ALL_SUBLINK:
oper = (Oper *) lfirst(sublink->oper);
appendStringInfo(buf, "%s ALL ", get_opname(oper->opno));
break;
case MULTIEXPR_SUBLINK:
oper = (Oper *) lfirst(sublink->oper);
appendStringInfo(buf, "%s ", get_opname(oper->opno));
break;
case EXPR_SUBLINK:
need_paren = false;
break;
default:
elog(ERROR, "get_sublink_expr: unsupported sublink type %d",
sublink->subLinkType);
break;
}
if (need_paren)
appendStringInfoChar(buf, '(');
get_query_def(query, buf, context->namespaces);
if (need_paren)
appendStringInfo(buf, "))");
else
appendStringInfoChar(buf, ')');
}
/* ----------
* get_from_clause - Parse back a FROM clause
* ----------
*/
static void
get_from_clause(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
char *sep;
List *l;
/*
* We use the query's jointree as a guide to what to print. However,
* we must ignore auto-added RTEs that are marked not inFromCl. (These
* can only appear at the top level of the jointree, so it's
* sufficient to check here.) Also ignore the rule pseudo-RTEs for NEW
* and OLD.
*/
sep = " FROM ";
foreach(l, query->jointree->fromlist)
{
Node *jtnode = (Node *) lfirst(l);
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
if (!rte->inFromCl)
continue;
if (strcmp(rte->eref->relname, "*NEW*") == 0)
continue;
if (strcmp(rte->eref->relname, "*OLD*") == 0)
continue;
}
appendStringInfo(buf, sep);
get_from_clause_item(jtnode, query, context);
sep = ", ";
}
}
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))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
if (rte->relname)
{
/* Normal relation RTE */
appendStringInfo(buf, "%s%s",
only_marker(rte),
quote_identifier(rte->relname));
}
else
{
/* Subquery RTE */
Assert(rte->subquery != NULL);
appendStringInfoChar(buf, '(');
get_query_def(rte->subquery, buf, context->namespaces);
appendStringInfoChar(buf, ')');
}
if (rte->alias != NULL)
{
appendStringInfo(buf, " %s",
quote_identifier(rte->alias->relname));
if (rte->alias->attrs != NIL)
{
List *col;
appendStringInfo(buf, "(");
foreach(col, rte->alias->attrs)
{
if (col != rte->alias->attrs)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
}
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
appendStringInfoChar(buf, '(');
get_from_clause_item(j->larg, query, context);
if (j->isNatural)
appendStringInfo(buf, " NATURAL");
switch (j->jointype)
{
case JOIN_INNER:
if (j->quals)
appendStringInfo(buf, " JOIN ");
else
appendStringInfo(buf, " CROSS JOIN ");
break;
case JOIN_LEFT:
appendStringInfo(buf, " LEFT JOIN ");
break;
case JOIN_FULL:
appendStringInfo(buf, " FULL JOIN ");
break;
case JOIN_RIGHT:
appendStringInfo(buf, " RIGHT JOIN ");
break;
case JOIN_UNION:
appendStringInfo(buf, " UNION JOIN ");
break;
default:
elog(ERROR, "get_from_clause_item: unknown join type %d",
(int) j->jointype);
}
get_from_clause_item(j->rarg, query, context);
if (!j->isNatural)
{
if (j->using)
{
List *col;
appendStringInfo(buf, " USING (");
foreach(col, j->using)
{
if (col != j->using)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
else if (j->quals)
{
dpns->namespace = makeList2(j->larg, j->rarg);
appendStringInfo(buf, " ON (");
get_rule_expr(j->quals, context);
appendStringInfoChar(buf, ')');
}
}
appendStringInfoChar(buf, ')');
/* Yes, it's correct to put alias after the right paren ... */
if (j->alias != NULL)
{
appendStringInfo(buf, " %s",
quote_identifier(j->alias->relname));
if (j->alias->attrs != NIL)
{
List *col;
appendStringInfo(buf, "(");
foreach(col, j->alias->attrs)
{
if (col != j->alias->attrs)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s",
quote_identifier(strVal(lfirst(col))));
}
appendStringInfoChar(buf, ')');
}
}
}
else
elog(ERROR, "get_from_clause_item: unexpected node type %d",
nodeTag(jtnode));
dpns->namespace = sv_namespace;
}
/* ----------
* get_opclass_name - fetch name of an index operator class
*
* The opclass name is appended (after a space) to buf.
*
* Output is suppressed if the opclass is the default for the given
* actual_datatype. (If you don't want this behavior, just pass
* InvalidOid for actual_datatype.)
* ----------
*/
static void
get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf)
{
HeapTuple ht_opc;
Form_pg_opclass opcrec;
ht_opc = SearchSysCache(CLAOID,
ObjectIdGetDatum(opclass),
0, 0, 0);
if (!HeapTupleIsValid(ht_opc))
elog(ERROR, "cache lookup failed for opclass %u", opclass);
opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
if (actual_datatype != opcrec->opcintype || !opcrec->opcdefault)
appendStringInfo(buf, " %s",
quote_identifier(NameStr(opcrec->opcname)));
ReleaseSysCache(ht_opc);
}
/* ----------
* tleIsArrayAssign - check for array assignment
* ----------
*/
static bool
tleIsArrayAssign(TargetEntry *tle)
{
ArrayRef *aref;
if (tle->expr == NULL || !IsA(tle->expr, ArrayRef))
return false;
aref = (ArrayRef *) tle->expr;
if (aref->refassgnexpr == NULL)
return false;
/*
* Currently, it should only be possible to see non-null refassgnexpr
* if we are indeed looking at an "UPDATE array[n] = expr" situation.
* So aref->refexpr ought to match the tle's target.
*/
if (aref->refexpr == NULL || !IsA(aref->refexpr, Var) ||
((Var *) aref->refexpr)->varattno != tle->resdom->resno)
elog(NOTICE, "tleIsArrayAssign: I'm confused ...");
return true;
}
/* ----------
* quote_identifier - Quote an identifier only if needed
*
* When quotes are needed, we palloc the required space; slightly
* space-wasteful but well worth it for notational simplicity.
* ----------
*/
static char *
quote_identifier(char *ident)
{
/*
* Can avoid quoting if ident starts with a lowercase letter and
* contains only lowercase letters, digits, and underscores, *and* is
* not any SQL keyword. Otherwise, supply quotes.
*/
bool safe;
char *result;
/*
* would like to use <ctype.h> macros here, but they might yield
* unwanted locale-specific results...
*/
safe = (ident[0] >= 'a' && ident[0] <= 'z');
if (safe)
{
char *ptr;
for (ptr = ident + 1; *ptr; ptr++)
{
char ch = *ptr;
safe = ((ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
(ch == '_'));
if (!safe)
break;
}
}
if (safe)
{
/*
* Check for keyword. This test is overly strong, since many of
* the "keywords" known to the parser are usable as column names,
* but the parser doesn't provide any easy way to test for whether
* an identifier is safe or not... so be safe not sorry.
*
* Note: ScanKeywordLookup() does case-insensitive comparison, but
* that's fine, since we already know we have all-lower-case.
*/
if (ScanKeywordLookup(ident) != NULL)
safe = false;
}
if (safe)
return ident; /* no change needed */
result = (char *) palloc(strlen(ident) + 2 + 1);
sprintf(result, "\"%s\"", ident);
return result;
}
/* ----------
* get_relation_name - Get a relation name by Oid
* ----------
*/
static char *
get_relation_name(Oid relid)
{
HeapTuple classtup;
Form_pg_class classStruct;
char *result;
classtup = SearchSysCache(RELOID,
ObjectIdGetDatum(relid),
0, 0, 0);
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup of relation %u failed", relid);
classStruct = (Form_pg_class) GETSTRUCT(classtup);
result = pstrdup(NameStr(classStruct->relname));
ReleaseSysCache(classtup);
return result;
}
/* ----------
* get_relid_attribute_name
* Get an attribute name by its relations Oid and its attnum
*
* Same as underlying syscache routine get_attname(), except that error
* is handled by elog() instead of returning NULL.
* ----------
*/
static char *
get_relid_attribute_name(Oid relid, AttrNumber attnum)
{
char *attname;
attname = get_attname(relid, attnum);
if (attname == NULL)
elog(ERROR, "cache lookup of attribute %d in relation %u failed",
attnum, relid);
return attname;
}