Patch adding pg_rule and pg_view - Mailing list pgsql-hackers

From jwieck@debis.com (Jan Wieck)
Subject Patch adding pg_rule and pg_view
Date
Msg-id m0z9EYv-000EBPC@orion.SAPserv.Hamburg.dsh.de
Whole thread Raw
In response to Re: Rules and views (was Re: [HACKERS] Rules: 2nd patch)  (jwieck@debis.com (Jan Wieck))
List 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 by date:

Previous
From: jwieck@debis.com (Jan Wieck)
Date:
Subject: Re: Rules and views (was Re: [HACKERS] Rules: 2nd patch)
Next
From: Garrett Wollman
Date:
Subject: More complete indexing for geometric types?