Patch adding pg_rule and pg_view
От | jwieck@debis.com (Jan Wieck) |
---|---|
Тема | Patch adding pg_rule and pg_view |
Дата | |
Msg-id | m0z9EYv-000EBPC@orion.SAPserv.Hamburg.dsh.de обсуждение исходный текст |
Ответ на | Re: Rules and views (was Re: [HACKERS] Rules: 2nd patch) (jwieck@debis.com (Jan Wieck)) |
Список | pgsql-hackers |
> You'll get it after the currently running clean compile of > the initial source tree (with the patch applied) and the rest > went through. Here they are. Jan -- #======================================================================# # It's easier to get forgiveness for being wrong than for being right. # # Let's break this rule - forgive me. # #======================================== jwieck@debis.com (Jan Wieck) # diff -crN src.orig/backend/utils/adt/Makefile src/backend/utils/adt/Makefile *** src.orig/backend/utils/adt/Makefile Wed Aug 19 10:36:19 1998 --- src/backend/utils/adt/Makefile Wed Aug 19 20:21:30 1998 *************** *** 22,29 **** geo_ops.o geo_selfuncs.o int.o int8.o like.o \ misc.o nabstime.o name.o not_in.o numutils.o \ oid.o oracle_compat.o \ ! regexp.o regproc.o selfuncs.o sets.o tid.o timestamp.o \ ! varchar.o varlena.o version.o all: SUBSYS.o --- 22,29 ---- geo_ops.o geo_selfuncs.o int.o int8.o like.o \ misc.o nabstime.o name.o not_in.o numutils.o \ oid.o oracle_compat.o \ ! regexp.o regproc.o ruleutils.o selfuncs.o sets.o \ ! tid.o timestamp.o varchar.o varlena.o version.o all: SUBSYS.o diff -crN src.orig/backend/utils/adt/ruleutils.c src/backend/utils/adt/ruleutils.c *** src.orig/backend/utils/adt/ruleutils.c Thu Jan 1 01:00:00 1970 --- src/backend/utils/adt/ruleutils.c Wed Aug 19 20:20:29 1998 *************** *** 0 **** --- 1,1376 ---- + /********************************************************************** + * get_ruledef.c - Function to get a rules definition text + * out of it's tuple + * + * IDENTIFICATION + * $Header: $ + * + * 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 <stdio.h> + #include <stdlib.h> + #include <stdarg.h> + #include <unistd.h> + #include <fcntl.h> + #include <string.h> + + #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; + TypeTupleForm 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 = (TypeTupleForm) 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; + AttributeTupleForm 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 = (AttributeTupleForm) 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; + } + + diff -crN src.orig/bin/initdb/initdb.sh src/bin/initdb/initdb.sh *** src.orig/bin/initdb/initdb.sh Fri Aug 14 19:09:08 1998 --- src/bin/initdb/initdb.sh Wed Aug 19 20:52:38 1998 *************** *** 436,441 **** --- 436,474 ---- echo "REVOKE ALL on pg_shadow FROM public" | \ postgres $PGSQL_OPT template1 > /dev/null + echo "creating view pg_rule" + echo "CREATE TABLE xpg_rule ( \ + rulename name, \ + definition text);" | postgres $PGSQL_OPT template1 > /dev/null + #move it into pg_rule + echo "UPDATE pg_class SET relname = 'pg_rule' WHERE relname = 'xpg_rule';" |\ + postgres $PGSQL_OPT template1 > /dev/null + echo "UPDATE pg_type SET typname = 'pg_rule' WHERE typname = 'xpg_rule';" |\ + postgres $PGSQL_OPT template1 > /dev/null + mv $PGDATA/base/template1/xpg_rule $PGDATA/base/template1/pg_rule + + echo "CREATE RULE _RETpg_rule AS ON SELECT TO pg_rule DO INSTEAD \ + SELECT rulename, pg_get_ruledef(rulename) AS definition \ + FROM pg_rewrite;" | postgres $PGSQL_OPT template1 > /dev/null + + echo "creating view pg_view" + echo "CREATE TABLE xpg_view ( \ + viewname name, \ + definition text);" | postgres $PGSQL_OPT template1 > /dev/null + #move it into pg_view + echo "UPDATE pg_class SET relname = 'pg_view' WHERE relname = 'xpg_view';" |\ + postgres $PGSQL_OPT template1 > /dev/null + echo "UPDATE pg_type SET typname = 'pg_view' WHERE typname = 'xpg_view';" |\ + postgres $PGSQL_OPT template1 > /dev/null + mv $PGDATA/base/template1/xpg_view $PGDATA/base/template1/pg_view + + echo "CREATE RULE _RETpg_view AS ON SELECT TO pg_view DO INSTEAD \ + SELECT relname AS viewname, \ + pg_get_viewdef(relname) AS definition \ + FROM pg_class WHERE relhasrules AND \ + pg_get_viewdef(relname) != 'Not a view';" | \ + postgres $PGSQL_OPT template1 > /dev/null + echo "loading pg_description" echo "copy pg_description from '$TEMPLATE_DESCR'" | \ postgres $PGSQL_OPT template1 > /dev/null diff -crN src.orig/include/catalog/pg_proc.h src/include/catalog/pg_proc.h *** src.orig/include/catalog/pg_proc.h Wed Aug 19 10:36:33 1998 --- src/include/catalog/pg_proc.h Wed Aug 19 20:17:56 1998 *************** *** 2033,2038 **** --- 2033,2044 ---- /* for multi-byte support */ DATA(insert OID = 1039 ( getdatabaseencoding PGUID 11 f t f 0 f 19 "0" 100 0 0 100 foo bar )); + /* System-view support functions */ + DATA(insert OID = 1640 ( pg_get_ruledef PGUID 11 f t f 1 f 25 "19" 100 0 0 100 foo bar )); + DESCR("source text of a rule"); + DATA(insert OID = 1641 ( pg_get_viewdef PGUID 11 f t f 1 f 25 "19" 100 0 0 100 foo bar )); + DESCR("select statement of a view"); + /* * prototypes for functions pg_proc.c */
В списке pgsql-hackers по дате отправления:
Предыдущее
От: jwieck@debis.com (Jan Wieck)Дата:
Сообщение: Re: Rules and views (was Re: [HACKERS] Rules: 2nd patch)