OSDN Git Service

Implement types regprocedure, regoper, regoperator, regclass, regtype
[pg-rex/syncrep.git] / src / backend / utils / adt / ruleutils.c
index 92c8f93..580abea 100644 (file)
@@ -1,9 +1,9 @@
 /**********************************************************************
- * get_ruledef.c       - Function to get a rules definition text
- *                       out of its tuple
+ * ruleutils.c - Functions to convert stored expressions/querytrees
+ *                             back to source text
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.51 2000/06/09 01:11:09 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.99 2002/04/25 02:56:55 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
  *
  **********************************************************************/
 
+#include "postgres.h"
+
 #include <unistd.h>
 #include <fcntl.h>
 
-#include "postgres.h"
-
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_shadow.h"
 #include "executor/spi.h"
 #include "lib/stringinfo.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/tlist.h"
 #include "parser/keywords.h"
 #include "parser/parse_expr.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rewriteSupport.h"
 #include "utils/lsyscache.h"
 
 
  * Local data types
  * ----------
  */
+
+/* Context info needed for invoking a recursive querytree display routine */
 typedef struct
 {
        StringInfo      buf;                    /* output buffer to append to */
-       List       *rangetables;        /* List of List of RangeTblEntry */
+       List       *namespaces;         /* List of deparse_namespace nodes */
        bool            varprefix;              /* TRUE to print prefixes on Vars */
 } deparse_context;
 
+/*
+ * Each level of query context around a subtree needs a level of Var namespace.
+ * A Var having varlevelsup=N refers to the N'th item (counting from 0) in
+ * the current context's namespaces list.
+ *
+ * The rangetable is the list of actual RTEs from the query tree.
+ *
+ * For deparsing plan trees, we allow two special RTE entries that are not
+ * part of the rtable list (mainly because they don't have consecutively
+ * allocated varnos).
+ */
 typedef struct
 {
-       Index           rt_index;
-       int                     levelsup;
-} check_if_rte_used_context;
+       List       *rtable;                     /* List of RangeTblEntry nodes */
+       int                     outer_varno;    /* varno for outer_rte */
+       RangeTblEntry *outer_rte;       /* special RangeTblEntry, or NULL */
+       int                     inner_varno;    /* varno for inner_rte */
+       RangeTblEntry *inner_rte;       /* special RangeTblEntry, or NULL */
+} deparse_namespace;
 
 
 /* ----------
  * Global data
  * ----------
  */
-static char *rulename = NULL;
-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";
-static void *plan_getam = NULL;
-static char *query_getam = "SELECT * FROM pg_am WHERE oid = $1";
-static void *plan_getopclass = NULL;
-static char *query_getopclass = "SELECT * FROM pg_opclass WHERE oid = $1";
+static void *plan_getrulebyoid = NULL;
+static char *query_getrulebyoid = "SELECT * FROM pg_rewrite WHERE oid = $1";
+static void *plan_getviewrule = NULL;
+static char *query_getviewrule = "SELECT * FROM pg_rewrite WHERE ev_class = $1 AND rulename = $2";
 
 
 /* ----------
@@ -94,27 +112,39 @@ static char *query_getopclass = "SELECT * FROM pg_opclass WHERE oid = $1";
  * as a parameter, and append their text output to its contents.
  * ----------
  */
+static text *pg_do_getviewdef(Oid viewoid);
 static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc);
 static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc);
-static void get_query_def(Query *query, StringInfo buf, List *parentrtables);
+static void get_query_def(Query *query, StringInfo buf, List *parentnamespace);
 static void get_select_query_def(Query *query, deparse_context *context);
 static void get_insert_query_def(Query *query, deparse_context *context);
 static void get_update_query_def(Query *query, deparse_context *context);
 static void get_delete_query_def(Query *query, deparse_context *context);
-static RangeTblEntry *get_rte_for_var(Var *var, deparse_context *context);
+static void get_utility_query_def(Query *query, deparse_context *context);
+static void get_basic_select_query(Query *query, deparse_context *context);
+static void get_setop_query(Node *setOp, Query *query,
+                                                       deparse_context *context);
+static void get_rule_sortgroupclause(SortClause *srt, List *tlist,
+                                                bool force_colno,
+                                                deparse_context *context);
+static void get_names_for_var(Var *var, deparse_context *context,
+                                 char **refname, char **attname);
 static void get_rule_expr(Node *node, deparse_context *context);
 static void get_func_expr(Expr *expr, deparse_context *context);
+static void get_agg_expr(Aggref *aggref, deparse_context *context);
+static Node *strip_type_coercion(Node *expr, Oid resultType);
 static void get_tle_expr(TargetEntry *tle, deparse_context *context);
 static void get_const_expr(Const *constval, deparse_context *context);
 static void get_sublink_expr(Node *node, deparse_context *context);
-static char *quote_identifier(char *ident);
-static char *get_relation_name(Oid relid);
-static char *get_attribute_name(Oid relid, int2 attnum);
-static bool check_if_rte_used(Node *node, Index rt_index, int levelsup);
-static bool check_if_rte_used_walker(Node *node,
-                                                check_if_rte_used_context *context);
+static void get_from_clause(Query *query, deparse_context *context);
+static void get_from_clause_item(Node *jtnode, Query *query,
+                                        deparse_context *context);
+static void get_opclass_name(Oid opclass, Oid actual_datatype,
+                                StringInfo buf);
+static bool tleIsArrayAssign(TargetEntry *tle);
+static char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
 
-#define inherit_marker(rte)  ((rte)->inh ? "*" : "")
+#define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
 
 
 /* ----------
@@ -123,96 +153,84 @@ static bool check_if_rte_used_walker(Node *node,
  *                               to recreate the rule
  * ----------
  */
-text *
-pg_get_ruledef(NameData *rname)
+Datum
+pg_get_ruledef(PG_FUNCTION_ARGS)
 {
+       Oid                     ruleoid = PG_GETARG_OID(0);
        text       *ruledef;
        Datum           args[1];
-       char            nulls[2];
+       char            nulls[1];
        int                     spirc;
        HeapTuple       ruletup;
        TupleDesc       rulettc;
        StringInfoData buf;
        int                     len;
 
-       /* ----------
-        * We need the rules name somewhere deep down
-        * ----------
-        */
-       rulename = pstrdup(NameStr(*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.
-        * ----------
+       /*
+        * On the first call prepare the plan to lookup pg_rewrite. We read
+        * pg_rewrite over the SPI manager instead of using the syscache to be
+        * checked for read access on pg_rewrite.
         */
-       if (plan_getrule == NULL)
+       if (plan_getrulebyoid == NULL)
        {
                Oid                     argtypes[1];
                void       *plan;
 
-               argtypes[0] = NAMEOID;
-               plan = SPI_prepare(query_getrule, 1, argtypes);
+               argtypes[0] = OIDOID;
+               plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
                if (plan == NULL)
-                       elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getrule);
-               plan_getrule = SPI_saveplan(plan);
+                       elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getrulebyoid);
+               plan_getrulebyoid = 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);
+       args[0] = ObjectIdGetDatum(ruleoid);
+       nulls[0] = ' ';
+       spirc = SPI_execp(plan_getrulebyoid, args, nulls, 1);
        if (spirc != SPI_OK_SELECT)
-               elog(ERROR, "failed to get pg_rewrite tuple for %s", rulename);
+               elog(ERROR, "failed to get pg_rewrite tuple for %u", ruleoid);
        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;
+               ruledef = palloc(VARHDRSZ + 1);
+               VARATT_SIZEP(ruledef) = VARHDRSZ + 1;
                VARDATA(ruledef)[0] = '-';
-               return ruledef;
+               PG_RETURN_TEXT_P(ruledef);
        }
 
        ruletup = SPI_tuptable->vals[0];
        rulettc = SPI_tuptable->tupdesc;
 
-       /* ----------
+       /*
         * Get the rules definition and put it into executors memory
-        * ----------
         */
        initStringInfo(&buf);
        make_ruledef(&buf, ruletup, rulettc);
        len = buf.len + VARHDRSZ;
        ruledef = SPI_palloc(len);
-       VARSIZE(ruledef) = len;
+       VARATT_SIZEP(ruledef) = len;
        memcpy(VARDATA(ruledef), buf.data, buf.len);
        pfree(buf.data);
 
-       /* ----------
+       /*
         * Disconnect from SPI manager
-        * ----------
         */
        if (SPI_finish() != SPI_OK_FINISH)
                elog(ERROR, "get_ruledef: SPI_finish() failed");
 
-       /* ----------
+       /*
         * Easy - isn't it?
-        * ----------
         */
-       return ruledef;
+       PG_RETURN_TEXT_P(ruledef);
 }
 
 
@@ -221,74 +239,92 @@ pg_get_ruledef(NameData *rname)
  *                               only return the SELECT part of a view
  * ----------
  */
-text *
-pg_get_viewdef(NameData *rname)
+Datum
+pg_get_viewdef(PG_FUNCTION_ARGS)
+{
+       /* By OID */
+       Oid                     viewoid = PG_GETARG_OID(0);
+       text       *ruledef;
+
+       ruledef = pg_do_getviewdef(viewoid);
+       PG_RETURN_TEXT_P(ruledef);
+}
+
+Datum
+pg_get_viewdef_name(PG_FUNCTION_ARGS)
+{
+       /* By qualified name */
+       text       *viewname = PG_GETARG_TEXT_P(0);
+       RangeVar   *viewrel;
+       Oid                     viewoid;
+       text       *ruledef;
+
+       viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname,
+                                                                                                                          "get_viewdef"));
+       viewoid = RangeVarGetRelid(viewrel, false);
+
+       ruledef = pg_do_getviewdef(viewoid);
+       PG_RETURN_TEXT_P(ruledef);
+}
+
+/*
+ * Common code for by-OID and by-name variants of pg_get_viewdef
+ */
+static text *
+pg_do_getviewdef(Oid viewoid)
 {
        text       *ruledef;
        Datum           args[2];
-       char            nulls[3];
+       char            nulls[2];
        int                     spirc;
        HeapTuple       ruletup;
        TupleDesc       rulettc;
        StringInfoData buf;
        int                     len;
-       char            name1[NAMEDATALEN + 5];
-       char            name2[NAMEDATALEN + 5];
-
-       /* ----------
-        * We need the rules name somewhere deep down
-        * ----------
-        */
-       rulename = pstrdup(NameStr(*rname));
+       char       *viewname;
 
-       /* ----------
+       /*
         * 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.
-        * ----------
+       /*
+        * On the first call prepare the plan to lookup pg_rewrite. We read
+        * pg_rewrite over the SPI manager instead of using the syscache to be
+        * checked for read access on pg_rewrite.
         */
-       if (plan_getview == NULL)
+       if (plan_getviewrule == NULL)
        {
                Oid                     argtypes[2];
                void       *plan;
 
-               argtypes[0] = NAMEOID;
+               argtypes[0] = OIDOID;
                argtypes[1] = NAMEOID;
-               plan = SPI_prepare(query_getview, 2, argtypes);
+               plan = SPI_prepare(query_getviewrule, 2, argtypes);
                if (plan == NULL)
-                       elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getview);
-               plan_getview = SPI_saveplan(plan);
+                       elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getviewrule);
+               plan_getviewrule = SPI_saveplan(plan);
        }
 
-       /* ----------
-        * Get the pg_rewrite tuple for this rule
-        * ----------
+       /*
+        * Get the pg_rewrite tuple for the view's SELECT rule
         */
-       sprintf(name1, "_RET%s", rulename);
-       sprintf(name2, "_ret%s", rulename);
-       args[0] = PointerGetDatum(name1);
-       args[1] = PointerGetDatum(name2);
+       viewname = get_rel_name(viewoid);
+       args[0] = ObjectIdGetDatum(viewoid);
+       args[1] = PointerGetDatum(ViewSelectRuleName);
        nulls[0] = ' ';
        nulls[1] = ' ';
-       nulls[2] = '\0';
-       spirc = SPI_execp(plan_getview, args, nulls, 1);
+       spirc = SPI_execp(plan_getviewrule, args, nulls, 2);
        if (spirc != SPI_OK_SELECT)
