/********************************************************************** * 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.3 1998/09/01 04:32:49 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 #include #include #include #include #include #include "executor/spi.h" #include "commands/trigger.h" #include "utils/elog.h" #include "utils/builtins.h" #include "nodes/nodes.h" #include "optimizer/clauses.h" #include "utils/syscache.h" #include "utils/lsyscache.h" #include "catalog/pg_class.h" #include "catalog/pg_type.h" #include "fmgr.h" /* ---------- * 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"; /* ---------- * Global functions * ---------- */ text *pg_get_ruledef(NameData *rname); text *pg_get_viewdef(NameData *rname); /* ---------- * 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); static char *get_select_query_def(Query *query); static char *get_insert_query_def(Query *query); static char *get_update_query_def(Query *query); static char *get_delete_query_def(Query *query); static char *get_rule_expr(List *rtable, int rt_index, Node *node, bool varprefix); static char *get_func_expr(List *rtable, int rt_index, Expr *expr, bool varprefix); static char *get_tle_expr(List *rtable, int rt_index, TargetEntry *tle, bool varprefix); static char *get_const_expr(Const *constval); static char *get_relation_name(Oid relid); static char *get_attribute_name(Oid relid, int2 attnum); static bool check_if_rte_used(int rt_index, Node *node, int sup); /* ---------- * 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; } /* ---------- * 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(8192); /* ---------- * 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); if (isnull) ev_qual = NULL; fno = SPI_fnumber(rulettc, "ev_action"); ev_action = SPI_getvalue(ruletup, rulettc, fno); if (isnull) ev_action = NULL; 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)); if (ev_attr > 0) { strcat(buf, "."); strcat(buf, get_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) { Node *qual; Query *query; qual = stringToNode(ev_qual); query = (Query *) lfirst(actions); strcat(buf, " WHERE "); strcat(buf, get_rule_expr(query->rtable, 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)); 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)); 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[8192]; 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); if (isnull) ev_qual = ""; fno = SPI_fnumber(rulettc, "ev_action"); ev_action = SPI_getvalue(ruletup, rulettc, fno); if (isnull) ev_action = NULL; 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_select_query_def(query)); 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) { switch (query->commandType) { case CMD_SELECT: return get_select_query_def(query); break; case CMD_UPDATE: return get_update_query_def(query); break; case CMD_INSERT: return get_insert_query_def(query); break; case CMD_DELETE: return get_delete_query_def(query); 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) { char buf[8192]; 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 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(i + 1, (Node *) (query->targetList), 0)) { rt_used[i] = TRUE; rt_numused++; } else { if (check_if_rte_used(i + 1, (Node *) (query->qual), 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(query->rtable, 0, tle, (rt_numused > 1))); /* Check if we must say AS ... */ if (nodeTag(tle->expr) != T_Var) tell_as = strcmp(tle->resdom->resname, "?column?"); else { Var *var = (Var *) (tle->expr); char *attname; rte = (RangeTblEntry *) nth(var->varno - 1, query->rtable); 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); } } /* 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, rte->relname); if (rt_numused > 1) { strcat(buf, " "); strcat(buf, rte->refname); } } } } /* Add the WHERE clause if given */ if (query->qual != NULL) { strcat(buf, " WHERE "); strcat(buf, get_rule_expr(query->rtable, 0, query->qual, (rt_numused > 1))); } /* Add the GROUP BY CLAUSE */ if (query->groupClause != NULL) { strcat(buf, " GROUP BY "); sep = ""; foreach(l, query->groupClause) { strcat(buf, sep); sep = ", "; strcat(buf, get_rule_expr(query->rtable, 0, lfirst(l), (rt_numused > 1))); } } /* ---------- * 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) { char buf[8192]; 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(i + 1, (Node *) (query->targetList), 0)) { rt_used[i] = TRUE; rt_numused++; } else { if (check_if_rte_used(i + 1, (Node *) (query->qual), 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); /* Add the target list */ sep = " ("; foreach(l, query->targetList) { tle = (TargetEntry *) lfirst(l); strcat(buf, sep); sep = ", "; strcat(buf, tle->resdom->resname); } 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(query->rtable, 0, tle, (rt_numused > 1))); } strcat(buf, ")"); } else strcat(buf, get_select_query_def(query)); /* ---------- * 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) { char buf[8192]; 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, tle->resdom->resname); strcat(buf, " = "); strcat(buf, get_tle_expr(query->rtable, query->resultRelation, tle, TRUE)); } /* Finally add a WHERE clause if given */ if (query->qual != NULL) { strcat(buf, " WHERE "); strcat(buf, get_rule_expr(query->rtable, 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) { char buf[8192]; 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); /* Add a WHERE clause if given */ if (query->qual != NULL) { strcat(buf, " WHERE "); strcat(buf, get_rule_expr(query->rtable, 0, query->qual, FALSE)); } /* ---------- * Copy the query string into allocated space and return it * ---------- */ return pstrdup(buf); } /* ---------- * get_rule_expr - Parse back an expression * ---------- */ static char * get_rule_expr(List *rtable, int rt_index, Node *node, bool varprefix) { char buf[8192]; if (node == NULL) return pstrdup(""); buf[0] = '\0'; /* ---------- * Up to now I don't know if all the node types below * can really occur in rules actions and qualifications. * There might be some work left. * ---------- */ switch (nodeTag(node)) { case T_TargetEntry: { TargetEntry *tle = (TargetEntry *) node; return get_rule_expr(rtable, rt_index, (Node *) (tle->expr), varprefix); } break; case T_Aggreg: { Aggreg *agg = (Aggreg *) node; strcat(buf, agg->aggname); strcat(buf, "("); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) (agg->target), varprefix)); strcat(buf, ")"); return pstrdup(buf); } break; case T_GroupClause: { GroupClause *grp = (GroupClause *) node; return get_rule_expr(rtable, rt_index, (Node *) (grp->entry), varprefix); } 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, get_rule_expr(rtable, rt_index, (Node *) get_leftop(expr), varprefix)); strcat(buf, " "); strcat(buf, get_opname(((Oper *) expr->oper)->opno)); strcat(buf, " "); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) get_rightop(expr), varprefix)); return pstrdup(buf); break; case OR_EXPR: strcat(buf, "("); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) get_leftop(expr), varprefix)); strcat(buf, ") OR ("); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) get_rightop(expr), varprefix)); strcat(buf, ")"); return pstrdup(buf); break; case AND_EXPR: strcat(buf, "("); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) get_leftop(expr), varprefix)); strcat(buf, ") AND ("); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) get_rightop(expr), varprefix)); strcat(buf, ")"); return pstrdup(buf); break; case NOT_EXPR: strcat(buf, "NOT ("); strcat(buf, get_rule_expr(rtable, rt_index, (Node *) get_leftop(expr), varprefix)); strcat(buf, ")"); return pstrdup(buf); break; case FUNC_EXPR: return get_func_expr(rtable, rt_index, (Expr *) node, varprefix); break; default: printf("\n%s\n", nodeToString(node)); elog(ERROR, "Expr not yet supported"); } } break; case T_Var: { Var *var = (Var *) node; RangeTblEntry *rte = (RangeTblEntry *) nth(var->varno - 1, rtable); if (!strcmp(rte->refname, "*NEW*")) strcat(buf, "new."); else { if (!strcmp(rte->refname, "*CURRENT*")) strcat(buf, "current."); else { if (varprefix && var->varno != rt_index) { strcat(buf, rte->refname); strcat(buf, "."); } } } strcat(buf, get_attribute_name(rte->relid, var->varattno)); return pstrdup(buf); } break; case T_List: { printf("\n%s\n", nodeToString(node)); elog(ERROR, "List not yet supported"); } break; case T_SubLink: { SubLink *sublink = (SubLink *) node; Query *query = (Query *) (sublink->subselect); List *l; char *sep; if (sublink->lefthand != NULL) { strcat(buf, "("); sep = ""; foreach(l, sublink->lefthand) { strcat(buf, sep); sep = ", "; strcat(buf, get_rule_expr(rtable, rt_index, lfirst(l), varprefix)); } strcat(buf, ") IN "); } strcat(buf, "("); strcat(buf, get_query_def(query)); strcat(buf, ")"); return pstrdup(buf); } break; case T_Const: return get_const_expr((Const *) node); break; default: printf("\n%s\n", nodeToString(node)); elog(ERROR, "get_ruledef of %s: unknown node type %d get_rule_expr()", rulename, nodeTag(node)); break; } return FALSE; } /* ---------- * get_func_expr - Parse back a Func node * ---------- */ static char * get_func_expr(List *rtable, int rt_index, Expr *expr, bool varprefix) { char buf[8192]; 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 %d 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(rtable, rt_index, lfirst(expr->args), varprefix)); strcat(buf, ") ISNULL"); return pstrdup(buf); } if (!strcmp(proname, "nonnullvalue")) { strcpy(buf, "("); strcat(buf, get_rule_expr(rtable, rt_index, lfirst(expr->args), varprefix)); strcat(buf, ") NOTNULL"); return pstrdup(buf); } } /* ---------- * Build a string of proname(args) * ---------- */ strcpy(buf, proname); strcat(buf, "("); sep = ""; foreach(l, expr->args) { strcat(buf, sep); sep = ", "; strcat(buf, get_rule_expr(rtable, 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(List *rtable, int rt_index, TargetEntry *tle, bool varprefix) { HeapTuple proctup; Form_pg_proc procStruct; Expr *expr; Func *func; 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) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); if (nodeTag(tle->expr) != T_Expr) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); expr = (Expr *) (tle->expr); if (expr->opType != FUNC_EXPR) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); func = (Func *) (expr->oper); /* ---------- * Get the functions pg_proc tuple * ---------- */ proctup = SearchSysCacheTuple(PROOID, ObjectIdGetDatum(func->funcid), 0, 0, 0); if (!HeapTupleIsValid(proctup)) elog(ERROR, "cache lookup for proc %d failed", func->funcid); procStruct = (Form_pg_proc) GETSTRUCT(proctup); /* ---------- * 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) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); if (procStruct->prorettype != procStruct->proargtypes[0]) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); if (procStruct->proargtypes[1] != INT4OID) return get_rule_expr(rtable, 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 (nodeTag((Node *) second_arg) != T_Const) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); if ((int4) (second_arg->constvalue) != tle->resdom->restypmod) return get_rule_expr(rtable, rt_index, tle->expr, varprefix); /* ---------- * Whow - got it. Now get rid of the padding function * ---------- */ return get_rule_expr(rtable, rt_index, lfirst(expr->args), varprefix); } /* ---------- * get_const_expr - Make a string representation * with the type cast out of a Const * ---------- */ char * get_const_expr(Const *constval) { HeapTuple typetup; Form_pg_type typeStruct; FmgrInfo finfo_output; char *extval; bool isnull = FALSE; char buf[8192]; if (constval->constisnull) return "NULL"; typetup = SearchSysCacheTuple(TYPOID, ObjectIdGetDatum(constval->consttype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup of type %d 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(buf, "'%s'::%s", extval, nameout(&(typeStruct->typname))); 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 %d 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 %d failed", attnum, relid); attStruct = (Form_pg_attribute) GETSTRUCT(atttup); return nameout(&(attStruct->attname)); } /* ---------- * check_if_rte_used - Check a targetlist or qual * if a given rangetable entry * is used in it * ---------- */ static bool check_if_rte_used(int rt_index, Node *node, int sup) { if (node == NULL) return FALSE; switch (nodeTag(node)) { case T_TargetEntry: { TargetEntry *tle = (TargetEntry *) node; return check_if_rte_used(rt_index, (Node *) (tle->expr), sup); } break; case T_Aggreg: { Aggreg *agg = (Aggreg *) node; return check_if_rte_used(rt_index, (Node *) (agg->target), sup); } break; case T_GroupClause: { GroupClause *grp = (GroupClause *) node; return check_if_rte_used(rt_index, (Node *) (grp->entry), sup); } break; case T_Expr: { Expr *expr = (Expr *) node; return check_if_rte_used(rt_index, (Node *) (expr->args), sup); } break; case T_Var: { Var *var = (Var *) node; return var->varno == rt_index && var->varlevelsup == sup; } break; case T_List: { List *l; foreach(l, (List *) node) { if (check_if_rte_used(rt_index, lfirst(l), sup)) return TRUE; } return FALSE; } break; case T_SubLink: { SubLink *sublink = (SubLink *) node; Query *query = (Query *) sublink->subselect; if (check_if_rte_used(rt_index, (Node *) (query->qual), sup + 1)) return TRUE; if (check_if_rte_used(rt_index, (Node *) (sublink->lefthand), sup)) return TRUE; return FALSE; } break; case T_Const: return FALSE; break; default: elog(ERROR, "get_ruledef of %s: unknown node type %d in check_if_rte_used()", rulename, nodeTag(node)); break; } return FALSE; }