postgres/src/backend/utils/adt/ruleutils.c
Tom Lane 958600156c Fix several problems in rule deparsing: didn't handle array
references or CASE expressions, didn't parenthesize complex expressions
properly.  Also, always output variable references as fully qualified
names to eliminate ambiguity bug recently reported.  (This could be
smarter, but reliability comes first.)
1999-08-28 03:59:05 +00:00

1789 lines
42 KiB
C

/**********************************************************************
* get_ruledef.c - Function to get a rules definition text
* out of it's tuple
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.24 1999/08/28 03:59:05 tgl 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 <unistd.h>
#include <fcntl.h>
#include "postgres.h"
#include "executor/spi.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
#include "utils/lsyscache.h"
#include "catalog/pg_shadow.h"
#include "catalog/pg_index.h"
#define BUFSIZE 8192
/* ----------
* Local data types
* ----------
*/
typedef struct QryHier
{
struct QryHier *parent;
Query *query;
} QryHier;
typedef struct {
Index rt_index;
int levelsup;
} check_if_rte_used_context;
/* ----------
* Global data
* ----------
*/
static char *rulename;
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 or rulename = $2";
static void *plan_getam = NULL;
static char *query_getam = "SELECT * FROM pg_am WHERE oid = $1";
static void *plan_getopclass = NULL;
static char *query_getopclass = "SELECT * FROM pg_opclass WHERE oid = $1";
/* ----------
* Global functions
* ----------
*/
text *pg_get_ruledef(NameData *rname);
text *pg_get_viewdef(NameData *rname);
text *pg_get_indexdef(Oid indexrelid);
NameData *pg_get_userbyid(int4 uid);
/* ----------
* Local functions
* ----------
*/
static char *make_ruledef(HeapTuple ruletup, TupleDesc rulettc);
static char *make_viewdef(HeapTuple ruletup, TupleDesc rulettc);
static char *get_query_def(Query *query, QryHier *parentqh);
static char *get_select_query_def(Query *query, QryHier *qh);
static char *get_insert_query_def(Query *query, QryHier *qh);
static char *get_update_query_def(Query *query, QryHier *qh);
static char *get_delete_query_def(Query *query, QryHier *qh);
static RangeTblEntry *get_rte_for_var(Var *var, QryHier *qh);
static char *get_rule_expr(QryHier *qh, int rt_index, Node *node, bool varprefix);
static char *get_func_expr(QryHier *qh, int rt_index, Expr *expr, bool varprefix);
static char *get_tle_expr(QryHier *qh, int rt_index, TargetEntry *tle, bool varprefix);
static char *get_const_expr(Const *constval);
static char *get_sublink_expr(QryHier *qh, int rt_index, Node *node, bool varprefix);
static char *get_relation_name(Oid relid);
static char *get_attribute_name(Oid relid, int2 attnum);
static bool check_if_rte_used(Node *node, Index rt_index, int levelsup);
static bool check_if_rte_used_walker(Node *node,
check_if_rte_used_context *context);
/* ----------
* get_ruledef - Do it all and return a text
* that could be used as a statement
* to recreate the rule
* ----------
*/
text *
pg_get_ruledef(NameData *rname)
{
text *ruledef;
Datum args[1];
char nulls[2];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
char *tmp;
int len;
/* ----------
* We need the rules name somewhere deep down
* ----------
*/
rulename = nameout(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 = SPI_palloc(VARHDRSZ + 1);
VARSIZE(ruledef) = VARHDRSZ + 1;
VARDATA(ruledef)[0] = '-';
return ruledef;
}
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
/* ----------
* Get the rules definition and put it into executors memory
* ----------
*/
tmp = make_ruledef(ruletup, rulettc);
len = strlen(tmp) + VARHDRSZ;
ruledef = SPI_palloc(len);
VARSIZE(ruledef) = len;
memcpy(VARDATA(ruledef), tmp, len - VARHDRSZ);
/* ----------
* Disconnect from SPI manager
* ----------
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "get_ruledef: SPI_finish() failed");
/* ----------
* Easy - isn't it?
* ----------
*/
return ruledef;
}
/* ----------
* get_viewdef - Mainly the same thing, but we
* only return the SELECT part of a view
* ----------
*/
text *
pg_get_viewdef(NameData *rname)
{
text *ruledef;
Datum args[2];
char nulls[3];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
char *tmp;
int len;
char name1[NAMEDATALEN + 5];
char name2[NAMEDATALEN + 5];
/* ----------
* We need the rules name somewhere deep down
* ----------
*/
rulename = nameout(rname);
/* ----------
* 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[2];
void *plan;
argtypes[0] = NAMEOID;
argtypes[1] = NAMEOID;
plan = SPI_prepare(query_getview, 2, 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
* ----------
*/
sprintf(name1, "_RET%s", rulename);
sprintf(name2, "_ret%s", rulename);
args[0] = PointerGetDatum(name1);
args[1] = PointerGetDatum(name2);
nulls[0] = ' ';
nulls[1] = ' ';
nulls[2] = '\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);
if (SPI_processed != 1)
tmp = "Not a view";
else
{
/* ----------
* Get the rules definition and put it into executors memory
* ----------
*/
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
tmp = make_viewdef(ruletup, rulettc);
}
len = strlen(tmp) + VARHDRSZ;
ruledef = SPI_palloc(len);
VARSIZE(ruledef) = len;
memcpy(VARDATA(ruledef), tmp, len - VARHDRSZ);
/* ----------
* Disconnect from SPI manager
* ----------
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "get_viewdef: SPI_finish() failed");
/* ----------
* Easy - isn't it?
* ----------
*/
return ruledef;
}
/* ----------
* get_viewdef - Mainly the same thing, but we
* only return the SELECT part of a view
* ----------
*/
text *
pg_get_indexdef(Oid indexrelid)
{
text *indexdef;
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_indrel;
HeapTuple spi_tup;
TupleDesc spi_ttc;
int spi_fno;
Form_pg_index idxrec;
Form_pg_class idxrelrec;
Form_pg_class indrelrec;
Datum spi_args[1];
char spi_nulls[2];
int spirc;
int len;
int keyno;
char buf[BUFSIZE];
char keybuf[BUFSIZE];
char *sep;
/* ----------
* Connect to SPI manager
* ----------
*/
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "get_indexdef: cannot connect to SPI manager");
/* ----------
* On the first call prepare the plans to lookup pg_am
* and pg_opclass.
* ----------
*/
if (plan_getam == NULL)
{
Oid argtypes[1];
void *plan;
argtypes[0] = OIDOID;
plan = SPI_prepare(query_getam, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getam);
plan_getam = SPI_saveplan(plan);
argtypes[0] = OIDOID;
plan = SPI_prepare(query_getopclass, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getopclass);
plan_getopclass = SPI_saveplan(plan);
}
/* ----------
* Fetch the pg_index tuple by the Oid of the index
* ----------
*/
ht_idx = SearchSysCacheTuple(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 = SearchSysCacheTuple(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 = SearchSysCacheTuple(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);
/* ----------
* Get the am name for the index relation
* ----------
*/
spi_args[0] = ObjectIdGetDatum(idxrelrec->relam);
spi_nulls[0] = ' ';
spi_nulls[1] = '\0';
spirc = SPI_execp(plan_getam, spi_args, spi_nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_am tuple for index %s", nameout(&(idxrelrec->relname)));
if (SPI_processed != 1)
elog(ERROR, "failed to get pg_am tuple for index %s", nameout(&(idxrelrec->relname)));
spi_tup = SPI_tuptable->vals[0];
spi_ttc = SPI_tuptable->tupdesc;
spi_fno = SPI_fnumber(spi_ttc, "amname");
/* ----------
* Start the index definition
* ----------
*/
sprintf(buf, "CREATE %sINDEX \"%s\" ON \"%s\" USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
nameout(&(idxrelrec->relname)),
nameout(&(indrelrec->relname)),
SPI_getvalue(spi_tup, spi_ttc, spi_fno));
/* ----------
* Collect the indexed attributes
* ----------
*/
sep = "";
keybuf[0] = '\0';
for (keyno = 0; keyno < INDEX_MAX_KEYS; keyno++)
{
if (idxrec->indkey[keyno] == InvalidAttrNumber)
break;
strcat(keybuf, sep);
sep = ", ";
/* ----------
* Add the indexed field name
* ----------
*/
strcat(keybuf, "\"");
if (idxrec->indkey[keyno] == ObjectIdAttributeNumber - 1)
strcat(keybuf, "oid");
else
strcat(keybuf, get_attribute_name(idxrec->indrelid,
idxrec->indkey[keyno]));
strcat(keybuf, "\"");
/* ----------
* If not a functional index, add the operator class name
* ----------
*/
if (idxrec->indproc == InvalidOid)
{
spi_args[0] = ObjectIdGetDatum(idxrec->indclass[keyno]);
spi_nulls[0] = ' ';
spi_nulls[1] = '\0';
spirc = SPI_execp(plan_getopclass, spi_args, spi_nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[keyno]);
if (SPI_processed != 1)
elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[keyno]);
spi_tup = SPI_tuptable->vals[0];
spi_ttc = SPI_tuptable->tupdesc;
spi_fno = SPI_fnumber(spi_ttc, "opcname");
strcat(keybuf, " \"");
strcat(keybuf, SPI_getvalue(spi_tup, spi_ttc, spi_fno));
strcat(keybuf, "\"");
}
}
/* ----------
* For functional index say 'func (attrs) opclass'
* ----------
*/
if (idxrec->indproc != InvalidOid)
{
HeapTuple proctup;
Form_pg_proc procStruct;
proctup = SearchSysCacheTuple(PROOID,
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);
strcat(buf, "\"");
strcat(buf, nameout(&(procStruct->proname)));
strcat(buf, "\" (");
strcat(buf, keybuf);
strcat(buf, ") ");
spi_args[0] = ObjectIdGetDatum(idxrec->indclass[0]);
spi_nulls[0] = ' ';
spi_nulls[1] = '\0';
spirc = SPI_execp(plan_getopclass, spi_args, spi_nulls, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[0]);
if (SPI_processed != 1)
elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[0]);
spi_tup = SPI_tuptable->vals[0];
spi_ttc = SPI_tuptable->tupdesc;
spi_fno = SPI_fnumber(spi_ttc, "opcname");
strcat(buf, "\"");
strcat(buf, SPI_getvalue(spi_tup, spi_ttc, spi_fno));
strcat(buf, "\"");
}
else
/* ----------
* For the others say 'attr opclass [, ...]'
* ----------
*/
strcat(buf, keybuf);
/* ----------
* Finish
* ----------
*/
strcat(buf, ")");
/* ----------
* Create the result in upper executor memory
* ----------
*/
len = strlen(buf) + VARHDRSZ;
indexdef = SPI_palloc(len);
VARSIZE(indexdef) = len;
memcpy(VARDATA(indexdef), buf, len - VARHDRSZ);
/* ----------
* Disconnect from SPI manager
* ----------
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "get_viewdef: SPI_finish() failed");
return indexdef;
}
/* ----------
* get_userbyid - Get a user name by usesysid and
* fallback to 'unknown (UID=n)'
* ----------
*/
NameData *
pg_get_userbyid(int4 uid)
{
HeapTuple usertup;
Form_pg_shadow user_rec;
NameData *result;
/* ----------
* Allocate space for the result
* ----------
*/
result = (NameData *) palloc(NAMEDATALEN);
memset(result->data, 0, NAMEDATALEN);
/* ----------
* Get the pg_shadow entry and print the result
* ----------
*/
usertup = SearchSysCacheTuple(USESYSID,
ObjectIdGetDatum(uid), 0, 0, 0);
if (HeapTupleIsValid(usertup))
{
user_rec = (Form_pg_shadow) GETSTRUCT(usertup);
StrNCpy(result->data, (&(user_rec->usename))->data, NAMEDATALEN);
}
else
sprintf((char *) result, "unknown (UID=%d)", uid);
return result;
}
/* ----------
* make_ruledef - reconstruct the CREATE RULE command
* for a given pg_rewrite tuple
* ----------
*/
static char *
make_ruledef(HeapTuple ruletup, TupleDesc rulettc)
{
char *buf;
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;
/* ----------
* Allocate space for the returned rule definition text
* ----------
*/
buf = palloc(BUFSIZE);
/* ----------
* 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
* ----------
*/
strcpy(buf, "CREATE RULE \"");
/* The rule name */
strcat(buf, rulename);
strcat(buf, "\" AS ON ");
/* The event the rule is fired for */
switch (ev_type)
{
case '1':
strcat(buf, "SELECT TO \"");
break;
case '2':
strcat(buf, "UPDATE TO \"");
break;
case '3':
strcat(buf, "INSERT TO \"");
break;
case '4':
strcat(buf, "DELETE TO \"");
break;
default:
elog(ERROR, "get_ruledef: rule %s has unsupported event type %d",
rulename, ev_type);
break;
}
/* The relation the rule is fired on */
strcat(buf, get_relation_name(ev_class));
strcat(buf, "\"");
if (ev_attr > 0)
{
strcat(buf, ".\"");
strcat(buf, get_attribute_name(ev_class, ev_attr));
strcat(buf, "\"");
}
/* 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;
QryHier qh;
qual = stringToNode(ev_qual);
query = (Query *) lfirst(actions);
qh.parent = NULL;
qh.query = query;
strcat(buf, " WHERE ");
strcat(buf, get_rule_expr(&qh, 0, qual, TRUE));
}
strcat(buf, " DO ");
/* The INSTEAD keyword (if so) */
if (is_instead)
strcat(buf, "INSTEAD ");
/* Finally the rules actions */
if (length(actions) > 1)
{
List *action;
Query *query;
strcat(buf, "(");
foreach(action, actions)
{
query = (Query *) lfirst(action);
strcat(buf, get_query_def(query, NULL));
strcat(buf, "; ");
}
strcat(buf, ");");
}
else
{
if (length(actions) == 0)
{
strcat(buf, "NOTHING;");
}
else
{
Query *query;
query = (Query *) lfirst(actions);
strcat(buf, get_query_def(query, NULL));
strcat(buf, ";");
}
}
/* ----------
* That's it
* ----------
*/
return buf;
}
/* ----------
* make_viewdef - reconstruct the SELECT part of a
* view rewrite rule
* ----------
*/
static char *
make_viewdef(HeapTuple ruletup, TupleDesc rulettc)
{
char buf[BUFSIZE];
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)
return "Not a view";
query = (Query *) lfirst(actions);
if (ev_type != '1' || ev_attr >= 0 || !is_instead || strcmp(ev_qual, "<>"))
return "Not a view";
strcpy(buf, get_query_def(query, NULL));
strcat(buf, ";");
/* ----------
* That's it
* ----------
*/
return pstrdup(buf);
}
/* ----------
* get_query_def - Parse back one action from
* the parsetree in the actions
* list
* ----------
*/
static char *
get_query_def(Query *query, QryHier *parentqh)
{
QryHier qh;
qh.parent = parentqh;
qh.query = query;
switch (query->commandType)
{
case CMD_SELECT:
return get_select_query_def(query, &qh);
break;
case CMD_UPDATE:
return get_update_query_def(query, &qh);
break;
case CMD_INSERT:
return get_insert_query_def(query, &qh);
break;
case CMD_DELETE:
return get_delete_query_def(query, &qh);
break;
case CMD_NOTHING:
return "NOTHING";
break;
default:
elog(ERROR, "get_ruledef of %s: query command type %d not implemented yet",
rulename, query->commandType);
break;
}
return NULL;
}
/* ----------
* get_select_query_def - Parse back a SELECT parsetree
* ----------
*/
static char *
get_select_query_def(Query *query, QryHier *qh)
{
char buf[BUFSIZE];
char *sep;
TargetEntry *tle;
RangeTblEntry *rte;
bool *rt_used;
int rt_length;
int rt_numused = 0;
bool rt_constonly = TRUE;
int i;
List *l;
/* ----------
* First we need to know which and how many of the
* range table entries in the query are used in the target list
* or queries qualification
* ----------
*/
rt_length = length(query->rtable);
rt_used = palloc(sizeof(bool) * rt_length);
for (i = 0; i < rt_length; i++)
{
if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
check_if_rte_used(query->qual, i + 1, 0) ||
check_if_rte_used(query->havingQual, i + 1, 0))
{
rt_used[i] = TRUE;
rt_numused++;
}
else
rt_used[i] = FALSE;
}
/* ----------
* Now check if any of the used rangetable entries is different
* from *NEW* and *CURRENT*. If so we must omit the FROM clause
* later.
* ----------
*/
i = 0;
foreach(l, query->rtable)
{
if (!rt_used[i++])
continue;
rte = (RangeTblEntry *) lfirst(l);
if (!strcmp(rte->refname, "*NEW*"))
continue;
if (!strcmp(rte->refname, "*CURRENT*"))
continue;
rt_constonly = FALSE;
break;
}
/* ----------
* Build up the query string - first we say SELECT
* ----------
*/
strcpy(buf, "SELECT");
/* Then we tell what to select (the targetlist) */
sep = " ";
foreach(l, query->targetList)
{
bool tell_as = FALSE;
tle = (TargetEntry *) lfirst(l);
strcat(buf, sep);
sep = ", ";
strcat(buf, get_tle_expr(qh, 0, tle, (rt_numused > 1)));
/* Check if we must say AS ... */
if (! IsA(tle->expr, Var))
tell_as = strcmp(tle->resdom->resname, "?column?");
else
{
Var *var = (Var *) (tle->expr);
char *attname;
rte = get_rte_for_var(var, qh);
attname = get_attribute_name(rte->relid, var->varattno);
if (strcmp(attname, tle->resdom->resname))
tell_as = TRUE;
}
/* and do if so */
if (tell_as)
{
strcat(buf, " AS \"");
strcat(buf, tle->resdom->resname);
strcat(buf, "\"");
}
}
/* If we need other tables that *NEW* or *CURRENT* add the FROM clause */
if (!rt_constonly && rt_numused > 0)
{
strcat(buf, " FROM");
i = 0;
sep = " ";
foreach(l, query->rtable)
{
if (rt_used[i++])
{
rte = (RangeTblEntry *) lfirst(l);
if (!strcmp(rte->refname, "*NEW*"))
continue;
if (!strcmp(rte->refname, "*CURRENT*"))
continue;
strcat(buf, sep);
sep = ", ";
strcat(buf, "\"");
strcat(buf, rte->relname);
strcat(buf, "\"");
if (strcmp(rte->relname, rte->refname) != 0)
{
strcat(buf, " \"");
strcat(buf, rte->refname);
strcat(buf, "\"");
}
}
}
}
/* Add the WHERE clause if given */
if (query->qual != NULL)
{
strcat(buf, " WHERE ");
strcat(buf, get_rule_expr(qh, 0, query->qual, (rt_numused > 1)));
}
/* Add the GROUP BY CLAUSE */
if (query->groupClause != NULL)
{
strcat(buf, " GROUP BY ");
sep = "";
foreach(l, query->groupClause)
{
GroupClause *grp = (GroupClause *) lfirst(l);
Node *groupexpr;
groupexpr = get_sortgroupclause_expr(grp,
query->targetList);
strcat(buf, sep);
strcat(buf, get_rule_expr(qh, 0, groupexpr, (rt_numused > 1)));
sep = ", ";
}
}
/* ----------
* Copy the query string into allocated space and return it
* ----------
*/
return pstrdup(buf);
}
/* ----------
* get_insert_query_def - Parse back an INSERT parsetree
* ----------
*/
static char *
get_insert_query_def(Query *query, QryHier *qh)
{
char buf[BUFSIZE];
char *sep;
TargetEntry *tle;
RangeTblEntry *rte;
bool *rt_used;
int rt_length;
int rt_numused = 0;
bool rt_constonly = TRUE;
int i;
List *l;
/* ----------
* We need to know if other tables than *NEW* or *CURRENT*
* are used in the query. If not, it's an INSERT ... VALUES,
* otherwise an INSERT ... SELECT.
* ----------
*/
rt_length = length(query->rtable);
rt_used = palloc(sizeof(bool) * rt_length);
for (i = 0; i < rt_length; i++)
{
if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
check_if_rte_used(query->qual, i + 1, 0) ||
check_if_rte_used(query->havingQual, i + 1, 0))
{
rt_used[i] = TRUE;
rt_numused++;
}
else
rt_used[i] = FALSE;
}
i = 0;
foreach(l, query->rtable)
{
if (!rt_used[i++])
continue;
rte = (RangeTblEntry *) lfirst(l);
if (!strcmp(rte->refname, "*NEW*"))
continue;
if (!strcmp(rte->refname, "*CURRENT*"))
continue;
rt_constonly = FALSE;
break;
}
/* ----------
* Start the query with INSERT INTO relname
* ----------
*/
rte = (RangeTblEntry *) nth(query->resultRelation - 1, query->rtable);
strcpy(buf, "INSERT INTO \"");
strcat(buf, rte->relname);
strcat(buf, "\"");
/* Add the target list */
sep = " (";
foreach(l, query->targetList)
{
tle = (TargetEntry *) lfirst(l);
strcat(buf, sep);
sep = ", ";
strcat(buf, "\"");
strcat(buf, tle->resdom->resname);
strcat(buf, "\"");
}
strcat(buf, ") ");
/* Add the VALUES or the SELECT */
if (rt_constonly && query->qual == NULL)
{
strcat(buf, "VALUES (");
sep = "";
foreach(l, query->targetList)
{
tle = (TargetEntry *) lfirst(l);
strcat(buf, sep);
sep = ", ";
strcat(buf, get_tle_expr(qh, 0, tle, (rt_numused > 1)));
}
strcat(buf, ")");
}
else
strcat(buf, get_select_query_def(query, qh));
/* ----------
* Copy the query string into allocated space and return it
* ----------
*/
return pstrdup(buf);
}
/* ----------
* get_update_query_def - Parse back an UPDATE parsetree
* ----------
*/
static char *
get_update_query_def(Query *query, QryHier *qh)
{
char buf[BUFSIZE];
char *sep;
TargetEntry *tle;
RangeTblEntry *rte;
List *l;
/* ----------
* Start the query with UPDATE relname SET
* ----------
*/
rte = (RangeTblEntry *) nth(query->resultRelation - 1, query->rtable);
strcpy(buf, "UPDATE ");
strcat(buf, rte->relname);
strcat(buf, " SET ");
/* Add the comma separated list of 'attname = value' */
sep = "";
foreach(l, query->targetList)
{
tle = (TargetEntry *) lfirst(l);
strcat(buf, sep);
sep = ", ";
strcat(buf, "\"");
strcat(buf, tle->resdom->resname);
strcat(buf, "\" = ");
strcat(buf, get_tle_expr(qh, query->resultRelation,
tle, TRUE));
}
/* Finally add a WHERE clause if given */
if (query->qual != NULL)
{
strcat(buf, " WHERE ");
strcat(buf, get_rule_expr(qh, query->resultRelation,
query->qual, TRUE));
}
/* ----------
* Copy the query string into allocated space and return it
* ----------
*/
return pstrdup(buf);
}
/* ----------
* get_delete_query_def - Parse back a DELETE parsetree
* ----------
*/
static char *
get_delete_query_def(Query *query, QryHier *qh)
{
char buf[BUFSIZE];
RangeTblEntry *rte;
/* ----------
* Start the query with DELETE FROM relname
* ----------
*/
rte = (RangeTblEntry *) nth(query->resultRelation - 1, query->rtable);
strcpy(buf, "DELETE FROM \"");
strcat(buf, rte->relname);
strcat(buf, "\"");
/* Add a WHERE clause if given */
if (query->qual != NULL)
{
strcat(buf, " WHERE ");
strcat(buf, get_rule_expr(qh, 0, query->qual, FALSE));
}
/* ----------
* Copy the query string into allocated space and return it
* ----------
*/
return pstrdup(buf);
}
/*
* Find the RTE referenced by a (possibly nonlocal) Var.
*/
static RangeTblEntry *
get_rte_for_var(Var *var, QryHier *qh)
{
int sup = var->varlevelsup;
while (sup-- > 0)
qh = qh->parent;
return (RangeTblEntry *) nth(var->varno - 1, qh->query->rtable);
}
/* ----------
* get_rule_expr - Parse back an expression
* ----------
*/
static char *
get_rule_expr(QryHier *qh, int rt_index, Node *node, bool varprefix)
{
char buf[BUFSIZE];
if (node == NULL)
return pstrdup("");
buf[0] = '\0';
/* ----------
* Each level of get_rule_expr must return 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...
* ----------
*/
switch (nodeTag(node))
{
case T_Const:
return get_const_expr((Const *) node);
break;
case T_Var:
{
Var *var = (Var *) node;
RangeTblEntry *rte = get_rte_for_var(var, qh);
if (!strcmp(rte->refname, "*NEW*"))
strcat(buf, "new.");
else if (!strcmp(rte->refname, "*CURRENT*"))
strcat(buf, "old.");
else
{
strcat(buf, "\"");
strcat(buf, rte->refname);
strcat(buf, "\".");
}
strcat(buf, "\"");
strcat(buf, get_attribute_name(rte->relid, var->varattno));
strcat(buf, "\"");
return pstrdup(buf);
}
break;
case T_Expr:
{
Expr *expr = (Expr *) node;
/* ----------
* Expr nodes have to be handled a bit detailed
* ----------
*/
switch (expr->opType)
{
case OP_EXPR:
strcat(buf, "(");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_leftop(expr),
varprefix));
strcat(buf, " ");
strcat(buf, get_opname(((Oper *) expr->oper)->opno));
strcat(buf, " ");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_rightop(expr),
varprefix));
strcat(buf, ")");
return pstrdup(buf);
break;
case OR_EXPR:
strcat(buf, "(");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_leftop(expr),
varprefix));
strcat(buf, " OR ");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_rightop(expr),
varprefix));
strcat(buf, ")");
return pstrdup(buf);
break;
case AND_EXPR:
strcat(buf, "(");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_leftop(expr),
varprefix));
strcat(buf, " AND ");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_rightop(expr),
varprefix));
strcat(buf, ")");
return pstrdup(buf);
break;
case NOT_EXPR:
strcat(buf, "(NOT ");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) get_leftop(expr),
varprefix));
strcat(buf, ")");
return pstrdup(buf);
break;
case FUNC_EXPR:
return get_func_expr(qh, rt_index,
(Expr *) node,
varprefix);
break;
default:
printf("\n%s\n", nodeToString(node));
elog(ERROR, "Expr type not supported");
}
}
break;
case T_Aggref:
{
Aggref *aggref = (Aggref *) node;
strcat(buf, "\"");
strcat(buf, aggref->aggname);
strcat(buf, "\"(");
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) (aggref->target), varprefix));
strcat(buf, ")");
return pstrdup(buf);
}
break;
case T_ArrayRef:
{
ArrayRef *aref = (ArrayRef *) node;
List *lowlist;
List *uplist;
strcat(buf, get_rule_expr(qh, rt_index,
aref->refexpr, varprefix));
lowlist = aref->reflowerindexpr;
foreach(uplist, aref->refupperindexpr)
{
strcat(buf, "[");
if (lowlist)
{
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) lfirst(lowlist),
varprefix));
strcat(buf, ":");
lowlist = lnext(lowlist);
}
strcat(buf, get_rule_expr(qh, rt_index,
(Node *) lfirst(uplist),
varprefix));
strcat(buf, "]");
}
/* XXX need to do anything with refassgnexpr? */
return pstrdup(buf);
}
break;
case T_CaseExpr:
{
CaseExpr *caseexpr = (CaseExpr *) node;
List *temp;
strcat(buf, "CASE");
foreach(temp, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(temp);
strcat(buf, " WHEN ");
strcat(buf, get_rule_expr(qh, rt_index,
when->expr, varprefix));
strcat(buf, " THEN ");
strcat(buf, get_rule_expr(qh, rt_index,
when->result, varprefix));
}
strcat(buf, " ELSE ");
strcat(buf, get_rule_expr(qh, rt_index,
caseexpr->defresult, varprefix));
strcat(buf, " END");
return pstrdup(buf);
}
break;
case T_SubLink:
return get_sublink_expr(qh, rt_index, node, varprefix);
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;
}
return FALSE;
}
/* ----------
* get_func_expr - Parse back a Func node
* ----------
*/
static char *
get_func_expr(QryHier *qh, int rt_index, Expr *expr, bool varprefix)
{
char buf[BUFSIZE];
HeapTuple proctup;
Form_pg_proc procStruct;
List *l;
char *sep;
Func *func = (Func *) (expr->oper);
char *proname;
/* ----------
* Get the functions pg_proc tuple
* ----------
*/
proctup = SearchSysCacheTuple(PROOID,
ObjectIdGetDatum(func->funcid), 0, 0, 0);
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup for proc %u failed", func->funcid);
procStruct = (Form_pg_proc) GETSTRUCT(proctup);
proname = nameout(&(procStruct->proname));
if (procStruct->pronargs == 1 && procStruct->proargtypes[0] == InvalidOid)
{
if (!strcmp(proname, "nullvalue"))
{
strcpy(buf, "(");
strcat(buf, get_rule_expr(qh, rt_index, lfirst(expr->args),
varprefix));
strcat(buf, " ISNULL)");
return pstrdup(buf);
}
if (!strcmp(proname, "nonnullvalue"))
{
strcpy(buf, "(");
strcat(buf, get_rule_expr(qh, rt_index, lfirst(expr->args),
varprefix));
strcat(buf, " NOTNULL)");
return pstrdup(buf);
}
}
/* ----------
* Build a string of proname(args)
* ----------
*/
strcpy(buf, "\"");
strcat(buf, proname);
strcat(buf, "\"(");
sep = "";
foreach(l, expr->args)
{
strcat(buf, sep);
sep = ", ";
strcat(buf, get_rule_expr(qh, rt_index, lfirst(l), varprefix));
}
strcat(buf, ")");
/* ----------
* Copy the function call string into allocated space and return it
* ----------
*/
return pstrdup(buf);
}
/* ----------
* get_tle_expr - A target list expression is a bit
* different from a normal expression.
* If the target column has an
* an atttypmod, the parser usually
* puts a padding-/cut-function call
* around the expression itself. We
* we must get rid of it, otherwise
* dump/reload/dump... would blow up
* the expressions.
* ----------
*/
static char *
get_tle_expr(QryHier *qh, int rt_index, TargetEntry *tle, bool varprefix)
{
Expr *expr = (Expr *) (tle->expr);
Func *func;
HeapTuple tup;
Form_pg_proc procStruct;
Form_pg_type typeStruct;
Const *second_arg;
/* ----------
* Check if the result has an atttypmod and if the
* expression in the targetlist entry is a function call
* ----------
*/
if (tle->resdom->restypmod < 0 ||
! IsA(expr, Expr) ||
expr->opType != FUNC_EXPR)
return get_rule_expr(qh, rt_index, tle->expr, varprefix);
func = (Func *) (expr->oper);
/* ----------
* Get the functions pg_proc tuple
* ----------
*/
tup = SearchSysCacheTuple(PROOID,
ObjectIdGetDatum(func->funcid), 0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup for proc %u failed", func->funcid);
procStruct = (Form_pg_proc) GETSTRUCT(tup);
/* ----------
* It must be a function with two arguments where the first
* is of the same type as the return value and the second is
* an int4.
* ----------
*/
if (procStruct->pronargs != 2 ||
procStruct->prorettype != procStruct->proargtypes[0] ||
procStruct->proargtypes[1] != INT4OID)
return get_rule_expr(qh, rt_index, tle->expr, varprefix);
/*
* Furthermore, the name of the function must be the same
* as the argument/result type name.
*/
tup = SearchSysCacheTuple(TYPOID,
ObjectIdGetDatum(procStruct->prorettype),
0, 0, 0);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup for type %u failed",
procStruct->prorettype);
typeStruct = (Form_pg_type) GETSTRUCT(tup);
if (strncmp(procStruct->proname.data, typeStruct->typname.data,
NAMEDATALEN) != 0)
return get_rule_expr(qh, rt_index, tle->expr, varprefix);
/* ----------
* Finally (to be totally safe) the second argument must be a
* const and match the value in the results atttypmod.
* ----------
*/
second_arg = (Const *) nth(1, expr->args);
if (! IsA(second_arg, Const) ||
((int4) second_arg->constvalue) != tle->resdom->restypmod)
return get_rule_expr(qh, rt_index, tle->expr, varprefix);
/* ----------
* Whow - got it. Now get rid of the padding function
* ----------
*/
return get_rule_expr(qh, rt_index, lfirst(expr->args), varprefix);
}
/* ----------
* get_const_expr - Make a string representation
* with the type cast out of a Const
* ----------
*/
static char *
get_const_expr(Const *constval)
{
HeapTuple typetup;
Form_pg_type typeStruct;
FmgrInfo finfo_output;
char *extval;
bool isnull = FALSE;
char buf[BUFSIZE];
char namebuf[64];
if (constval->constisnull)
return pstrdup("NULL");
typetup = SearchSysCacheTuple(TYPOID,
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);
fmgr_info(typeStruct->typoutput, &finfo_output);
extval = (char *) (*fmgr_faddr(&finfo_output)) (constval->constvalue,
&isnull, -1);
sprintf(namebuf, "::\"%s\"", nameout(&(typeStruct->typname)));
if (strcmp(namebuf, "::unknown") == 0)
namebuf[0] = '\0';
sprintf(buf, "'%s'%s", extval, namebuf);
return pstrdup(buf);
}
/* ----------
* get_sublink_expr - Parse back a sublink
* ----------
*/
static char *
get_sublink_expr(QryHier *qh, int rt_index, Node *node, bool varprefix)
{
SubLink *sublink = (SubLink *) node;
Query *query = (Query *) (sublink->subselect);
Oper *oper;
List *l;
char *sep;
char buf[BUFSIZE];
buf[0] = '\0';
strcat(buf, "(");
if (sublink->lefthand != NULL)
{
if (length(sublink->lefthand) > 1)
strcat(buf, "(");
sep = "";
foreach(l, sublink->lefthand)
{
strcat(buf, sep);
sep = ", ";
strcat(buf, get_rule_expr(qh, rt_index,
lfirst(l), varprefix));
}
if (length(sublink->lefthand) > 1)
strcat(buf, ") ");
else
strcat(buf, " ");
}
switch (sublink->subLinkType)
{
case EXISTS_SUBLINK:
strcat(buf, "EXISTS ");
break;
case ANY_SUBLINK:
oper = (Oper *) lfirst(sublink->oper);
strcat(buf, get_opname(oper->opno));
strcat(buf, " ANY ");
break;
case ALL_SUBLINK:
oper = (Oper *) lfirst(sublink->oper);
strcat(buf, get_opname(oper->opno));
strcat(buf, " ALL ");
break;
case EXPR_SUBLINK:
oper = (Oper *) lfirst(sublink->oper);
strcat(buf, get_opname(oper->opno));
strcat(buf, " ");
break;
default:
elog(ERROR, "unupported sublink type %d",
sublink->subLinkType);
break;
}
strcat(buf, "(");
strcat(buf, get_query_def(query, qh));
strcat(buf, "))");
return pstrdup(buf);
}
/* ----------
* get_relation_name - Get a relation name by Oid
* ----------
*/
static char *
get_relation_name(Oid relid)
{
HeapTuple classtup;
Form_pg_class classStruct;
classtup = SearchSysCacheTuple(RELOID,
ObjectIdGetDatum(relid), 0, 0, 0);
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup of relation %u failed", relid);
classStruct = (Form_pg_class) GETSTRUCT(classtup);
return nameout(&(classStruct->relname));
}
/* ----------
* get_attribute_name - Get an attribute name by it's
* relations Oid and it's attnum
* ----------
*/
static char *
get_attribute_name(Oid relid, int2 attnum)
{
HeapTuple atttup;
Form_pg_attribute attStruct;
atttup = SearchSysCacheTuple(ATTNUM,
ObjectIdGetDatum(relid), (Datum) attnum, 0, 0);
if (!HeapTupleIsValid(atttup))
elog(ERROR, "cache lookup of attribute %d in relation %u failed",
attnum, relid);
attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
return nameout(&(attStruct->attname));
}
/* ----------
* check_if_rte_used
* Check a targetlist or qual to see if a given rangetable entry
* is used in it
* ----------
*/
static bool
check_if_rte_used(Node *node, Index rt_index, int levelsup)
{
check_if_rte_used_context context;
context.rt_index = rt_index;
context.levelsup = levelsup;
return check_if_rte_used_walker(node, &context);
}
static bool
check_if_rte_used_walker(Node *node,
check_if_rte_used_context *context)
{
if (node == NULL)
return false;
if (IsA(node, Var))
{
Var *var = (Var *) node;
return var->varno == context->rt_index &&
var->varlevelsup == context->levelsup;
}
if (IsA(node, SubLink))
{
SubLink *sublink = (SubLink *) node;
Query *query = (Query *) sublink->subselect;
/* Recurse into subquery; expression_tree_walker will not */
if (check_if_rte_used((Node *) (query->targetList),
context->rt_index, context->levelsup + 1) ||
check_if_rte_used(query->qual,
context->rt_index, context->levelsup + 1) ||
check_if_rte_used(query->havingQual,
context->rt_index, context->levelsup + 1))
return true;
/* fall through to let expression_tree_walker examine lefthand args */
}
return expression_tree_walker(node, check_if_rte_used_walker,
(void *) context);
}