-               elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename);
+               elog(ERROR, "failed to get pg_rewrite tuple for view %s", viewname);
        initStringInfo(&buf);
        if (SPI_processed != 1)
                appendStringInfo(&buf, "Not a view");
        else
        {
-               /* ----------
+               /*
                 * Get the rules definition and put it into executors memory
-                * ----------
                 */
                ruletup = SPI_tuptable->vals[0];
                rulettc = SPI_tuptable->tupdesc;
@@ -296,21 +332,16 @@ pg_get_viewdef(NameData *rname)
        }
        len = buf.len + VARHDRSZ;
        ruledef = SPI_palloc(len);
-       VARSIZE(ruledef) = len;
+       VARATT_SIZEP(ruledef) = len;
        memcpy(VARDATA(ruledef), buf.data, buf.len);
        pfree(buf.data);
 
-       /* ----------
+       /*
         * Disconnect from SPI manager
-        * ----------
         */
        if (SPI_finish() != SPI_OK_FINISH)
                elog(ERROR, "get_viewdef: SPI_finish() failed");
 
-       /* ----------
-        * Easy - isn't it?
-        * ----------
-        */
        return ruledef;
 }
 
@@ -327,259 +358,271 @@ pg_get_indexdef(PG_FUNCTION_ARGS)
        HeapTuple       ht_idx;
        HeapTuple       ht_idxrel;
        HeapTuple       ht_indrel;
-       HeapTuple       spi_tup;
-       TupleDesc       spi_ttc;
-       int                     spi_fno;
+       HeapTuple       ht_am;
        Form_pg_index idxrec;
        Form_pg_class idxrelrec;
        Form_pg_class indrelrec;
-       Datum           spi_args[1];
-       char            spi_nulls[2];
-       int                     spirc;
+       Form_pg_am      amrec;
        int                     len;
        int                     keyno;
        StringInfoData buf;
        StringInfoData keybuf;
        char       *sep;
 
-       /* ----------
-        * Connect to SPI manager
-        * ----------
-        */
-       if (SPI_connect() != SPI_OK_CONNECT)
-               elog(ERROR, "get_indexdef: cannot connect to SPI manager");
-
-       /* ----------
-        * On the first call prepare the plans to lookup pg_am
-        * and pg_opclass.
-        * ----------
-        */
-       if (plan_getam == NULL)
-       {
-               Oid                     argtypes[1];
-               void       *plan;
-
-               argtypes[0] = OIDOID;
-               plan = SPI_prepare(query_getam, 1, argtypes);
-               if (plan == NULL)
-                       elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getam);
-               plan_getam = SPI_saveplan(plan);
-
-               argtypes[0] = OIDOID;
-               plan = SPI_prepare(query_getopclass, 1, argtypes);
-               if (plan == NULL)
-                       elog(ERROR, "SPI_prepare() failed for \"%s\"", query_getopclass);
-               plan_getopclass = SPI_saveplan(plan);
-       }
-
-       /* ----------
+       /*
         * Fetch the pg_index tuple by the Oid of the index
-        * ----------
         */
-       ht_idx = SearchSysCacheTuple(INDEXRELID,
-                                                                ObjectIdGetDatum(indexrelid), 0, 0, 0);
+       ht_idx = SearchSysCache(INDEXRELID,
+                                                       ObjectIdGetDatum(indexrelid),
+                                                       0, 0, 0);
        if (!HeapTupleIsValid(ht_idx))
                elog(ERROR, "syscache lookup for index %u failed", indexrelid);
        idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
 
-       /* ----------
+       /*
         * Fetch the pg_class tuple of the index relation
-        * ----------
         */
-       ht_idxrel = SearchSysCacheTuple(RELOID,
-                                                 ObjectIdGetDatum(idxrec->indexrelid), 0, 0, 0);
+       ht_idxrel = SearchSysCache(RELOID,
+                                                          ObjectIdGetDatum(idxrec->indexrelid),
+                                                          0, 0, 0);
        if (!HeapTupleIsValid(ht_idxrel))
                elog(ERROR, "syscache lookup for relid %u failed", idxrec->indexrelid);
        idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
 
-       /* ----------
+       /*
         * Fetch the pg_class tuple of the indexed relation
-        * ----------
         */
-       ht_indrel = SearchSysCacheTuple(RELOID,
-                                                       ObjectIdGetDatum(idxrec->indrelid), 0, 0, 0);
+       ht_indrel = SearchSysCache(RELOID,
+                                                          ObjectIdGetDatum(idxrec->indrelid),
+                                                          0, 0, 0);
        if (!HeapTupleIsValid(ht_indrel))
                elog(ERROR, "syscache lookup for relid %u failed", idxrec->indrelid);
        indrelrec = (Form_pg_class) GETSTRUCT(ht_indrel);
 
-       /* ----------
-        * Get the am name for the index relation
-        * ----------
+       /*
+        * Fetch the pg_am tuple of the index' access method
         */
-       spi_args[0] = ObjectIdGetDatum(idxrelrec->relam);
-       spi_nulls[0] = ' ';
-       spi_nulls[1] = '\0';
-       spirc = SPI_execp(plan_getam, spi_args, spi_nulls, 1);
-       if (spirc != SPI_OK_SELECT)
-               elog(ERROR, "failed to get pg_am tuple for index %s",
-                        NameStr(idxrelrec->relname));
-       if (SPI_processed != 1)
-               elog(ERROR, "failed to get pg_am tuple for index %s",
-                        NameStr(idxrelrec->relname));
-       spi_tup = SPI_tuptable->vals[0];
-       spi_ttc = SPI_tuptable->tupdesc;
-       spi_fno = SPI_fnumber(spi_ttc, "amname");
+       ht_am = SearchSysCache(AMOID,
+                                                  ObjectIdGetDatum(idxrelrec->relam),
+                                                  0, 0, 0);
+       if (!HeapTupleIsValid(ht_am))
+               elog(ERROR, "syscache lookup for AM %u failed", idxrelrec->relam);
+       amrec = (Form_pg_am) GETSTRUCT(ht_am);
 
-       /* ----------
+       /*
         * Start the index definition
-        * ----------
         */
        initStringInfo(&buf);
        appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
                                         idxrec->indisunique ? "UNIQUE " : "",
-                                 quote_identifier(pstrdup(NameStr(idxrelrec->relname))),
-                                 quote_identifier(pstrdup(NameStr(indrelrec->relname))),
-                                        quote_identifier(SPI_getvalue(spi_tup, spi_ttc,
-                                                                                                  spi_fno)));
-
-       /* ----------
-        * Collect the indexed attributes
-        * ----------
+                                        quote_identifier(NameStr(idxrelrec->relname)),
+                                        quote_identifier(NameStr(indrelrec->relname)),
+                                        quote_identifier(NameStr(amrec->amname)));
+
+       /*
+        * Collect the indexed attributes in keybuf
         */
        initStringInfo(&keybuf);
        sep = "";
        for (keyno = 0; keyno < INDEX_MAX_KEYS; keyno++)
        {
-               if (idxrec->indkey[keyno] == InvalidAttrNumber)
+               AttrNumber      attnum = idxrec->indkey[keyno];
+
+               if (attnum == InvalidAttrNumber)
                        break;
 
                appendStringInfo(&keybuf, sep);
                sep = ", ";
 
-               /* ----------
+               /*
                 * Add the indexed field name
-                * ----------
                 */
                appendStringInfo(&keybuf, "%s",
-                                       quote_identifier(get_attribute_name(idxrec->indrelid,
-                                                                                               idxrec->indkey[keyno])));
+                         quote_identifier(get_relid_attribute_name(idxrec->indrelid,
+                                                                                                               attnum)));
 
-               /* ----------
+               /*
                 * If not a functional index, add the operator class name
-                * ----------
                 */
                if (idxrec->indproc == InvalidOid)
-               {
-                       spi_args[0] = ObjectIdGetDatum(idxrec->indclass[keyno]);
-                       spi_nulls[0] = ' ';
-                       spi_nulls[1] = '\0';
-                       spirc = SPI_execp(plan_getopclass, spi_args, spi_nulls, 1);
-                       if (spirc != SPI_OK_SELECT)
-                               elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[keyno]);
-                       if (SPI_processed != 1)
-                               elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[keyno]);
-                       spi_tup = SPI_tuptable->vals[0];
-                       spi_ttc = SPI_tuptable->tupdesc;
-                       spi_fno = SPI_fnumber(spi_ttc, "opcname");
-                       appendStringInfo(&keybuf, " %s",
-                                                 quote_identifier(SPI_getvalue(spi_tup, spi_ttc,
-                                                                                                               spi_fno)));
-               }
+                       get_opclass_name(idxrec->indclass[keyno],
+                                                        get_atttype(idxrec->indrelid, attnum),
+                                                        &keybuf);
        }
 
-       /* ----------
-        * For functional index say 'func (attrs) opclass'
-        * ----------
-        */
        if (idxrec->indproc != InvalidOid)
        {
+               /*
+                * For functional index say 'func (attrs) opclass'
+                */
                HeapTuple       proctup;
                Form_pg_proc procStruct;
 
-               proctup = SearchSysCacheTuple(PROCOID,
-                                                        ObjectIdGetDatum(idxrec->indproc), 0, 0, 0);
+               proctup = SearchSysCache(PROCOID,
+                                                                ObjectIdGetDatum(idxrec->indproc),
+                                                                0, 0, 0);
                if (!HeapTupleIsValid(proctup))
                        elog(ERROR, "cache lookup for proc %u failed", idxrec->indproc);
-
                procStruct = (Form_pg_proc) GETSTRUCT(proctup);
-               appendStringInfo(&buf, "%s(%s) ",
-                                quote_identifier(pstrdup(NameStr(procStruct->proname))),
+
+               appendStringInfo(&buf, "%s(%s)",
+                                                quote_identifier(NameStr(procStruct->proname)),
                                                 keybuf.data);
+               get_opclass_name(idxrec->indclass[0], procStruct->prorettype, &buf);
 
-               spi_args[0] = ObjectIdGetDatum(idxrec->indclass[0]);
-               spi_nulls[0] = ' ';
-               spi_nulls[1] = '\0';
-               spirc = SPI_execp(plan_getopclass, spi_args, spi_nulls, 1);
-               if (spirc != SPI_OK_SELECT)
-                       elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[0]);
-               if (SPI_processed != 1)
-                       elog(ERROR, "failed to get pg_opclass tuple %u", idxrec->indclass[0]);
-               spi_tup = SPI_tuptable->vals[0];
-               spi_ttc = SPI_tuptable->tupdesc;
-               spi_fno = SPI_fnumber(spi_ttc, "opcname");
-               appendStringInfo(&buf, "%s",
-                                                quote_identifier(SPI_getvalue(spi_tup, spi_ttc,
-                                                                                                          spi_fno)));
+               ReleaseSysCache(proctup);
        }
        else
-               /* ----------
-                * For the others say 'attr opclass [, ...]'
-                * ----------
+       {
+               /*
+                * Otherwise say 'attr opclass [, ...]'
                 */
                appendStringInfo(&buf, "%s", keybuf.data);
+       }
 
-       /* ----------
-        * Finish
-        * ----------
-        */
        appendStringInfo(&buf, ")");
 
-       /* ----------
-        * Create the result in upper executor memory
-        * ----------
+       /*
+        * If it's a partial index, decompile and append the predicate
+        */
+       if (VARSIZE(&idxrec->indpred) > VARHDRSZ)
+       {
+               Node       *node;
+               List       *context;
+               char       *exprstr;
+               char       *str;
+
+               /* Convert TEXT object to C string */
+               exprstr = DatumGetCString(DirectFunctionCall1(textout,
+                                                                        PointerGetDatum(&idxrec->indpred)));
+               /* Convert expression to node tree */
+               node = (Node *) stringToNode(exprstr);
+
+               /*
+                * If top level is a List, assume it is an implicit-AND structure,
+                * and convert to explicit AND.  This is needed for partial index
+                * predicates.
+                */
+               if (node && IsA(node, List))
+                       node = (Node *) make_ands_explicit((List *) node);
+               /* Deparse */
+               context = deparse_context_for(NameStr(indrelrec->relname),
+                                                                         idxrec->indrelid);
+               str = deparse_expression(node, context, false);
+               appendStringInfo(&buf, " WHERE %s", str);
+       }
+
+       /*
+        * Create the result as a TEXT datum, and free working data
         */
        len = buf.len + VARHDRSZ;
