Rules: 2nd patch
Hi,
here's the second patch for the rule system. It fixes the
backend crashes that occur when using views/rules in the same
session where they are created. The bug was that the new
actions parsetrees and event qualifications, added to the
relations rd_rules locks, where not copied to the cache
memory context.
In addition it prepares tcop/postgres.c to handle a zero
length rewritten list returned by QueryRewrite() correctly
(due to instead nothing).
Next patch will allow instead nothing on select rules to
return no tuples at all instead of one with all NULL fields
(as it is now).
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 -cr backend.orig/rewrite/rewriteSupport.c backend/rewrite/rewriteSupport.c
*** backend.orig/rewrite/rewriteSupport.c Wed Aug 19 10:36:15 1998
--- backend/rewrite/rewriteSupport.c Wed Aug 19 10:55:03 1998
***************
*** 158,163 ****
--- 158,167 ----
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) CacheCxt);
thisRule = (RewriteRule *) palloc(sizeof(RewriteRule));
+ if (qual != NULL)
+ qual = copyObject(qual);
+ if (actions != NIL)
+ actions = copyObject(actions);
MemoryContextSwitchTo(oldcxt);
thisRule->ruleId = ruleId;
diff -cr backend.orig/tcop/postgres.c backend/tcop/postgres.c
*** backend.orig/tcop/postgres.c Fri Aug 7 11:05:41 1998
--- backend/tcop/postgres.c Wed Aug 19 10:51:50 1998
***************
*** 451,474 ****
/* rewrite queries (retrieve, append, delete, replace) */
rewritten = QueryRewrite(querytree);
! /*
! * Rewrite the UNIONS.
! */
! foreach(rewritten_list, rewritten)
! {
! Query *qry = (Query *) lfirst(rewritten_list);
!
! union_result = NIL;
! foreach(union_list, qry->unionClause)
! union_result = nconc(union_result, QueryRewrite((Query *) lfirst(union_list)));
! qry->unionClause = union_result;
! }
!
! if (rewritten != NULL)
{
int len,
k;
len = length(rewritten);
if (len == 1)
new_list->qtrees[j++] = (Query *) lfirst(rewritten);
--- 451,474 ----
/* rewrite queries (retrieve, append, delete, replace) */
rewritten = QueryRewrite(querytree);
! if (rewritten != NIL)
{
int len,
k;
+ /*
+ * Rewrite the UNIONS.
+ */
+ foreach(rewritten_list, rewritten)
+ {
+ Query *qry = (Query *) lfirst(rewritten_list);
+
+ union_result = NIL;
+ foreach(union_list, qry->unionClause)
+ union_result = nconc(union_result, QueryRewrite((Query *) lfirst(union_list)));
+ qry->unionClause = union_result;
+ }
+
len = length(rewritten);
if (len == 1)
new_list->qtrees[j++] = (Query *) lfirst(rewritten);
***************
*** 487,492 ****
--- 487,500 ----
}
}
+ /* ----------
+ * Due to rewriting, the new list could also have been
+ * shrunk (do instead nothing). Forget obsolete queries
+ * at the end.
+ * ----------
+ */
+ new_list->len = j;
+
/* we're done with the original lists, free it */
free(querytree_list->qtrees);
free(querytree_list);
***************
*** 530,536 ****
elog(NOTICE, "(transaction aborted): %s",
"queries ignored until END");
! *queryListP = (QueryTreeList *) NULL;
return (List *) NULL;
}
--- 538,547 ----
elog(NOTICE, "(transaction aborted): %s",
"queries ignored until END");
! free(querytree_list->qtrees);
! free(querytree_list);
! if (queryListP)
! *queryListP = (QueryTreeList *) NULL;
return (List *) NULL;
}
***************
*** 571,576 ****
--- 582,597 ----
else
plan_list = lappend(plan_list, NULL);
#endif
+ }
+
+ /* ----------
+ * Check if the rewriting had thrown away anything
+ * ----------
+ */
+ if (querytree_list->len == 0) {
+ free(querytree_list->qtrees);
+ free(querytree_list);
+ querytree_list = NULL;
}
if (queryListP)
Next patch will allow instead nothing on select rules to
return no tuples at all instead of one with all NULL fields
(as it is now).
Ooops - though it would be so easy - but it isn't. Instead
rules for select are a damned thing. Select rules are applied
totally different from the others, since the parsetree cannot
get splitted. Anything I tried resulted in something that
doesn't work at all, except for unqualified instead nothing
rule (but what is a write-only table good for? :-). So up to
now they make no sense for me. Qualified instead nothing is a
topic for a view, with the handmade negated qualification.
Next thing will be the UPDATE NEW stuff then.
Another rule related topic though:
As the rule system becomes more and more useful now, wouldn't
it be nice to enable them for users? I think rule
creation/deletion must be restricted to the table owner (or
superuser). And anything the rule does has to be checked
against the permissions of the rule owner.
Next rule related topic:
My new function get_ruledef(name) has now a little sister.
Her name is get_viewdef(name) and when called with a relation
name she's telling either 'Not a view' or the complete SELECT
statement, that builds the view. I've defined them in the
regression database and setup a view xx_view. Now I can do
regression=> select * from xx_view;
viewname|definition
--------+--------------------------------------------------------------------------------------------------------------------------------------------------
pg_user |SELECT usename, usesysid, usecreatedb, usetrace, usesuper, usecatupd, '********'::text AS passwd, valuntil FROM pg_shadow;
street |SELECT r.name, r.thepath, c.cname FROM road r, real_city c WHERE c.outline ## r.thepath;
iexit |SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE ih.thepath ## r.thepath;
toyemp |SELECT name, age, location, '12'::int4 * salary AS annualsal FROM emp;
rtest_v1|SELECT a, b FROM rtest_t1;
xx_view |SELECT relname AS viewname, get_viewdef(relname) AS definition FROM pg_class WHERE (relhasrules) AND (get_viewdef(relname) <> 'Not a view'::text);
(6 rows)
regression=>
(Sorry for the long lines)
Except for the explicit type casting on all constants, they
are exactly the definitions of the original views. And the
postgres parser accepts that as input to produce bit by bit
the same rules/views again.
I tought them how to handle aggregates and group by and will
tell them about isnull soon.
If nobody votes against, I would like to make them builtins
as pg_get_ruledef() and pg_get_viewdef() and then setup the
appropriate views (pg_rule and pg_view) in template1 by
initdb like we do it with pg_user. It cannot break anything,
except that a rule action the two functions cannot handle
will make the new views unusable. But it would be a really
powerful possibility for pg_dump or just useful to see what
that damn event qual and parsetree's in pg_rewrite came from.
Comments?
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) #
If nobody votes against, I would like to make them builtins
as pg_get_ruledef() and pg_get_viewdef() and then setup the
appropriate views (pg_rule and pg_view) in template1 by
initdb like we do it with pg_user. It cannot break anything,
except that a rule action the two functions cannot handle
will make the new views unusable. But it would be a really
powerful possibility for pg_dump or just useful to see what
that damn event qual and parsetree's in pg_rewrite came from.Comments?
Go, Jan, go.
--
Bruce Momjian | 830 Blythe Avenue
maillist@candle.pha.pa.us | Drexel Hill, Pennsylvania 19026
+ If your life is a hard drive, | (610) 353-9879(w)
+ Christ can be your backup. | (610) 853-3000(h)
If nobody votes against, I would like to make them builtins
as pg_get_ruledef() and pg_get_viewdef() and then setup the
appropriate views (pg_rule and pg_view) in template1 by
initdb like we do it with pg_user. It cannot break anything,
except that a rule action the two functions cannot handle
will make the new views unusable. But it would be a really
powerful possibility for pg_dump or just useful to see what
that damn event qual and parsetree's in pg_rewrite came from.Comments?
Go, Jan, go.
You'll get it after the currently running clean compile of
the initial source tree (with the patch applied) and the rest
went through.
--
Bruce Momjian | 830 Blythe Avenue
For the rule permissions:
I think it would be O.K. to check the rule actions against
the owner of their event relation. And the creator/destructor
must be the owner of it or a superuser. This way, a regular
user can do with rules anything he want's on the tables he
has access to and as soon as he grant's access to his tables
the granted user can use them as he would including the rule
actions.
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) #
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
*/
Hi,
I vote to make them builtins too.
Keith.
Bruce Momjian <maillist@candle.pha.pa.us>
Show quoted text
If nobody votes against, I would like to make them builtins
as pg_get_ruledef() and pg_get_viewdef() and then setup the
appropriate views (pg_rule and pg_view) in template1 by
initdb like we do it with pg_user. It cannot break anything,
except that a rule action the two functions cannot handle
will make the new views unusable. But it would be a really
powerful possibility for pg_dump or just useful to see what
that damn event qual and parsetree's in pg_rewrite came from.Comments?
Go, Jan, go.
Import Notes
Resolved by subject fallback