Обсуждение: Rules: 2nd patch

Поиск
Список
Период
Сортировка

Rules: 2nd patch

От
jwieck@debis.com (Jan Wieck)
Дата:
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)

Rules and views (was Re: [HACKERS] Rules: 2nd patch)

От
jwieck@debis.com (Jan Wieck)
Дата:
>     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
FROMpg_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) #

Re: Rules and views (was Re: [HACKERS] Rules: 2nd patch)

От
Bruce Momjian
Дата:
>     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)

Re: Rules and views (was Re: [HACKERS] Rules: 2nd patch)

От
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.

    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) #

Patch adding pg_rule and pg_view

От
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
   */