-       indexdef = SPI_palloc(len);
-       VARSIZE(indexdef) = len;
+       indexdef = (text *) palloc(len);
+       VARATT_SIZEP(indexdef) = len;
        memcpy(VARDATA(indexdef), buf.data, buf.len);
+
        pfree(buf.data);
        pfree(keybuf.data);
 
-       /* ----------
-        * Disconnect from SPI manager
-        * ----------
-        */
-       if (SPI_finish() != SPI_OK_FINISH)
-               elog(ERROR, "get_viewdef: SPI_finish() failed");
+       ReleaseSysCache(ht_idx);
+       ReleaseSysCache(ht_idxrel);
+       ReleaseSysCache(ht_indrel);
+       ReleaseSysCache(ht_am);
 
        PG_RETURN_TEXT_P(indexdef);
 }
 
 
 /* ----------
+ * get_expr                    - Decompile an expression tree
+ *
+ * Input: an expression tree in nodeToString form, and a relation OID
+ *
+ * Output: reverse-listed expression
+ *
+ * Currently, the expression can only refer to a single relation, namely
+ * the one specified by the second parameter.  This is sufficient for
+ * partial indexes, column default expressions, etc.
+ * ----------
+ */
+Datum
+pg_get_expr(PG_FUNCTION_ARGS)
+{
+       text       *expr = PG_GETARG_TEXT_P(0);
+       Oid                     relid = PG_GETARG_OID(1);
+       text       *result;
+       Node       *node;
+       List       *context;
+       char       *exprstr;
+       char       *relname;
+       char       *str;
+
+       /* Get the name for the relation */
+       relname = get_rel_name(relid);
+       if (relname == NULL)
+               PG_RETURN_NULL();               /* should we raise an error? */
+
+       /* Convert input TEXT object to C string */
+       exprstr = DatumGetCString(DirectFunctionCall1(textout,
+                                                                                                 PointerGetDatum(expr)));
+
+       /* Convert expression to node tree */
+       node = (Node *) stringToNode(exprstr);
+
+       /*
+        * If top level is a List, assume it is an implicit-AND structure, and
+        * convert to explicit AND.  This is needed for partial index
+        * predicates.
+        */
+       if (node && IsA(node, List))
+               node = (Node *) make_ands_explicit((List *) node);
+
+       /* Deparse */
+       context = deparse_context_for(relname, relid);
+       str = deparse_expression(node, context, false);
+
+       /* Pass the result back as TEXT */
+       result = DatumGetTextP(DirectFunctionCall1(textin,
+                                                                                          CStringGetDatum(str)));
+
+       PG_RETURN_TEXT_P(result);
+}
+
+
+/* ----------
  * get_userbyid                        - Get a user name by usesysid and
  *                               fallback to 'unknown (UID=n)'
  * ----------
  */
-NameData   *
-pg_get_userbyid(int32 uid)
+Datum
+pg_get_userbyid(PG_FUNCTION_ARGS)
 {
+       int32           uid = PG_GETARG_INT32(0);
+       Name            result;
        HeapTuple       usertup;
        Form_pg_shadow user_rec;
-       NameData   *result;
 
-       /* ----------
+       /*
         * Allocate space for the result
-        * ----------
         */
-       result = (NameData *) palloc(NAMEDATALEN);
+       result = (Name) palloc(NAMEDATALEN);
        memset(NameStr(*result), 0, NAMEDATALEN);
 
-       /* ----------
+       /*
         * Get the pg_shadow entry and print the result
-        * ----------
         */
-       usertup = SearchSysCacheTuple(SHADOWSYSID,
-                                                                 ObjectIdGetDatum(uid), 0, 0, 0);
+       usertup = SearchSysCache(SHADOWSYSID,
+                                                        ObjectIdGetDatum(uid),
+                                                        0, 0, 0);
        if (HeapTupleIsValid(usertup))
        {
                user_rec = (Form_pg_shadow) GETSTRUCT(usertup);
                StrNCpy(NameStr(*result), NameStr(user_rec->usename), NAMEDATALEN);
+               ReleaseSysCache(usertup);
        }
        else
-               sprintf((char *) result, "unknown (UID=%d)", uid);
+               sprintf(NameStr(*result), "unknown (UID=%d)", uid);
 
-       return result;
+       PG_RETURN_NAME(result);
 }
 
 /* ----------
@@ -588,32 +631,24 @@ pg_get_userbyid(int32 uid)
  * expr is the node tree to be deparsed.  It must be a transformed expression
  * tree (ie, not the raw output of gram.y).
  *
- * rangetables is a List of Lists of RangeTblEntry nodes: first sublist is for
- * varlevelsup = 0, next for varlevelsup = 1, etc.     In each sublist the first
- * item is for varno = 1, next varno = 2, etc. (Each sublist has the same
- * format as the rtable list of a parsetree or query.)
+ * dpcontext is a list of deparse_namespace nodes representing the context
+ * for interpreting Vars in the node tree.
  *
  * forceprefix is TRUE to force all Vars to be prefixed with their table names.
- * Otherwise, a prefix is printed only if there's more than one table involved
- * (and someday the code might try to print one only if there's ambiguity).
  *
  * The result is a palloc'd string.
  * ----------
  */
 char *
-deparse_expression(Node *expr, List *rangetables, bool forceprefix)
+deparse_expression(Node *expr, List *dpcontext, bool forceprefix)
 {
        StringInfoData buf;
        deparse_context context;
 
        initStringInfo(&buf);
        context.buf = &buf;
-       context.rangetables = rangetables;
-       context.varprefix = (forceprefix ||
-                                                length(rangetables) != 1 ||
-                                                length((List *) lfirst(rangetables)) != 1);
-
-       rulename = "";                          /* in case of errors */
+       context.namespaces = dpcontext;
+       context.varprefix = forceprefix;
 
        get_rule_expr(expr, &context);
 
@@ -621,6 +656,150 @@ deparse_expression(Node *expr, List *rangetables, bool forceprefix)
 }
 
 /* ----------
+ * deparse_context_for                 - Build deparse context for a single relation
+ *
+ * Given the reference name (alias) and OID of a relation, build deparsing
+ * context for an expression referencing only that relation (as varno 1,
+ * varlevelsup 0).  This is sufficient for many uses of deparse_expression.
+ * ----------
+ */
+List *
+deparse_context_for(const char *aliasname, Oid relid)
+{
+       deparse_namespace *dpns;
+       RangeTblEntry *rte;
+
+       dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
+
+       /* Build a minimal RTE for the rel */
+       rte = makeNode(RangeTblEntry);
+       rte->rtekind = RTE_RELATION;
+       rte->relid = relid;
+       rte->eref = makeAlias(aliasname, NIL);
+       rte->inh = false;
+       rte->inFromCl = true;
+
+       /* Build one-element rtable */
+       dpns->rtable = makeList1(rte);
+       dpns->outer_varno = dpns->inner_varno = 0;
+       dpns->outer_rte = dpns->inner_rte = NULL;
+
+       /* Return a one-deep namespace stack */
+       return makeList1(dpns);
+}
+
+/*
+ * deparse_context_for_plan            - Build deparse context for a plan node
+ *
+ * We assume we are dealing with an upper-level plan node having either
+ * one or two referenceable children (pass innercontext = NULL if only one).
+ * The passed-in Nodes should be made using deparse_context_for_subplan
+ * and/or deparse_context_for_relation.  The resulting context will work
+ * for deparsing quals, tlists, etc of the plan node.
+ */
+List *
+deparse_context_for_plan(int outer_varno, Node *outercontext,
+                                                int inner_varno, Node *innercontext)
+{
+       deparse_namespace *dpns;
+
+       dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
+
+       dpns->rtable = NIL;
+       dpns->outer_varno = outer_varno;
+       dpns->outer_rte = (RangeTblEntry *) outercontext;
+       dpns->inner_varno = inner_varno;
+       dpns->inner_rte = (RangeTblEntry *) innercontext;
+
+       /* Return a one-deep namespace stack */
+       return makeList1(dpns);
+}
+
+/*
+ * deparse_context_for_relation                - Build deparse context for 1 relation
+ *
+ * Helper routine to build one of the inputs for deparse_context_for_plan.
+ * Pass the reference name (alias) and OID of a relation.
+ *
+ * The returned node is actually a RangeTblEntry, but we declare it as just
+ * Node to discourage callers from assuming anything.
+ */
+Node *
+deparse_context_for_relation(const char *aliasname, Oid relid)
+{
+       RangeTblEntry *rte = makeNode(RangeTblEntry);
+
+       rte->rtekind = RTE_RELATION;
+       rte->relid = relid;
+       rte->eref = makeAlias(aliasname, NIL);
+       rte->inh = false;
+       rte->inFromCl = true;
+
+       return (Node *) rte;
+}
+
+/*
+ * deparse_context_for_subplan - Build deparse context for a plan node
+ *
+ * Helper routine to build one of the inputs for deparse_context_for_plan.
+ * Pass the tlist of the subplan node, plus the query rangetable.
+ *
+ * The returned node is actually a RangeTblEntry, but we declare it as just
+ * Node to discourage callers from assuming anything.
+ */
+Node *
+deparse_context_for_subplan(const char *name, List *tlist,
+                                                       List *rtable)
+{
+       RangeTblEntry *rte = makeNode(RangeTblEntry);
+       List       *attrs = NIL;
+       int                     nattrs = 0;
+       int                     rtablelength = length(rtable);
+       List       *tl;
+       char            buf[32];
+
+       foreach(tl, tlist)
+       {
+               TargetEntry *tle = lfirst(tl);
+               Resdom *resdom = tle->resdom;
+
+               nattrs++;
+               Assert(resdom->resno == nattrs);
+               if (resdom->resname)
+               {
+                       attrs = lappend(attrs, makeString(resdom->resname));
+                       continue;
+               }
+               if (tle->expr && IsA(tle->expr, Var))
+               {
+                       Var        *var = (Var *) tle->expr;
+
+                       /* varno/varattno won't be any good, but varnoold might be */
+                       if (var->varnoold > 0 && var->varnoold <= rtablelength)
+                       {
+                               RangeTblEntry *varrte = rt_fetch(var->varnoold, rtable);
+                               char *varname;
+
+                               varname = get_rte_attribute_name(varrte, var->varoattno);
+                               attrs = lappend(attrs, makeString(varname));
+                               continue;
+                       }
+               }
+               /* Fallback if can't get name */
+               snprintf(buf, sizeof(buf), "?column%d?", resdom->resno);
+               attrs = lappend(attrs, makeString(pstrdup(buf)));
+       }
+
+       rte->rtekind = RTE_SPECIAL;     /* XXX */
+       rte->relid = InvalidOid;
+       rte->eref = makeAlias(name, attrs);
+       rte->inh = false;
+       rte->inFromCl = true;
+
+       return (Node *) rte;
+}
+
+/* ----------
  * make_ruledef                        - reconstruct the CREATE RULE command
  *                               for a given pg_rewrite tuple
  * ----------
@@ -628,6 +807,7 @@ deparse_expression(Node *expr, List *rangetables, bool forceprefix)
 static void
 make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
 {
+       char       *rulename;
        char            ev_type;
        Oid                     ev_class;
        int2            ev_attr;
@@ -636,24 +816,38 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
        char       *ev_action;
        List       *actions = NIL;
        int                     fno;
+       Datum           dat;
        bool            isnull;
 
-       /* ----------
+       /*
         * Get the attribute values from the rules tuple
-        * ----------
         */
+       fno = SPI_fnumber(rulettc, "rulename");
+       dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       Assert(!isnull);
+       rulename = NameStr(*(DatumGetName(dat)));
+
        fno = SPI_fnumber(rulettc, "ev_type");
-       ev_type = (char) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       Assert(!isnull);
+       ev_type = DatumGetChar(dat);
 
        fno = SPI_fnumber(rulettc, "ev_class");
-       ev_class = (Oid) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       Assert(!isnull);
+       ev_class = DatumGetObjectId(dat);
 
        fno = SPI_fnumber(rulettc, "ev_attr");
-       ev_attr = (int2) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       Assert(!isnull);
+       ev_attr = DatumGetInt16(dat);
 
        fno = SPI_fnumber(rulettc, "is_instead");
-       is_instead = (bool) SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
+       Assert(!isnull);
+       is_instead = DatumGetBool(dat);
 
+       /* these could be nulls */
        fno = SPI_fnumber(rulettc, "ev_qual");
        ev_qual = SPI_getvalue(ruletup, rulettc, fno);
 
@@ -663,9 +857,8 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
                actions = (List *) stringToNode(ev_action);
 
 
-       /* ----------
+       /*
         * Build the rules definition text
-        * ----------
         */
        appendStringInfo(buf, "CREATE RULE %s AS ON ",
                                         quote_identifier(rulename));
@@ -697,11 +890,11 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
 
        /* The relation the rule is fired on */
        appendStringInfo(buf, " TO %s",
-                                        quote_identifier(get_relation_name(ev_class)));
+                                        quote_identifier(get_rel_name(ev_class)));
        if (ev_attr > 0)
                appendStringInfo(buf, ".%s",
-                                                quote_identifier(get_attribute_name(ev_class,
-                                                                                                                        ev_attr)));
+                                         quote_identifier(get_relid_attribute_name(ev_class,
+                                                                                                                         ev_attr)));
 
        /* If the rule has an event qualification, add it */
        if (ev_qual == NULL)
@@ -711,15 +904,32 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
                Node       *qual;
                Query      *query;
                deparse_context context;
+               deparse_namespace dpns;
 
                appendStringInfo(buf, " WHERE ");
 
                qual = stringToNode(ev_qual);
+
+               /*
+                * We need to make a context for recognizing any Vars in the qual
+                * (which can only be references to OLD and NEW).  Use the rtable
+                * of the first query in the action list for this purpose.
+                */
                query = (Query *) lfirst(actions);
 
+               /*
+                * If the action is INSERT...SELECT, OLD/NEW have been pushed
+                * down into the SELECT, and that's what we need to look at.
+                * (Ugly kluge ... try to fix this when we redesign querytrees.)
+                */
+               query = getInsertSelectQuery(query, NULL);
+
                context.buf = buf;
-               context.rangetables = lcons(query->rtable, NIL);
+               context.namespaces = makeList1(&dpns);
                context.varprefix = (length(query->rtable) != 1);
+               dpns.rtable = query->rtable;
+               dpns.outer_varno = dpns.inner_varno = 0;
+               dpns.outer_rte = dpns.inner_rte = NULL;
 
                get_rule_expr(qual, &context);
        }
@@ -782,9 +992,8 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
        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);
@@ -814,7 +1023,8 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
 
        query = (Query *) lfirst(actions);
 
-       if (ev_type != '1' || ev_attr >= 0 || !is_instead || strcmp(ev_qual, "<>"))
+       if (ev_type != '1' || ev_attr >= 0 || !is_instead ||
+               strcmp(ev_qual, "<>") != 0)
        {
                appendStringInfo(buf, "Not a view");
                return;
@@ -832,14 +1042,18 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
  * ----------
  */
 static void
-get_query_def(Query *query, StringInfo buf, List *parentrtables)
+get_query_def(Query *query, StringInfo buf, List *parentnamespace)
 {
        deparse_context context;
+       deparse_namespace dpns;
 
        context.buf = buf;
-       context.rangetables = lcons(query->rtable, parentrtables);
-       context.varprefix = (parentrtables != NIL ||
+       context.namespaces = lcons(&dpns, parentnamespace);
+       context.varprefix = (parentnamespace != NIL ||
                                                 length(query->rtable) != 1);
+       dpns.rtable = query->rtable;
+       dpns.outer_varno = dpns.inner_varno = 0;
+       dpns.outer_rte = dpns.inner_rte = NULL;
 
        switch (query->commandType)
        {
@@ -863,9 +1077,13 @@ get_query_def(Query *query, StringInfo buf, List *parentrtables)
                        appendStringInfo(buf, "NOTHING");
                        break;
 
+               case CMD_UTILITY:
+                       get_utility_query_def(query, &context);
+                       break;
+
                default:
-                       elog(ERROR, "get_ruledef of %s: query command type %d not implemented yet",
-                                rulename, query->commandType);
+                       elog(ERROR, "get_query_def: unknown query command type %d",
+                                query->commandType);
                        break;
        }
 }
@@ -879,74 +1097,113 @@ static void
 get_select_query_def(Query *query, deparse_context *context)
 {
        StringInfo      buf = context->buf;
+       bool            force_colno;
        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 to know which and how many of the
-        * range table entries in the query are used in the target list
-        * or queries qualification
-        * ----------
+       /*
+        * If the Query node has a setOperations tree, then it's the top level
+        * of a UNION/INTERSECT/EXCEPT query; only the ORDER BY and LIMIT
+        * fields are interesting in the top query itself.
         */
-       rt_length = length(query->rtable);
-       rt_used = palloc(sizeof(bool) * rt_length);
-       for (i = 0; i < rt_length; i++)
+       if (query->setOperations)
        {
-               if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
-                       check_if_rte_used(query->qual, i + 1, 0) ||
-                       check_if_rte_used(query->havingQual, i + 1, 0))
-               {
-                       rt_used[i] = TRUE;
-                       rt_numused++;
-               }
-               else
-                       rt_used[i] = FALSE;
+               get_setop_query(query->setOperations, query, context);
+               /* ORDER BY clauses must be simple in this case */
+               force_colno = true;
+       }
+       else
+       {
+               get_basic_select_query(query, context);
+               force_colno = false;
        }
 
-       /* ----------
-        * Now check if any of the used rangetable entries is different
-        * from *NEW* and *CURRENT*. If so we must provide the FROM clause
-        * later.
-        * ----------
-        */
-       i = 0;
-       foreach(l, query->rtable)
+       /* Add the ORDER BY clause if given */
+       if (query->sortClause != NIL)
        {
-               if (!rt_used[i++])
-                       continue;
+               appendStringInfo(buf, " ORDER BY ");
+               sep = "";
+               foreach(l, query->sortClause)
+               {
+                       SortClause *srt = (SortClause *) lfirst(l);
+                       char       *opname;
 
-               rte = (RangeTblEntry *) lfirst(l);
-               if (rte->ref == NULL)
-                       continue;
-               if (!strcmp(rte->ref->relname, "*NEW*"))
-                       continue;
-               if (!strcmp(rte->ref->relname, "*CURRENT*"))
-                       continue;
+                       appendStringInfo(buf, sep);
+                       get_rule_sortgroupclause(srt, query->targetList,
+                                                                        force_colno, context);
+                       opname = get_opname(srt->sortop);
+                       if (strcmp(opname, "<") != 0)
+                       {
+                               if (strcmp(opname, ">") == 0)
+                                       appendStringInfo(buf, " DESC");
+                               else
+                                       appendStringInfo(buf, " USING %s", opname);
+                       }
+                       sep = ", ";
+               }
+       }
 
-               rt_constonly = FALSE;
-               break;
+       /* Add the LIMIT clause if given */
+       if (query->limitOffset != NULL)
+       {
+               appendStringInfo(buf, " OFFSET ");
+               get_rule_expr(query->limitOffset, context);
+       }
+       if (query->limitCount != NULL)
+       {
+               appendStringInfo(buf, " LIMIT ");
+               if (IsA(query->limitCount, Const) &&
+                       ((Const *) query->limitCount)->constisnull)
+                       appendStringInfo(buf, "ALL");
+               else
+                       get_rule_expr(query->limitCount, context);
        }
+}
+
+static void
+get_basic_select_query(Query *query, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+       char       *sep;
+       List       *l;
 
-       /* ----------
+       /*
         * Build up the query string - first we say SELECT
-        * ----------
         */
        appendStringInfo(buf, "SELECT");
 
+       /* Add the DISTINCT clause if given */
+       if (query->distinctClause != NIL)
+       {
+               if (has_distinct_on_clause(query))
+               {
+                       appendStringInfo(buf, " DISTINCT ON (");
+                       sep = "";
+                       foreach(l, query->distinctClause)
+                       {
+                               SortClause *srt = (SortClause *) lfirst(l);
+
+                               appendStringInfo(buf, sep);
+                               get_rule_sortgroupclause(srt, query->targetList,
+                                                                                false, context);
+                               sep = ", ";
+                       }
+                       appendStringInfo(buf, ")");
+               }
+               else
+                       appendStringInfo(buf, " DISTINCT");
+       }
+
        /* Then we tell what to select (the targetlist) */
        sep = " ";
        foreach(l, query->targetList)
        {
-               bool            tell_as = FALSE;
+               TargetEntry *tle = (TargetEntry *) lfirst(l);
+               bool            tell_as = false;
+
+               if (tle->resdom->resjunk)
+                       continue;                       /* ignore junk entries */
 
-               tle = (TargetEntry *) lfirst(l);
                appendStringInfo(buf, sep);
                sep = ", ";
 
@@ -955,16 +1212,16 @@ get_select_query_def(Query *query, deparse_context *context)
 
                /* Check if we must say AS ... */
                if (!IsA(tle->expr, Var))
-                       tell_as = strcmp(tle->resdom->resname, "?column?");
+                       tell_as = (strcmp(tle->resdom->resname, "?column?") != 0);
                else
                {
                        Var                *var = (Var *) (tle->expr);
+                       char       *refname;
                        char       *attname;
 
-                       rte = get_rte_for_var(var, context);
-                       attname = get_attribute_name(rte->relid, var->varattno);
-                       if (strcmp(attname, tle->resdom->resname))
-                               tell_as = TRUE;
+                       get_names_for_var(var, context, &refname, &attname);
+                       tell_as = (attname == NULL ||
+                                          strcmp(attname, tle->resdom->resname) != 0);
                }
 
                /* and do if so */
@@ -973,87 +1230,116 @@ get_select_query_def(Query *query, deparse_context *context)
                                                         quote_identifier(tle->resdom->resname));
        }
 
-       /* If we need other tables than *NEW* or *CURRENT* add the FROM clause */
-       if (!rt_constonly && rt_numused > 0)
+       /* Add the FROM clause if needed */
+       get_from_clause(query, context);
+
+       /* Add the WHERE clause if given */
+       if (query->jointree->quals != NULL)
        {
-               sep = " FROM ";
-               i = 0;
-               foreach(l, query->rtable)
-               {
-                       if (rt_used[i++])
-                       {
-                               rte = (RangeTblEntry *) lfirst(l);
+               appendStringInfo(buf, " WHERE ");
+               get_rule_expr(query->jointree->quals, context);
+       }
 
-                               if (rte->ref == NULL)
-                                       continue;
-                               if (!strcmp(rte->ref->relname, "*NEW*"))
-                                       continue;
-                               if (!strcmp(rte->ref->relname, "*CURRENT*"))
-                                       continue;
+       /* Add the GROUP BY clause if given */
+       if (query->groupClause != NULL)
+       {
+               appendStringInfo(buf, " GROUP BY ");
+               sep = "";
+               foreach(l, query->groupClause)
+               {
+                       GroupClause *grp = (GroupClause *) lfirst(l);
 
-                               appendStringInfo(buf, sep);
-                               sep = ", ";
-                               appendStringInfo(buf, "%s%s",
-                                                                quote_identifier(rte->relname),
-                                                                inherit_marker(rte));
-
-                               /*
-                                * NOTE: SQL92 says you can't write column aliases unless
-                                * you write a table alias --- so, if there's an alias
-                                * list, make sure we emit a table alias even if it's the
-                                * same as the table's real name.
-                                */
-                               if ((rte->ref != NULL)
-                                       && ((strcmp(rte->relname, rte->ref->relname) != 0)
-                                               || (rte->ref->attrs != NIL)))
-                               {
-                                       appendStringInfo(buf, " %s",
-                                                                        quote_identifier(rte->ref->relname));
-                                       if (rte->ref->attrs != NIL)
-                                       {
-                                               List       *col;
-
-                                               appendStringInfo(buf, " (");
-                                               foreach(col, rte->ref->attrs)
-                                               {
-                                                       if (col != rte->ref->attrs)
-                                                               appendStringInfo(buf, ", ");
-                                                       appendStringInfo(buf, "%s",
-                                                                 quote_identifier(strVal(lfirst(col))));
-                                               }
-                                               appendStringInfoChar(buf, ')');
-                                       }
-                               }
-                       }
+                       appendStringInfo(buf, sep);
+                       get_rule_sortgroupclause(grp, query->targetList,
+                                                                        false, context);
+                       sep = ", ";
                }
        }
 
-       /* Add the WHERE clause if given */
-       if (query->qual != NULL)
+       /* Add the HAVING clause if given */
+       if (query->havingQual != NULL)
        {
-               appendStringInfo(buf, " WHERE ");
-               get_rule_expr(query->qual, context);
+               appendStringInfo(buf, " HAVING ");
+               get_rule_expr(query->havingQual, context);
        }
+}
 
-       /* Add the GROUP BY CLAUSE */
-       if (query->groupClause != NULL)
+static void
+get_setop_query(Node *setOp, Query *query, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+
+       if (IsA(setOp, RangeTblRef))
        {
-               appendStringInfo(buf, " GROUP BY ");
-               sep = "";
-               foreach(l, query->groupClause)
-               {
-                       GroupClause *grp = (GroupClause *) lfirst(l);
-                       Node       *groupexpr;
+               RangeTblRef *rtr = (RangeTblRef *) setOp;
+               RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
+               Query      *subquery = rte->subquery;
 
-                       groupexpr = get_sortgroupclause_expr(grp,
-                                                                                                query->targetList);
-                       appendStringInfo(buf, sep);
-                       get_rule_expr(groupexpr, context);
-                       sep = ", ";
+               Assert(subquery != NULL);
+               get_query_def(subquery, buf, context->namespaces);
+       }
+       else if (IsA(setOp, SetOperationStmt))
+       {
+               SetOperationStmt *op = (SetOperationStmt *) setOp;
+
+               appendStringInfo(buf, "((");
+               get_setop_query(op->larg, query, context);
+               switch (op->op)
+               {
+                       case SETOP_UNION:
+                               appendStringInfo(buf, ") UNION ");
+                               break;
+                       case SETOP_INTERSECT:
+                               appendStringInfo(buf, ") INTERSECT ");
+                               break;
+                       case SETOP_EXCEPT:
+                               appendStringInfo(buf, ") EXCEPT ");
+                               break;
+                       default:
+                               elog(ERROR, "get_setop_query: unexpected set op %d",
+                                        (int) op->op);
                }
+               if (op->all)
+                       appendStringInfo(buf, "ALL (");
+               else
+                       appendStringInfo(buf, "(");
+               get_setop_query(op->rarg, query, context);
+               appendStringInfo(buf, "))");
+       }
+       else
+       {
+               elog(ERROR, "get_setop_query: unexpected node %d",
+                        (int) nodeTag(setOp));
        }
 }
 
+/*
+ * Display a sort/group clause.
+ */
+static void
+get_rule_sortgroupclause(SortClause *srt, List *tlist, bool force_colno,
+                                                deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+       TargetEntry *tle;
+       Node       *expr;
+
+       tle = get_sortgroupclause_tle(srt, tlist);
+       expr = tle->expr;
+
+       /*
+        * Use column-number form if requested by caller or if expression is a
+        * constant --- a constant is ambiguous (and will be misinterpreted by
+        * findTargetlistEntry()) if we dump it explicitly.
+        */
+       if (force_colno || (expr && IsA(expr, Const)))
+       {
+               Assert(!tle->resdom->resjunk);
+               appendStringInfo(buf, "%d", tle->resdom->resno);
+       }
+       else
+               get_rule_expr(expr, context);
+}
 
 /* ----------
  * get_insert_query_def                        - Parse back an INSERT parsetree
@@ -1063,68 +1349,40 @@ static void
 get_insert_query_def(Query *query, deparse_context *context)
 {
        StringInfo      buf = context->buf;
-       char       *sep;
-       TargetEntry *tle;
+       RangeTblEntry *select_rte = NULL;
        RangeTblEntry *rte;
-       bool       *rt_used;
-       int                     rt_length;
-       int                     rt_numused = 0;
-       bool            rt_constonly = TRUE;
-       int                     i;
+       char       *sep;
        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.
-        * ----------
+       /*
+        * If it's an INSERT ... SELECT there will be a single subquery RTE
+        * for the 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((Node *) (query->targetList), i + 1, 0) ||
-                       check_if_rte_used(query->qual, i + 1, 0) ||
-                       check_if_rte_used(query->havingQual, i + 1, 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 (rte->ref == NULL)
-                       continue;
-               if (!strcmp(rte->ref->relname, "*NEW*"))
-                       continue;
-               if (!strcmp(rte->ref->relname, "*CURRENT*"))
+               if (rte->subquery == NULL)
                        continue;
-
-               rt_constonly = FALSE;
-               break;
+               if (select_rte)
+                       elog(ERROR, "get_insert_query_def: too many RTEs in INSERT!");
+               select_rte = rte;
        }
 
-       /* ----------
+       /*
         * Start the query with INSERT INTO relname
-        * ----------
         */
        rte = rt_fetch(query->resultRelation, query->rtable);
        appendStringInfo(buf, "INSERT INTO %s",
-                                        quote_identifier(rte->relname));
+                                        quote_identifier(rte->eref->aliasname));
 
-       /* Add the target list */
+       /* Add the insert-column-names list */
        sep = " (";
        foreach(l, query->targetList)
        {
-               tle = (TargetEntry *) lfirst(l);
+               TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+               if (tle->resdom->resjunk)
+                       continue;                       /* ignore junk entries */
 
                appendStringInfo(buf, sep);
                sep = ", ";
@@ -1133,13 +1391,16 @@ get_insert_query_def(Query *query, deparse_context *context)
        appendStringInfo(buf, ") ");
 
        /* Add the VALUES or the SELECT */
-       if (rt_constonly && query->qual == NULL)
+       if (select_rte == NULL)
        {
                appendStringInfo(buf, "VALUES (");
                sep = "";
                foreach(l, query->targetList)
                {
-                       tle = (TargetEntry *) lfirst(l);
+                       TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+                       if (tle->resdom->resjunk)
+                               continue;               /* ignore junk entries */
 
                        appendStringInfo(buf, sep);
                        sep = ", ";
@@ -1148,7 +1409,7 @@ get_insert_query_def(Query *query, deparse_context *context)
                appendStringInfoChar(buf, ')');
        }
        else
-               get_select_query_def(query, context);
+               get_query_def(select_rte->subquery, buf, NIL);
 }
 
 
@@ -1161,37 +1422,48 @@ get_update_query_def(Query *query, deparse_context *context)
 {
        StringInfo      buf = context->buf;
        char       *sep;
-       TargetEntry *tle;
        RangeTblEntry *rte;
        List       *l;
 
-       /* ----------
+       /*
         * Start the query with UPDATE relname SET
-        * ----------
         */
        rte = rt_fetch(query->resultRelation, query->rtable);
        appendStringInfo(buf, "UPDATE %s%s SET ",
-                                        quote_identifier(rte->relname),
-                                        inherit_marker(rte));
+                                        only_marker(rte),
+                                        quote_identifier(rte->eref->aliasname));
 
        /* Add the comma separated list of 'attname = value' */
        sep = "";
        foreach(l, query->targetList)
        {
-               tle = (TargetEntry *) lfirst(l);
+               TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+               if (tle->resdom->resjunk)
+                       continue;                       /* ignore junk entries */
 
                appendStringInfo(buf, sep);
                sep = ", ";
-               appendStringInfo(buf, "%s = ",
-                                                quote_identifier(tle->resdom->resname));
+
+               /*
+                * If the update expression is an array assignment, we mustn't put
+                * out "attname =" here; it will come out of the display of the
+                * ArrayRef node instead.
+                */
+               if (!tleIsArrayAssign(tle))
+                       appendStringInfo(buf, "%s = ",
+                                                        quote_identifier(tle->resdom->resname));
                get_tle_expr(tle, context);
        }
 
+       /* Add the FROM clause if needed */
+       get_from_clause(query, context);
+
        /* Finally add a WHERE clause if given */
-       if (query->qual != NULL)
+       if (query->jointree->quals != NULL)
        {
                appendStringInfo(buf, " WHERE ");
-               get_rule_expr(query->qual, context);
+               get_rule_expr(query->jointree->quals, context);
        }
 }
 
@@ -1206,39 +1478,95 @@ get_delete_query_def(Query *query, deparse_context *context)
        StringInfo      buf = context->buf;
        RangeTblEntry *rte;
 
-       /* ----------
+       /*
         * Start the query with DELETE FROM relname
-        * ----------
         */
        rte = rt_fetch(query->resultRelation, query->rtable);
        appendStringInfo(buf, "DELETE FROM %s%s",
-                                        quote_identifier(rte->relname),
-                                        inherit_marker(rte));
+                                        only_marker(rte),
+                                        quote_identifier(rte->eref->aliasname));
 
        /* Add a WHERE clause if given */
-       if (query->qual != NULL)
+       if (query->jointree->quals != NULL)
        {
                appendStringInfo(buf, " WHERE ");
-               get_rule_expr(query->qual, context);
+               get_rule_expr(query->jointree->quals, context);
+       }
+}
+
+
+/* ----------
+ * get_utility_query_def                       - Parse back a UTILITY parsetree
+ * ----------
+ */
+static void
+get_utility_query_def(Query *query, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+
+       if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
+       {
+               NotifyStmt *stmt = (NotifyStmt *) query->utilityStmt;
+
+               appendStringInfo(buf, "NOTIFY %s",
+                                                quote_identifier(stmt->relation->relname));
        }
+       else
+               elog(ERROR, "get_utility_query_def: unexpected statement type");
 }
 
+
 /*
- * Find the RTE referenced by a (possibly nonlocal) Var.
+ * Get the relation refname and attname for a (possibly nonlocal) Var.
+ *
+ * refname will be returned as NULL if the Var references an unnamed join.
+ * In this case the Var *must* be displayed without any qualification.
+ *
+ * attname will be returned as NULL if the Var represents a whole tuple
+ * of the relation.
  */
-static RangeTblEntry *
-get_rte_for_var(Var *var, deparse_context *context)
+static void
+get_names_for_var(Var *var, deparse_context *context,
+                                 char **refname, char **attname)
 {
-       List       *rtlist = context->rangetables;
+       List       *nslist = context->namespaces;
        int                     sup = var->varlevelsup;
+       deparse_namespace *dpns;
+       RangeTblEntry *rte;
 
-       while (sup-- > 0)
-               rtlist = lnext(rtlist);
+       /* Find appropriate nesting depth */
+       while (sup-- > 0 && nslist != NIL)
+               nslist = lnext(nslist);
+       if (nslist == NIL)
+               elog(ERROR, "get_names_for_var: bogus varlevelsup %d",
+                        var->varlevelsup);
+       dpns = (deparse_namespace *) lfirst(nslist);
+
+       /* Find the relevant RTE */
+       if (var->varno >= 1 && var->varno <= length(dpns->rtable))
+               rte = rt_fetch(var->varno, dpns->rtable);
+       else if (var->varno == dpns->outer_varno)
+               rte = dpns->outer_rte;
+       else if (var->varno == dpns->inner_varno)
+               rte = dpns->inner_rte;
+       else
+               rte = NULL;
+       if (rte == NULL)
+               elog(ERROR, "get_names_for_var: bogus varno %d",
+                        var->varno);
+
+       /* Emit results */
+       if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
+               *refname = NULL;
+       else
+               *refname = rte->eref->aliasname;
 
-       return rt_fetch(var->varno, (List *) lfirst(rtlist));
+       if (var->varattno == InvalidAttrNumber)
+               *attname = NULL;
+       else
+               *attname = get_rte_attribute_name(rte, var->varattno);
 }
 
-
 /* ----------
  * get_rule_expr                       - Parse back an expression
  * ----------
@@ -1251,14 +1579,13 @@ get_rule_expr(Node *node, deparse_context *context)
        if (node == NULL)
                return;
 
-       /* ----------
+       /*
         * Each level of get_rule_expr must emit an indivisible term
-        * (parenthesized if necessary) to ensure result is reparsed into
-        * the same expression tree.
+        * (parenthesized if necessary) to ensure result is reparsed into the
+        * same expression tree.
         *
         * There might be some work left here to support additional node types.
         * Can we ever see Param nodes here?
-        * ----------
         */
        switch (nodeTag(node))
        {
@@ -1269,24 +1596,24 @@ get_rule_expr(Node *node, deparse_context *context)
                case T_Var:
                        {
                                Var                *var = (Var *) node;
-                               RangeTblEntry *rte = get_rte_for_var(var, context);
+                               char       *refname;
+                               char       *attname;
 
-                               if (context->varprefix)
+                               get_names_for_var(var, context, &refname, &attname);
+                               if (refname && (context->varprefix || attname == NULL))
                                {
-                                       if (rte->ref == NULL)
-                                               appendStringInfo(buf, "%s.",
-                                                                                quote_identifier(rte->relname));
-                                       else if (!strcmp(rte->ref->relname, "*NEW*"))
-                                               appendStringInfo(buf, "new.");
-                                       else if (!strcmp(rte->ref->relname, "*CURRENT*"))
-                                               appendStringInfo(buf, "old.");
+                                       if (strcmp(refname, "*NEW*") == 0)
+                                               appendStringInfo(buf, "new");
+                                       else if (strcmp(refname, "*OLD*") == 0)
+                                               appendStringInfo(buf, "old");
                                        else
-                                               appendStringInfo(buf, "%s.",
-                                                                       quote_identifier(rte->ref->relname));
+                                               appendStringInfo(buf, "%s",
+                                                                                quote_identifier(refname));
+                                       if (attname)
+                                               appendStringInfoChar(buf, '.');
                                }
-                               appendStringInfo(buf, "%s",
-                                                 quote_identifier(get_attribute_name(rte->relid,
-                                                                                                               var->varattno)));
+                               if (attname)
+                                       appendStringInfo(buf, "%s", quote_identifier(attname));
                        }
                        break;
 
@@ -1295,9 +1622,8 @@ get_rule_expr(Node *node, deparse_context *context)
                                Expr       *expr = (Expr *) node;
                                List       *args = expr->args;
 
-                               /* ----------
+                               /*
                                 * Expr nodes have to be handled a bit detailed
-                                * ----------
                                 */
                                switch (expr->opType)
                                {
@@ -1318,10 +1644,11 @@ get_rule_expr(Node *node, deparse_context *context)
                                                        HeapTuple       tp;
                                                        Form_pg_operator optup;
 
-                                                       tp = SearchSysCacheTuple(OPEROID,
-                                                                                                 ObjectIdGetDatum(opno),
-                                                                                                        0, 0, 0);
-                                                       Assert(HeapTupleIsValid(tp));
+                                                       tp = SearchSysCache(OPEROID,
+                                                                                               ObjectIdGetDatum(opno),
+                                                                                               0, 0, 0);
+                                                       if (!HeapTupleIsValid(tp))
+                                                               elog(ERROR, "cache lookup for operator %u failed", opno);
                                                        optup = (Form_pg_operator) GETSTRUCT(tp);
                                                        switch (optup->oprkind)
                                                        {
@@ -1340,10 +1667,15 @@ get_rule_expr(Node *node, deparse_context *context)
                                                                default:
                                                                        elog(ERROR, "get_rule_expr: bogus oprkind");
                                                        }
+                                                       ReleaseSysCache(tp);
                                                }
                                                appendStringInfoChar(buf, ')');
                                                break;
 
+                                       case FUNC_EXPR:
+                                               get_func_expr((Expr *) node, context);
+                                               break;
+
                                        case OR_EXPR:
                                                appendStringInfoChar(buf, '(');
                                                get_rule_expr((Node *) lfirst(args), context);
@@ -1372,8 +1704,13 @@ get_rule_expr(Node *node, deparse_context *context)
                                                appendStringInfoChar(buf, ')');
                                                break;
 
-                                       case FUNC_EXPR:
-                                               get_func_expr((Expr *) node, context);
+                                       case SUBPLAN_EXPR:
+                                               /*
+                                                * We cannot see an already-planned subplan in rule
+                                                * deparsing, only while EXPLAINing a query plan.
+                                                * For now, just punt.
+                                                */
+                                               appendStringInfo(buf, "(subplan)");
                                                break;
 
                                        default:
@@ -1384,18 +1721,7 @@ get_rule_expr(Node *node, deparse_context *context)
                        break;
 
                case T_Aggref:
-                       {
-                               Aggref     *aggref = (Aggref *) node;
-
-                               appendStringInfo(buf, "%s(%s",
-                                                                quote_identifier(aggref->aggname),
-                                                                aggref->aggdistinct ? "DISTINCT " : "");
-                               if (aggref->aggstar)
-                                       appendStringInfo(buf, "*");
-                               else
-                                       get_rule_expr(aggref->target, context);
-                               appendStringInfoChar(buf, ')');
-                       }
+                       get_agg_expr((Aggref *) node, context);
                        break;
 
                case T_Iter:
@@ -1405,10 +1731,21 @@ get_rule_expr(Node *node, deparse_context *context)
                case T_ArrayRef:
                        {
                                ArrayRef   *aref = (ArrayRef *) node;
+                               bool            savevarprefix = context->varprefix;
                                List       *lowlist;
                                List       *uplist;
 
+                               /*
+                                * If we are doing UPDATE array[n] = expr, we need to
+                                * suppress any prefix on the array name.  Currently, that
+                                * is the only context in which we will see a non-null
+                                * refassgnexpr --- but someday a smarter test may be
+                                * needed.
+                                */
+                               if (aref->refassgnexpr)
+                                       context->varprefix = false;
                                get_rule_expr(aref->refexpr, context);
+                               context->varprefix = savevarprefix;
                                lowlist = aref->reflowerindexpr;
                                foreach(uplist, aref->refupperindexpr)
                                {
@@ -1422,7 +1759,46 @@ get_rule_expr(Node *node, deparse_context *context)
                                        get_rule_expr((Node *) lfirst(uplist), context);
                                        appendStringInfo(buf, "]");
                                }
-                               /* XXX need to do anything with refassgnexpr? */
+                               if (aref->refassgnexpr)
+                               {
+                                       appendStringInfo(buf, " = ");
+                                       get_rule_expr(aref->refassgnexpr, context);
+                               }
+                       }
+                       break;
+
+               case T_FieldSelect:
+                       {
+                               FieldSelect *fselect = (FieldSelect *) node;
+                               Oid                     argType = exprType(fselect->arg);
+                               HeapTuple       typetup;
+                               Form_pg_type typeStruct;
+                               Oid                     typrelid;
+                               char       *fieldname;
+
+                               /* lookup arg type and get the field name */
+                               typetup = SearchSysCache(TYPEOID,
+                                                                                ObjectIdGetDatum(argType),
+                                                                                0, 0, 0);
+                               if (!HeapTupleIsValid(typetup))
+                                       elog(ERROR, "cache lookup of type %u failed",
+                                                argType);
+                               typeStruct = (Form_pg_type) GETSTRUCT(typetup);
+                               typrelid = typeStruct->typrelid;
+                               if (!OidIsValid(typrelid))
+                                       elog(ERROR, "Argument type %s of FieldSelect is not a tuple type",
+                                                format_type_be(argType));
+                               ReleaseSysCache(typetup);
+                               fieldname = get_relid_attribute_name(typrelid,
+                                                                                                        fselect->fieldnum);
+                               /*
+                                * If the argument is simple enough, we could emit
+                                * arg.fieldname, but most cases where FieldSelect is used
+                                * are *not* simple.  So, always use parenthesized syntax.
+                                */
+                               appendStringInfoChar(buf, '(');
+                               get_rule_expr(fselect->arg, context);
+                               appendStringInfo(buf, ").%s", quote_identifier(fieldname));
                        }
                        break;
 
@@ -1435,9 +1811,9 @@ get_rule_expr(Node *node, deparse_context *context)
 
                                appendStringInfoChar(buf, '(');
                                get_rule_expr(relabel->arg, context);
-                               typetup = SearchSysCacheTuple(TYPEOID,
+                               typetup = SearchSysCache(TYPEOID,
                                                                   ObjectIdGetDatum(relabel->resulttype),
-                                                                                         0, 0, 0);
+                                                                                0, 0, 0);
                                if (!HeapTupleIsValid(typetup))
                                        elog(ERROR, "cache lookup of type %u failed",
                                                 relabel->resulttype);
@@ -1445,6 +1821,7 @@ get_rule_expr(Node *node, deparse_context *context)
                                extval = pstrdup(NameStr(typeStruct->typname));
                                appendStringInfo(buf, ")::%s", quote_identifier(extval));
                                pfree(extval);
+                               ReleaseSysCache(typetup);
                        }
                        break;
 
@@ -1469,14 +1846,88 @@ get_rule_expr(Node *node, deparse_context *context)
                        }
                        break;
 
+               case T_NullTest:
+                       {
+                               NullTest   *ntest = (NullTest *) node;
+
+                               appendStringInfo(buf, "(");
+                               get_rule_expr(ntest->arg, context);
+                               switch (ntest->nulltesttype)
+                               {
+                                       case IS_NULL:
+                                               appendStringInfo(buf, " IS NULL)");
+                                               break;
+                                       case IS_NOT_NULL:
+                                               appendStringInfo(buf, " IS NOT NULL)");
+                                               break;
+                                       default:
+                                               elog(ERROR, "get_rule_expr: unexpected nulltesttype %d",
+                                                        (int) ntest->nulltesttype);
+                               }
+                       }
+                       break;
+
+               case T_BooleanTest:
+                       {
+                               BooleanTest *btest = (BooleanTest *) node;
+
+                               appendStringInfo(buf, "(");
+                               get_rule_expr(btest->arg, context);
+                               switch (btest->booltesttype)
+                               {
+                                       case IS_TRUE:
+                                               appendStringInfo(buf, " IS TRUE)");
+                                               break;
+                                       case IS_NOT_TRUE:
+                                               appendStringInfo(buf, " IS NOT TRUE)");
+                                               break;
+                                       case IS_FALSE:
+                                               appendStringInfo(buf, " IS FALSE)");
+                                               break;
+                                       case IS_NOT_FALSE:
+                                               appendStringInfo(buf, " IS NOT FALSE)");
+                                               break;
+                                       case IS_UNKNOWN:
+                                               appendStringInfo(buf, " IS UNKNOWN)");
+                                               break;
+                                       case IS_NOT_UNKNOWN:
+                                               appendStringInfo(buf, " IS NOT UNKNOWN)");
+                                               break;
+                                       default:
+                                               elog(ERROR, "get_rule_expr: unexpected booltesttype %d",
+                                                        (int) btest->booltesttype);
+                               }
+                       }
+                       break;
+
                case T_SubLink:
                        get_sublink_expr(node, context);
                        break;
 
+               case T_Param:
+                       {
+                               Param      *param = (Param *) node;
+
+                               switch (param->paramkind)
+                               {
+                                       case PARAM_NAMED:
+                                       case PARAM_NEW:
+                                       case PARAM_OLD:
+                                               appendStringInfo(buf, "$%s", param->paramname);
+                                               break;
+                                       case PARAM_NUM:
+                                       case PARAM_EXEC:
+                                               appendStringInfo(buf, "$%d", param->paramid);
+                                               break;
+                                       default:
+                                               appendStringInfo(buf, "(param)");
+                                               break;
+                               }
+                       }
+                       break;
+
                default:
-                       printf("\n%s\n", nodeToString(node));
-                       elog(ERROR, "get_ruledef of %s: unknown node type %d in get_rule_expr()",
-                                rulename, nodeTag(node));
+                       elog(ERROR, "get_rule_expr: unknown node type %d", nodeTag(node));
                        break;
        }
 }
@@ -1491,6 +1942,7 @@ get_func_expr(Expr *expr, deparse_context *context)
 {
        StringInfo      buf = context->buf;
        Func       *func = (Func *) (expr->oper);
+       Oid                     funcoid = func->funcid;
        HeapTuple       proctup;
        Form_pg_proc procStruct;
        char       *proname;
@@ -1501,36 +1953,14 @@ get_func_expr(Expr *expr, deparse_context *context)
        /*
         * Get the functions pg_proc tuple
         */
-       proctup = SearchSysCacheTuple(PROCOID,
-                                                                 ObjectIdGetDatum(func->funcid),
-                                                                 0, 0, 0);
+       proctup = SearchSysCache(PROCOID,
+                                                        ObjectIdGetDatum(funcoid),
+                                                        0, 0, 0);
        if (!HeapTupleIsValid(proctup))
-               elog(ERROR, "cache lookup for proc %u failed", func->funcid);
+               elog(ERROR, "cache lookup for proc %u failed", funcoid);
 
        procStruct = (Form_pg_proc) GETSTRUCT(proctup);
-       proname = pstrdup(NameStr(procStruct->proname));
-
-       /*
-        * nullvalue() and nonnullvalue() should get turned into special
-        * syntax
-        */
-       if (procStruct->pronargs == 1 && procStruct->proargtypes[0] == InvalidOid)
-       {
-               if (!strcmp(proname, "nullvalue"))
-               {
-                       appendStringInfoChar(buf, '(');
-                       get_rule_expr((Node *) lfirst(expr->args), context);
-                       appendStringInfo(buf, " ISNULL)");
-                       return;
-               }
-               if (!strcmp(proname, "nonnullvalue"))
-               {
-                       appendStringInfoChar(buf, '(');
-                       get_rule_expr((Node *) lfirst(expr->args), context);
-                       appendStringInfo(buf, " NOTNULL)");
-                       return;
-               }
-       }
+       proname = NameStr(procStruct->proname);
 
        /*
         * Check to see if function is a length-coercion function for some
@@ -1539,48 +1969,32 @@ get_func_expr(Expr *expr, deparse_context *context)
        if (exprIsLengthCoercion((Node *) expr, &coercedTypmod))
        {
                Node       *arg = lfirst(expr->args);
+               char       *typdesc;
 
                /*
-                * Strip off any RelabelType on the input, so we don't print
-                * redundancies like x::bpchar::char(8). XXX Are there any cases
-                * where this is a bad idea?
+                * Strip off any type coercions on the input, so we don't print
+                * redundancies like x::bpchar::character(8).
+                *
+                * XXX Are there any cases where this is a bad idea?
                 */
-               if (IsA(arg, RelabelType))
-                       arg = ((RelabelType *) arg)->arg;
+               arg = strip_type_coercion(arg, procStruct->prorettype);
+
                appendStringInfoChar(buf, '(');
                get_rule_expr(arg, context);
-               appendStringInfo(buf, ")::");
 
                /*
                 * Show typename with appropriate length decoration. Note that
-                * since exprIsLengthCoercion succeeded, the function name is the
-                * same as its output type name.
+                * since exprIsLengthCoercion succeeded, the function's output
+                * type is the right thing to report.  Also note we don't need
+                * to quote the result of format_type_with_typemod: it takes
+                * care of double-quoting any identifier that needs it.
                 */
-               if (strcmp(proname, "bpchar") == 0)
-               {
-                       if (coercedTypmod > (int32) VARHDRSZ)
-                               appendStringInfo(buf, "char(%d)", coercedTypmod - VARHDRSZ);
-                       else
-                               appendStringInfo(buf, "char");
-               }
-               else if (strcmp(proname, "varchar") == 0)
-               {
-                       if (coercedTypmod > (int32) VARHDRSZ)
-                               appendStringInfo(buf, "varchar(%d)", coercedTypmod - VARHDRSZ);
-                       else
-                               appendStringInfo(buf, "varchar");
-               }
-               else if (strcmp(proname, "numeric") == 0)
-               {
-                       if (coercedTypmod >= (int32) VARHDRSZ)
-                               appendStringInfo(buf, "numeric(%d,%d)",
-                                                        ((coercedTypmod - VARHDRSZ) >> 16) & 0xffff,
-                                                                (coercedTypmod - VARHDRSZ) & 0xffff);
-                       else
-                               appendStringInfo(buf, "numeric");
-               }
-               else
-                       appendStringInfo(buf, "%s", quote_identifier(proname));
+               typdesc = format_type_with_typemod(procStruct->prorettype,
+                                                                                  coercedTypmod);
+               appendStringInfo(buf, ")::%s", typdesc);
+               pfree(typdesc);
+
+               ReleaseSysCache(proctup);
                return;
        }
 
@@ -1596,6 +2010,120 @@ get_func_expr(Expr *expr, deparse_context *context)
                get_rule_expr((Node *) lfirst(l), context);
        }
        appendStringInfoChar(buf, ')');
+
+       ReleaseSysCache(proctup);
+}
+
+/* ----------
+ * get_agg_expr                        - Parse back an Aggref node
+ * ----------
+ */
+static void
+get_agg_expr(Aggref *aggref, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+       HeapTuple       proctup;
+       Form_pg_proc procStruct;
+       char       *proname;
+
+       /*
+        * Get the aggregate's pg_proc tuple
+        */
+       proctup = SearchSysCache(PROCOID,
+                                                        ObjectIdGetDatum(aggref->aggfnoid),
+                                                        0, 0, 0);
+       if (!HeapTupleIsValid(proctup))
+               elog(ERROR, "cache lookup for proc %u failed", aggref->aggfnoid);
+
+       procStruct = (Form_pg_proc) GETSTRUCT(proctup);
+       proname = NameStr(procStruct->proname);
+
+       /*
+        * Display it
+        */
+       appendStringInfo(buf, "%s(%s",
+                                        quote_identifier(proname),
+                                        aggref->aggdistinct ? "DISTINCT " : "");
+       if (aggref->aggstar)
+               appendStringInfo(buf, "*");
+       else
+               get_rule_expr(aggref->target, context);
+       appendStringInfoChar(buf, ')');
+
+       ReleaseSysCache(proctup);
+}
+
+
+/*
+ * strip_type_coercion
+ *             Strip any type coercions at the top of the given expression tree,
+ *             as long as they are coercions to the given datatype.
+ *
+ * A RelabelType node is always a type coercion.  A function call is also
+ * considered a type coercion if it has one argument and the function name
+ * is the same as the (internal) name of its result type.
+ *
+ * XXX It'd be better if the parsetree retained some explicit indication
+ * of the coercion, so we didn't need these heuristics.
+ */
+static Node *
+strip_type_coercion(Node *expr, Oid resultType)
+{
+       if (expr == NULL || exprType(expr) != resultType)
+               return expr;
+
+       if (IsA(expr, RelabelType))
+               return strip_type_coercion(((RelabelType *) expr)->arg, resultType);
+
+       if (IsA(expr, Expr) &&((Expr *) expr)->opType == FUNC_EXPR)
+       {
+               Func       *func;
+               HeapTuple       procTuple;
+               HeapTuple       typeTuple;
+               Form_pg_proc procStruct;
+               Form_pg_type typeStruct;
+
+               func = (Func *) (((Expr *) expr)->oper);
+               Assert(IsA(func, Func));
+               if (length(((Expr *) expr)->args) != 1)
+                       return expr;
+               /* Lookup the function in pg_proc */
+               procTuple = SearchSysCache(PROCOID,
+                                                                  ObjectIdGetDatum(func->funcid),
+                                                                  0, 0, 0);
+               if (!HeapTupleIsValid(procTuple))
+                       elog(ERROR, "cache lookup for proc %u failed", func->funcid);
+               procStruct = (Form_pg_proc) GETSTRUCT(procTuple);
+               /* Double-check func has one arg and correct result type */
+               if (procStruct->pronargs != 1 ||
+                       procStruct->prorettype != resultType)
+               {
+                       ReleaseSysCache(procTuple);
+                       return expr;
+               }
+               /* See if function has same name as its result type */
+               typeTuple = SearchSysCache(TYPEOID,
+                                                               ObjectIdGetDatum(procStruct->prorettype),
+                                                                  0, 0, 0);
+               if (!HeapTupleIsValid(typeTuple))
+                       elog(ERROR, "cache lookup for type %u failed",
+                                procStruct->prorettype);
+               typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
+               if (strncmp(NameStr(procStruct->proname),
+                                       NameStr(typeStruct->typname),
+                                       NAMEDATALEN) != 0)
+               {
+                       ReleaseSysCache(procTuple);
+                       ReleaseSysCache(typeTuple);
+                       return expr;
+               }
+               /* Okay, it is indeed a type-coercion function */
+               ReleaseSysCache(procTuple);
+               ReleaseSysCache(typeTuple);
+               return strip_type_coercion(lfirst(((Expr *) expr)->args), resultType);
+       }
+
+       return expr;
 }
 
 
@@ -1651,9 +2179,9 @@ get_const_expr(Const *constval, deparse_context *context)
        char       *extval;
        char       *valptr;
 
-       typetup = SearchSysCacheTuple(TYPEOID,
-                                                                 ObjectIdGetDatum(constval->consttype),
-                                                                 0, 0, 0);
+       typetup = SearchSysCache(TYPEOID,
+                                                        ObjectIdGetDatum(constval->consttype),
+                                                        0, 0, 0);
        if (!HeapTupleIsValid(typetup))
                elog(ERROR, "cache lookup of type %u failed", constval->consttype);
 
@@ -1661,7 +2189,6 @@ get_const_expr(Const *constval, deparse_context *context)
 
        if (constval->constisnull)
        {
-
                /*
                 * Always label the type of a NULL constant.  This not only
                 * prevents misdecisions about the type, but it ensures that our
@@ -1670,13 +2197,14 @@ get_const_expr(Const *constval, deparse_context *context)
                extval = pstrdup(NameStr(typeStruct->typname));
                appendStringInfo(buf, "NULL::%s", quote_identifier(extval));
                pfree(extval);
+               ReleaseSysCache(typetup);
                return;
        }
 
        extval = DatumGetCString(OidFunctionCall3(typeStruct->typoutput,
-                                                        constval->constvalue,
-                                                        ObjectIdGetDatum(typeStruct->typelem),
-                                                        Int32GetDatum(-1)));
+                                                                                         constval->constvalue,
+                                                                  ObjectIdGetDatum(typeStruct->typelem),
+                                                                                         Int32GetDatum(-1)));
 
        switch (constval->consttype)
        {
@@ -1704,7 +2232,7 @@ get_const_expr(Const *constval, deparse_context *context)
                                        appendStringInfoChar(buf, '\\');
                                        appendStringInfoChar(buf, ch);
                                }
-                               else if (ch >= 0 && ch < ' ')
+                               else if (((unsigned char) ch) < ((unsigned char) ' '))
                                        appendStringInfo(buf, "\\%03o", (int) ch);
                                else
                                        appendStringInfoChar(buf, ch);
@@ -1728,6 +2256,8 @@ get_const_expr(Const *constval, deparse_context *context)
                        pfree(extval);
                        break;
        }
+
+       ReleaseSysCache(typetup);
 }
 
 
@@ -1804,7 +2334,7 @@ get_sublink_expr(Node *node, deparse_context *context)
        if (need_paren)
                appendStringInfoChar(buf, '(');
 
-       get_query_def(query, buf, context->rangetables);
+       get_query_def(query, buf, context->namespaces);
 
        if (need_paren)
                appendStringInfo(buf, "))");
@@ -1812,6 +2342,236 @@ get_sublink_expr(Node *node, deparse_context *context)
                appendStringInfoChar(buf, ')');
 }
 
+
+/* ----------
+ * get_from_clause                     - Parse back a FROM clause
+ * ----------
+ */
+static void
+get_from_clause(Query *query, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+       char       *sep;
+       List       *l;
+
+       /*
+        * We use the query's jointree as a guide to what to print.  However,
+        * we must ignore auto-added RTEs that are marked not inFromCl. (These
+        * can only appear at the top level of the jointree, so it's
+        * sufficient to check here.) Also ignore the rule pseudo-RTEs for NEW
+        * and OLD.
+        */
+       sep = " FROM ";
+
+       foreach(l, query->jointree->fromlist)
+       {
+               Node       *jtnode = (Node *) lfirst(l);
+
+               if (IsA(jtnode, RangeTblRef))
+               {
+                       int                     varno = ((RangeTblRef *) jtnode)->rtindex;
+                       RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+                       if (!rte->inFromCl)
+                               continue;
+                       if (strcmp(rte->eref->aliasname, "*NEW*") == 0)
+                               continue;
+                       if (strcmp(rte->eref->aliasname, "*OLD*") == 0)
+                               continue;
+               }
+
+               appendStringInfo(buf, sep);
+               get_from_clause_item(jtnode, query, context);
+               sep = ", ";
+       }
+}
+
+static void
+get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+
+       if (IsA(jtnode, RangeTblRef))
+       {
+               int                     varno = ((RangeTblRef *) jtnode)->rtindex;
+               RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+               switch (rte->rtekind)
+               {
+                       case RTE_RELATION:
+                               /* Normal relation RTE */
+                               appendStringInfo(buf, "%s%s",
+                                                                only_marker(rte),
+                                                                quote_identifier(get_rel_name(rte->relid)));
+                               break;
+                       case RTE_SUBQUERY:
+                               /* Subquery RTE */
+                               appendStringInfoChar(buf, '(');
+                               get_query_def(rte->subquery, buf, context->namespaces);
+                               appendStringInfoChar(buf, ')');
+                               break;
+                       default:
+                               elog(ERROR, "unexpected rte kind %d", (int) rte->rtekind);
+                               break;
+               }
+               if (rte->alias != NULL)
+               {
+                       appendStringInfo(buf, " %s",
+                                                        quote_identifier(rte->alias->aliasname));
+                       if (rte->alias->colnames != NIL)
+                       {
+                               List       *col;
+
+                               appendStringInfo(buf, "(");
+                               foreach(col, rte->alias->colnames)
+                               {
+                                       if (col != rte->alias->colnames)
+                                               appendStringInfo(buf, ", ");
+                                       appendStringInfo(buf, "%s",
+                                                                 quote_identifier(strVal(lfirst(col))));
+                               }
+                               appendStringInfoChar(buf, ')');
+                       }
+               }
+       }
+       else if (IsA(jtnode, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) jtnode;
+
+               appendStringInfoChar(buf, '(');
+               get_from_clause_item(j->larg, query, context);
+               if (j->isNatural)
+                       appendStringInfo(buf, " NATURAL");
+               switch (j->jointype)
+               {
+                       case JOIN_INNER:
+                               if (j->quals)
+                                       appendStringInfo(buf, " JOIN ");
+                               else
+                                       appendStringInfo(buf, " CROSS JOIN ");
+                               break;
+                       case JOIN_LEFT:
+                               appendStringInfo(buf, " LEFT JOIN ");
+                               break;
+                       case JOIN_FULL:
+                               appendStringInfo(buf, " FULL JOIN ");
+                               break;
+                       case JOIN_RIGHT:
+                               appendStringInfo(buf, " RIGHT JOIN ");
+                               break;
+                       case JOIN_UNION:
+                               appendStringInfo(buf, " UNION JOIN ");
+                               break;
+                       default:
+                               elog(ERROR, "get_from_clause_item: unknown join type %d",
+                                        (int) j->jointype);
+               }
+               get_from_clause_item(j->rarg, query, context);
+               if (!j->isNatural)
+               {
+                       if (j->using)
+                       {
+                               List       *col;
+
+                               appendStringInfo(buf, " USING (");
+                               foreach(col, j->using)
+                               {
+                                       if (col != j->using)
+                                               appendStringInfo(buf, ", ");
+                                       appendStringInfo(buf, "%s",
+                                                                 quote_identifier(strVal(lfirst(col))));
+                               }
+                               appendStringInfoChar(buf, ')');
+                       }
+                       else if (j->quals)
+                       {
+                               appendStringInfo(buf, " ON (");
+                               get_rule_expr(j->quals, context);
+                               appendStringInfoChar(buf, ')');
+                       }
+               }
+               appendStringInfoChar(buf, ')');
+               /* Yes, it's correct to put alias after the right paren ... */
+               if (j->alias != NULL)
+               {
+                       appendStringInfo(buf, " %s",
+                                                        quote_identifier(j->alias->aliasname));
+                       if (j->alias->colnames != NIL)
+                       {
+                               List       *col;
+
+                               appendStringInfo(buf, "(");
+                               foreach(col, j->alias->colnames)
+                               {
+                                       if (col != j->alias->colnames)
+                                               appendStringInfo(buf, ", ");
+                                       appendStringInfo(buf, "%s",
+                                                                 quote_identifier(strVal(lfirst(col))));
+                               }
+                               appendStringInfoChar(buf, ')');
+                       }
+               }
+       }
+       else
+               elog(ERROR, "get_from_clause_item: unexpected node type %d",
+                        nodeTag(jtnode));
+}
+
+/* ----------
+ * get_opclass_name                    - fetch name of an index operator class
+ *
+ * The opclass name is appended (after a space) to buf.
+ *
+ * Output is suppressed if the opclass is the default for the given
+ * actual_datatype.  (If you don't want this behavior, just pass
+ * InvalidOid for actual_datatype.)
+ * ----------
+ */
+static void
+get_opclass_name(Oid opclass, Oid actual_datatype,
+                                StringInfo buf)
+{
+       HeapTuple       ht_opc;
+       Form_pg_opclass opcrec;
+
+       ht_opc = SearchSysCache(CLAOID,
+                                                       ObjectIdGetDatum(opclass),
+                                                       0, 0, 0);
+       if (!HeapTupleIsValid(ht_opc))
+               elog(ERROR, "cache lookup failed for opclass %u", opclass);
+       opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
+       if (actual_datatype != opcrec->opcintype || !opcrec->opcdefault)
+               appendStringInfo(buf, " %s",
+                                                quote_identifier(NameStr(opcrec->opcname)));
+       ReleaseSysCache(ht_opc);
+}
+
+/* ----------
+ * tleIsArrayAssign                    - check for array assignment
+ * ----------
+ */
+static bool
+tleIsArrayAssign(TargetEntry *tle)
+{
+       ArrayRef   *aref;
+
+       if (tle->expr == NULL || !IsA(tle->expr, ArrayRef))
+               return false;
+       aref = (ArrayRef *) tle->expr;
+       if (aref->refassgnexpr == NULL)
+               return false;
+
+       /*
+        * Currently, it should only be possible to see non-null refassgnexpr
+        * if we are indeed looking at an "UPDATE array[n] = expr" situation.
+        * So aref->refexpr ought to match the tle's target.
+        */
+       if (aref->refexpr == NULL || !IsA(aref->refexpr, Var) ||
+               ((Var *) aref->refexpr)->varattno != tle->resdom->resno)
+               elog(WARNING, "tleIsArrayAssign: I'm confused ...");
+       return true;
+}
+
 /* ----------
  * quote_identifier                    - Quote an identifier only if needed
  *
@@ -1819,10 +2579,9 @@ get_sublink_expr(Node *node, deparse_context *context)
  * space-wasteful but well worth it for notational simplicity.
  * ----------
  */
-static char *
-quote_identifier(char *ident)
+const char *
+quote_identifier(const char *ident)
 {
-
        /*
         * Can avoid quoting if ident starts with a lowercase letter and
         * contains only lowercase letters, digits, and underscores, *and* is
@@ -1838,7 +2597,7 @@ quote_identifier(char *ident)
        safe = (ident[0] >= 'a' && ident[0] <= 'z');
        if (safe)
        {
-               char       *ptr;
+               const char *ptr;
 
                for (ptr = ident + 1; *ptr; ptr++)
                {
@@ -1854,17 +2613,16 @@ quote_identifier(char *ident)
 
        if (safe)
        {
-
                /*
                 * Check for keyword.  This test is overly strong, since many of
                 * the "keywords" known to the parser are usable as column names,
                 * but the parser doesn't provide any easy way to test for whether
                 * an identifier is safe or not... so be safe not sorry.
                 *
-                * Note: ScanKeywordLookup() expects an all-lower-case input, but
-                * we've already checked we have that.
+                * Note: ScanKeywordLookup() does case-insensitive comparison, but
+                * that's fine, since we already know we have all-lower-case.
                 */
-               if (ScanKeywordLookup(ident) != NULL)
+               if (ScanKeywordLookup((char *) ident) != NULL)
                        safe = false;
        }
 
@@ -1877,96 +2635,41 @@ quote_identifier(char *ident)
 }
 
 /* ----------
- * get_relation_name                   - Get a relation name by Oid
+ * quote_qualified_identifier  - Quote a possibly-qualified identifier
+ *
+ * Return a name of the form namespace.ident, or just ident if namespace
+ * is NULL, quoting each component if necessary.  The result is palloc'd.
  * ----------
  */
-static char *
-get_relation_name(Oid relid)
+char *
+quote_qualified_identifier(const char *namespace,
+                                                  const char *ident)
 {
-       HeapTuple       classtup;
-       Form_pg_class classStruct;
-
-       classtup = SearchSysCacheTuple(RELOID,
-                                                                  ObjectIdGetDatum(relid), 0, 0, 0);
-       if (!HeapTupleIsValid(classtup))
-               elog(ERROR, "cache lookup of relation %u failed", relid);
+       StringInfoData buf;
 
-       classStruct = (Form_pg_class) GETSTRUCT(classtup);
-       return pstrdup(NameStr(classStruct->relname));
+       initStringInfo(&buf);
+       if (namespace)
+               appendStringInfo(&buf, "%s.", quote_identifier(namespace));
+       appendStringInfo(&buf, "%s", quote_identifier(ident));
+       return buf.data;
 }
 
-
 /* ----------
- * get_attribute_name                  - Get an attribute name by its
- *                                       relations Oid and its attnum
+ * get_relid_attribute_name
+ *             Get an attribute name by its relations Oid and its attnum
+ *
+ * Same as underlying syscache routine get_attname(), except that error
+ * is handled by elog() instead of returning NULL.
  * ----------
  */
 static char *
-get_attribute_name(Oid relid, int2 attnum)
+get_relid_attribute_name(Oid relid, AttrNumber attnum)
 {
-       HeapTuple       atttup;
-       Form_pg_attribute attStruct;
+       char       *attname;
 
-       atttup = SearchSysCacheTuple(ATTNUM,
-                                                                ObjectIdGetDatum(relid), (Datum) attnum,
-                                                                0, 0);
-       if (!HeapTupleIsValid(atttup))
+       attname = get_attname(relid, attnum);
+       if (attname == NULL)
                elog(ERROR, "cache lookup of attribute %d in relation %u failed",
                         attnum, relid);
-
-       attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
-       return pstrdup(NameStr(attStruct->attname));
-}
-
-
-/* ----------
- * check_if_rte_used
- *             Check a targetlist or qual to see if a given rangetable entry
- *             is used in it
- * ----------
- */
-static bool
-check_if_rte_used(Node *node, Index rt_index, int levelsup)
-{
-       check_if_rte_used_context context;
-
-       context.rt_index = rt_index;
-       context.levelsup = levelsup;
-       return check_if_rte_used_walker(node, &context);
-}
-
-static bool
-check_if_rte_used_walker(Node *node,
-                                                check_if_rte_used_context *context)
-{
-       if (node == NULL)
-               return false;
-       if (IsA(node, Var))
-       {
-               Var                *var = (Var *) node;
-
-               return var->varno == context->rt_index &&
-                       var->varlevelsup == context->levelsup;
-       }
-       if (IsA(node, SubLink))
-       {
-               SubLink    *sublink = (SubLink *) node;
-               Query      *query = (Query *) sublink->subselect;
-
-               /* Recurse into subquery; expression_tree_walker will not */
-               if (check_if_rte_used((Node *) (query->targetList),
-                                                         context->rt_index, context->levelsup + 1) ||
-                       check_if_rte_used(query->qual,
-                                                         context->rt_index, context->levelsup + 1) ||
-                       check_if_rte_used(query->havingQual,
-                                                         context->rt_index, context->levelsup + 1))
-                       return true;
-
-               /*
-                * fall through to let expression_tree_walker examine lefthand
-                * args
-                */
-       }
-       return expression_tree_walker(node, check_if_rte_used_walker,
-                                                                 (void *) context);
+       return attname;
 }