psql backslash commands are schema-aware. Pattern matching behavior
authorTom Lane
Sat, 10 Aug 2002 03:56:24 +0000 (03:56 +0000)
committerTom Lane
Sat, 10 Aug 2002 03:56:24 +0000 (03:56 +0000)
follows recent pghackers discussion.  This commit includes all the
relevant fixes from Greg Mullane's patch of 24-June.

doc/src/sgml/ref/grant.sgml
doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/describe.c
src/bin/psql/describe.h
src/bin/psql/large_obj.c
src/bin/psql/tab-complete.c

index a1384409cf3b521d00a4ecbb6b3384e95c8edf47..5a66973e32d0cea72c6144bd2c80cc7cf678fdca 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -249,16 +249,17 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
    
 
    
-    Use 's \z command
+    Use 's \dp command
     to obtain information about existing privileges, for example:
 
-lusitania=> \z mytable
-    Access privileges for database "lusitania"
-  Table  |           Access privileges
----------+---------------------------------------
- mytable | {=r,miriam=arwdRxt,"group todos=arw"}
+lusitania=> \dp mytable
+        Access privileges for database "lusitania"
+ Schema |  Table  |           Access privileges
+--------+---------+---------------------------------------
+ public | mytable | {=r,miriam=arwdRxt,"group todos=arw"}
+(1 row)
 
-    The entries shown by \z are interpreted thus:
+    The entries shown by \dp are interpreted thus:
 
               =xxxx -- privileges granted to PUBLIC
          uname=xxxx -- privileges granted to a user
index b9f8554abfb6a7d6fd14267a6f2a67ba448ea516..48370d1e2146c720d22985989ee268a65939d8d6 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -538,7 +538,7 @@ testdb=>
     
 
     
-    To include whitespace into an argument you must quote it with a
+    To include whitespace into an argument you may quote it with a
     single quote. To include a single quote into such an argument,
     precede it by a backslash. Anything contained in single quotes is
     furthermore subject to C-like substitutions for
@@ -551,25 +551,24 @@ testdb=>
 
     
     If an unquoted argument begins with a colon (:),
-    it is taken as a variable and the value of the variable is taken as
-    the argument instead.
+    it is taken as a psql variable and the value of the
+    variable is used as the argument instead.
     
 
     
     Arguments that are quoted in backticks
     (`) are taken as a command line that is passed to
-    the shell. The output of the command (with a trailing newline
+    the shell. The output of the command (with any trailing newline
     removed) is taken as the argument value. The above escape sequences
     also apply in backticks.
     
 
     
-    Some commands take the name of an SQL identifier
+    Some commands take an SQL identifier
     (such as a table name) as argument. These arguments follow the
     syntax rules of SQL regarding double quotes: an
-    identifier without double quotes is coerced to lower-case. For all
-    other commands double quotes are not special and will become part of
-    the argument.
+    identifier without double quotes is coerced to lower-case, while
+    whitespace within double quotes is included in the argument.
     
 
     
@@ -732,18 +731,17 @@ testdb=>
       
 
       
-        \d relation 
+        \d pattern ]
 
         
         
-   Shows all columns of e
-   class="parameter">relation (which could be a
-   table, view, index, or sequence), their types, and any special
+   For each relation (table, view, index, or sequence) matching the
+   pattern, show all
+   columns, their types, and any special
    attributes such as NOT NULL or defaults, if
-   any. If the relation is, in fact, a table, any defined indices,
-   primary keys, unique constraints and check constraints are also
-   listed. If the relation is a view, the view definition is also
-   shown.
+   any. Associated indexes, constraints, rules, and triggers are
+   also shown, as is the view definition if the relation is a view.
+   (Matching the pattern is defined below.)
    
 
    
@@ -753,7 +751,8 @@ testdb=>
 
    
    
-   If \d is called without any arguments, it is
+   If \d is used without a
+   pattern argument, it is
    equivalent to \dtvs which will show a list of
    all tables, views, and sequences. This is purely a convenience
    measure.
@@ -776,34 +775,35 @@ testdb=>
       
 
       
-        \dd [ object ]
+        \dd [ pattern ]
         
         
-        Shows the descriptions of 
-        class="parameter">object (which can be a regular
-        expression), or of all objects if no argument is given.
+        Shows the descriptions of objects matching the 
+        class="parameter">pattern, or of all visible objects if
+   no argument is given.  But in either case, only objects that have
+   a description are listed.
         (Object covers aggregates, functions, operators,
         types, relations (tables, views, indexes, sequences, large
         objects), rules, and triggers.) For example:
 
 => \dd version
-              Object descriptions
-  Name   |   What   |        Description
----------+----------+---------------------------
- version | function | PostgreSQL version string
+                     Object descriptions
+   Schema   |  Name   |  Object  |        Description
+------------+---------+----------+---------------------------
pg_catalog | version | function | PostgreSQL version string
 (1 row)
 
         
 
         
-        Descriptions for objects can be generated with the
+        Descriptions for objects can be created with the
         COMMENT ON SQL command.
    
 
         
         
         PostgreSQL stores the object
-        descriptions in the pg_description system table.
+        descriptions in the pg_description system table.
         
         
 
@@ -816,7 +816,7 @@ testdb=>
         
         
         Lists all available domains (derived types). If 
-        class="parameter">pattern (a regular expression)
+        class="parameter">pattern
         is specified, only matching domains are shown.
         
         
@@ -830,7 +830,7 @@ testdb=>
         
         Lists available functions, together with their argument and
         return types. If 
-        class="parameter">pattern (a regular expression)
+        class="parameter">pattern
         is specified, only matching functions are shown. If the form
         \df+ is used, additional information about
         each function, including language and description, is shown.
@@ -844,18 +844,17 @@ testdb=>
 
    
    
-   This is not the actual command name: The letters i, s, t, v, S
+   This is not the actual command name: the letters i, s, t, v, S
    stand for index, sequence, table, view, and system table,
-   respectively. You can specify any or all of them in any order to
-   obtain a listing of them, together with who the owner is.
+   respectively. You can specify any or all of these letters, in any
+   order, to obtain a listing of all the matching objects.
+   If + is appended to the command name, each object is
+   listed with its associated description, if any.
    
 
    
-   If pattern is
-   specified, it is a regular expression that restricts the listing
-   to those objects whose name matches. If one appends a
-   + to the command name, each object is listed with
-   its associated description, if any.
+   If a pattern is
+   specified, only objects whose name matches the pattern are listed.
    
    
       
@@ -873,12 +872,12 @@ testdb=>
 
 
       
-        \do [ name ]
+        \do [ pattern ]
         
         
         Lists available operators with their operand and return types.
-        If name is
-        specified, only operators with that name will be shown.
+   If a pattern is
+   specified, only operators whose name matches the pattern are listed.
         
         
       
@@ -888,9 +887,17 @@ testdb=>
         \dp [ pattern ]
    
    
-   This is an alias for \z which was included
-   for its greater mnemonic value (display
-   permissions).
+        Produces a list of all available tables with their
+        associated access permissions.
+   If a pattern is
+   specified, only tables whose name matches the pattern are listed.
+   
+
+   
+   The commands  and
+   
+   are used to set access permissions.  See 
+   for more information.
    
    
       
@@ -912,7 +919,7 @@ testdb=>
         \du [ pattern ]
         
         
-        Lists all configured users or only those that match 
+        Lists all database users, or only those that match 
         class="parameter">pattern.
         
         
@@ -1608,57 +1615,23 @@ lo_import 152801
         \z [ pattern ]
         
         
-        Produces a list of all tables in the database with their
-        appropriate access permissions listed. If an argument is given
-        it is taken as a regular expression which limits the listing to
-        those tables which match it.
-        
-
-   
-
-test=> \z
-Access permissions for database "test"
- Relation |           Access permissions
-----------+-------------------------------------
- my_table | {"=r","joe=arwR", "group staff=ar"}
-(1 row )
-
-        Read this as follows:
-
-   
-          
-          
-          "=r"PUBLIC has read
-          (SELECT) permission on the table.
-     
-     
-
-     
-     
-     "joe=arwR": User joe has
-     read, write (UPDATE,
-     DELETE), append
-     (INSERT) permissions, and permission to
-     create rules on the table.
-     
-     
-
-     
-     
-     "group staff=ar": Group
-     staff has SELECT and
-     INSERT permission.
-     
-     
-   
+        Produces a list of all available tables with their
+        associated access permissions.
+   If a pattern is
+   specified, only tables whose name matches the pattern are listed.
    
 
    
    The commands  and
    
-   are used to set access permissions.
+   are used to set access permissions.  See 
+   for more information.
    
 
+   
+   This is an alias for \dp (display
+   permissions).
+        
         
       
 
@@ -1688,6 +1661,46 @@ Access permissions for database "test"
 
     
   
+
+  
+   The various \d commands accept a 
+   class="parameter">pattern parameter to specify the
+   object name(s) to be displayed.  Patterns are interpreted similarly
+   to SQL identifiers, in that unquoted letters are forced to lowercase,
+   while double quotes (") protect letters from case conversion
+   and allow incorporation of whitespace into the identifier.  Within
+   double quotes, paired double quotes reduce to a single double quote in
+   the resulting name.  For example, FOO"BAR"BAZ is interpreted
+   as fooBARbaz, and "A weird"" name" becomes
+   A weird" name.
+  
+
+  
+   More interestingly, \d patterns allow the use of
+   * to mean any sequence of characters, and
+   ? to mean any single character.  (This notation
+   is comparable to Unix shell filename patterns.)  Advanced users can
+   also use regular-expression notations such as character classes, for
+   example [0-9] to match any digit.  To make any of
+   these pattern-matching characters be interpreted literally, surround it
+   with double quotes.
+  
+
+  
+   A pattern that contains an (unquoted) dot is interpreted as a schema
+   name pattern followed by an object name pattern.  For example,
+    \dt foo*.bar* displays all tables in schemas whose name
+   starts with foo and whose table name 
+   starts with bar.  If no dot appears, then the pattern
+   matches only objects that are visible in the current schema search path.
+  
+
+  
+   Whenever the pattern parameter
+   is omitted completely, the \d commands display all objects
+   that are visible in the current schema search path.  To see all objects
+   in the database, use the pattern *.*.
+  
  
 
  
@@ -2402,11 +2415,12 @@ $ ./configure --with-includes=/opt/gnu/include --with-libs=/opt/gnu/lib  ...
     
       
       
-      In some earlier life psql allowed the
-      first argument to start directly after the (single-letter)
-      command. For compatibility this is still supported to some extent
+      In an earlier life psql allowed the
+      first argument of a single-letter backslash command to start
+      directly after the command, without intervening whitespace. For
+      compatibility this is still supported to some extent,
       but I am not going to explain the details here as this use is
-      discouraged. But if you get strange messages, keep this in mind.
+      discouraged.  If you get strange messages, keep this in mind.
       For example
 
 testdb=> \foo
@@ -2421,7 +2435,8 @@ Field separator is "oo",
       psql only works smoothly with servers
       of the same version. That does not mean other combinations will
       fail outright, but subtle and not-so-subtle problems might come
-      up.
+      up.  Backslash commands are particularly likely to fail if the
+      server is of a different version.
       
       
 
index f99f909f285217e040d5b2110963f42ec8563782..9be47a90c91b39c190a34a22f5390f88b04d4efc 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright 2000 by PostgreSQL Global Development Group
+ * Copyright 2000-2002 by PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.74 2002/07/18 02:02:30 ishii Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.75 2002/08/10 03:56:23 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "command.h"
@@ -54,7 +54,7 @@ enum option_type
    OT_NORMAL,                  /* normal case */
    OT_SQLID,                   /* treat as SQL identifier */
    OT_SQLIDHACK,               /* SQL identifier, but don't downcase */
-   OT_FILEPIPE                 /* it's a file or pipe */
+   OT_FILEPIPE                 /* it's a filename or pipe */
 };
 
 static char *scan_option(char **string, enum option_type type,
@@ -328,10 +328,11 @@ exec_command(const char *cmd,
    /* \d* commands */
    else if (cmd[0] == 'd')
    {
-       char       *name;
+       char       *pattern;
        bool        show_verbose;
 
-       name = scan_option(&string, OT_SQLID, NULL, true);
+       /* We don't do SQLID reduction on the pattern yet */
+       pattern = scan_option(&string, OT_NORMAL, NULL, true);
 
        show_verbose = strchr(cmd, '+') ? true : false;
 
@@ -339,51 +340,53 @@ exec_command(const char *cmd,
        {
            case '\0':
            case '+':
-               if (name)
-                   success = describeTableDetails(name, show_verbose);
+               if (pattern)
+                   success = describeTableDetails(pattern, show_verbose);
                else
                    /* standard listing of interesting things */
                    success = listTables("tvs", NULL, show_verbose);
                break;
            case 'a':
-               success = describeAggregates(name);
+               success = describeAggregates(pattern, show_verbose);
                break;
            case 'd':
-               success = objectDescription(name);
+               success = objectDescription(pattern);
                break;
            case 'f':
-               success = describeFunctions(name, show_verbose);
+               success = describeFunctions(pattern, show_verbose);
                break;
            case 'l':
                success = do_lo_list();
                break;
            case 'o':
-               success = describeOperators(name);
+               success = describeOperators(pattern);
                break;
            case 'p':
-               success = permissionsList(name);
+               success = permissionsList(pattern);
                break;
            case 'T':
-               success = describeTypes(name, show_verbose);
+               success = describeTypes(pattern, show_verbose);
                break;
            case 't':
            case 'v':
            case 'i':
            case 's':
            case 'S':
-               success = listTables(&cmd[1], name, show_verbose);
+               success = listTables(&cmd[1], pattern, show_verbose);
                break;
            case 'u':
-               success = describeUsers(name);
+               success = describeUsers(pattern);
                break;
            case 'D':
-               success = listDomains(name);
+               success = listDomains(pattern);
                break;
 
            default:
                status = CMD_UNKNOWN;
        }
-       free(name);
+
+       if (pattern)
+           free(pattern);
    }
 
 
@@ -815,13 +818,14 @@ exec_command(const char *cmd,
        success = do_pset("expanded", NULL, &pset.popt, quiet);
 
 
-   /* \z -- list table rights (grant/revoke) */
+   /* \z -- list table rights (equivalent to \dp) */
    else if (strcmp(cmd, "z") == 0)
    {
-       char       *opt = scan_option(&string, OT_SQLID, NULL, true);
+       char       *pattern = scan_option(&string, OT_NORMAL, NULL, true);
 
-       success = permissionsList(opt);
-       free(opt);
+       success = permissionsList(pattern);
+       if (pattern)
+           free(pattern);
    }
 
    /* \! -- shell escape */
@@ -881,11 +885,27 @@ exec_command(const char *cmd,
 
 /*
  * scan_option()
+ *
+ * *string points to possible option string on entry; on exit, it's updated
+ * to point past the option string (if any).
+ *
+ * type tells what processing, if any, to perform on the option string;
+ * for example, if it's a SQL identifier, we want to downcase any unquoted
+ * letters.
+ *
+ * if quote is not NULL, *quote is set to 0 if no quoting was found, else
+ * the quote symbol.
+ *
+ * if semicolon is true, trailing semicolon(s) that would otherwise be taken
+ * as part of the option string will be stripped.
+ *
+ * Return value is NULL if no option found, else a malloc'd copy of the
+ * processed option value.
  */
 static char *
 scan_option(char **string, enum option_type type, char *quote, bool semicolon)
 {
-   unsigned int pos = 0;
+   unsigned int pos;
    char       *options_string;
    char       *return_val;
 
@@ -897,82 +917,27 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
 
    options_string = *string;
    /* skip leading whitespace */
-   pos += strspn(options_string + pos, " \t\n\r");
+   pos = strspn(options_string, " \t\n\r");
 
    switch (options_string[pos])
    {
            /*
-            * Double quoted string
+            * End of line: no option present
             */
-       case '"':
-           {
-               unsigned int jj;
-               unsigned short int bslash_count = 0;
-
-               /* scan for end of quote */
-               for (jj = pos + 1; options_string[jj]; jj += PQmblen(&options_string[jj], pset.encoding))
-               {
-                   if (options_string[jj] == '"' && bslash_count % 2 == 0)
-                       break;
-
-                   if (options_string[jj] == '\\')
-                       bslash_count++;
-                   else
-                       bslash_count = 0;
-               }
-
-               if (options_string[jj] == 0)
-               {
-                   psql_error("parse error at the end of line\n");
-                   *string = &options_string[jj];
-                   return NULL;
-               }
-
-               return_val = malloc(jj - pos + 2);
-               if (!return_val)
-               {
-                   psql_error("out of memory\n");
-                   exit(EXIT_FAILURE);
-               }
-
-               /*
-                * If this is expected to be an SQL identifier like option
-                * then we strip out the double quotes
-                */
-
-               if (type == OT_SQLID || type == OT_SQLIDHACK)
-               {
-                   unsigned int k,
-                               cc;
-
-                   bslash_count = 0;
-                   cc = 0;
-                   for (k = pos + 1; options_string[k]; k += PQmblen(&options_string[k], pset.encoding))
-                   {
-                       if (options_string[k] == '"' && bslash_count % 2 == 0)
-                           break;
-
-                       if (options_string[jj] == '\\')
-                           bslash_count++;
-                       else
-                           bslash_count = 0;
-
-                       return_val[cc++] = options_string[k];
-                   }
-                   return_val[cc] = '\0';
-               }
-               else
-               {
-                   strncpy(return_val, &options_string[pos], jj - pos + 1);
-                   return_val[jj - pos + 1] = '\0';
-               }
-
-               *string = options_string + jj + 1;
-               if (quote)
-                   *quote = '"';
+       case '\0':
+           *string = &options_string[pos];
+           return NULL;
 
-               return return_val;
-           }
+           /*
+            * Next command: treat like end of line
+            *
+            * XXX this means we can't conveniently accept options that
+            * start with a backslash; therefore, option processing that
+            * encourages use of backslashes is rather broken.
+            */
+       case '\\':
+           *string = &options_string[pos];
+           return NULL;
 
            /*
             * A single quote has a psql internal meaning, such as for
@@ -1015,7 +980,7 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
        case '`':
            {
                bool        error = false;
-               FILE       *fd = NULL;
+               FILE       *fd;
                char       *file;
                PQExpBufferData output;
                char        buf[512];
@@ -1040,10 +1005,10 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
                    error = true;
                }
 
+               initPQExpBuffer(&output);
+
                if (!error)
                {
-                   initPQExpBuffer(&output);
-
                    do
                    {
                        result = fread(buf, 1, 512, fd);
@@ -1056,27 +1021,26 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
                        appendBinaryPQExpBuffer(&output, buf, result);
                    } while (!feof(fd));
                    appendPQExpBufferChar(&output, '\0');
+               }
 
-                   if (pclose(fd) == -1)
-                   {
-                       psql_error("%s: %s\n", file, strerror(errno));
-                       error = true;
-                   }
+               if (fd && pclose(fd) == -1)
+               {
+                   psql_error("%s: %s\n", file, strerror(errno));
+                   error = true;
                }
 
                if (!error)
                {
                    if (output.data[strlen(output.data) - 1] == '\n')
                        output.data[strlen(output.data) - 1] = '\0';
-               }
-
-               if (!error)
                    return_val = output.data;
+               }
                else
                {
                    return_val = xstrdup("");
                    termPQExpBuffer(&output);
                }
+
                options_string[pos + 1 + len] = '`';
                *string = options_string + pos + len + 2;
                if (quote)
@@ -1084,13 +1048,6 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
                return return_val;
            }
 
-           /*
-            * end of line
-            */
-       case 0:
-           *string = &options_string[pos];
-           return NULL;
-
            /*
             * Variable substitution
             */
@@ -1109,17 +1066,10 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
                return_val = xstrdup(value);
                options_string[pos + token_end + 1] = save_char;
                *string = &options_string[pos + token_end + 1];
+               /* XXX should we set *quote to ':' here? */
                return return_val;
            }
 
-           /*
-            * Next command
-            */
-       case '\\':
-           *string = options_string + pos;
-           return NULL;
-           break;
-
            /*
             * | could be the beginning of a pipe if so, take rest of line
             * as command
@@ -1127,49 +1077,135 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
        case '|':
            if (type == OT_FILEPIPE)
            {
-               *string += strlen(options_string + pos);
+               *string += strlen(*string);
                return xstrdup(options_string + pos);
-               break;
            }
            /* fallthrough for other option types */
 
            /*
-            * A normal word
+            * Default case: token extends to next whitespace, except that
+            * whitespace within double quotes doesn't end the token.
+            *
+            * If we are processing the option as a SQL identifier, then
+            * downcase unquoted letters and remove double-quotes --- but
+            * doubled double-quotes become output double-quotes, per spec.
+            *
+            * Note that a string like FOO"BAR"BAZ will be converted to
+            * fooBARbaz; this is somewhat inconsistent with the SQL spec,
+            * which would have us parse it as several identifiers.  But
+            * for psql's purposes, we want a string like "foo"."bar" to
+            * be treated as one option, so there's little choice.
             */
        default:
            {
-               size_t      token_end;
+               bool        inquotes = false;
+               size_t      token_len;
                char       *cp;
 
-               token_end = strcspn(&options_string[pos], " \t\n\r");
-               return_val = malloc(token_end + 1);
+               /* Find end of option */
+
+               cp = &options_string[pos];
+               for (;;)
+               {
+                   /* Find next quote, whitespace, or end of string */
+                   cp += strcspn(cp, "\" \t\n\r");
+                   if (inquotes)
+                   {
+                       if (*cp == '\0')
+                       {
+                           psql_error("parse error at the end of line\n");
+                           *string = cp;
+                           return NULL;
+                       }
+                       if (*cp == '"')
+                           inquotes = false;
+                       cp++;
+                   }
+                   else
+                   {
+                       if (*cp != '"')
+                           break; /* whitespace or end of string */
+                       if (quote)
+                           *quote = '"';
+                       inquotes = true;
+                       cp++;
+                   }
+               }
+
+               *string = cp;
+
+               /* Copy the option */
+               token_len = cp - &options_string[pos];
+
+               return_val = malloc(token_len + 1);
                if (!return_val)
                {
                    psql_error("out of memory\n");
                    exit(EXIT_FAILURE);
                }
-               strncpy(return_val, &options_string[pos], token_end);
-               return_val[token_end] = 0;
 
-               /* Strip any trailing semi-colons for some types */
+               memcpy(return_val, &options_string[pos], token_len);
+               return_val[token_len] = '\0';
+
+               /* Strip any trailing semi-colons if requested */
                if (semicolon)
                {
-                   int         i;
+                   int     i;
+
+                   for (i = token_len - 1;
+                        i >= 0 && return_val[i] == ';';
+                        i--)
+                       /* skip */;
+
+                   if (i < 0)
+                   {
+                       /* nothing left after stripping the semicolon... */
+                       free(return_val);
+                       return NULL;
+                   }
 
-                   for (i = strlen(return_val) - 1; i && return_val[i] == ';'; i--);
-                   if (i < strlen(return_val) - 1)
+                   if (i < token_len - 1)
                        return_val[i + 1] = '\0';
                }
 
-               if (type == OT_SQLID)
-                   for (cp = return_val; *cp; cp += PQmblen(cp, pset.encoding))
-                       if (isupper((unsigned char) *cp))
-                           *cp = tolower((unsigned char) *cp);
+               /*
+                * If SQL identifier processing was requested,
+                * then we strip out excess double quotes and downcase
+                * unquoted letters.
+                */
+               if (type == OT_SQLID || type == OT_SQLIDHACK)
+               {
+                   inquotes = false;
+                   cp = return_val;
+
+                   while (*cp)
+                   {
+                       if (*cp == '"')
+                       {
+                           if (inquotes && cp[1] == '"')
+                           {
+                               /* Keep the first quote, remove the second */
+                               cp++;
+                           }
+                           inquotes = !inquotes;
+                           /* Collapse out quote at *cp */
+                           memmove(cp, cp+1, strlen(cp));
+                           /* do not advance cp */
+                       }
+                       else
+                       {
+                           if (!inquotes && type == OT_SQLID)
+                           {
+                               if (isupper((unsigned char) *cp))
+                                   *cp = tolower((unsigned char) *cp);
+                           }
+                           cp += PQmblen(cp, pset.encoding);
+                       }
+                   }
+               }
 
-               *string = &options_string[pos + token_end];
                return return_val;
            }
-
    }
 }
 
@@ -1429,7 +1465,7 @@ test_superuser(const char *username)
        return false;
 
    initPQExpBuffer(&buf);
-   printfPQExpBuffer(&buf, "SELECT usesuper FROM pg_user WHERE usename = '%s'", username);
+   printfPQExpBuffer(&buf, "SELECT usesuper FROM pg_catalog.pg_user WHERE usename = '%s'", username);
    res = PSQLexec(buf.data);
    termPQExpBuffer(&buf);
 
index 8deffacb5fae43ff2c1684bc513aa41631eb67cb..59369972d49b5c254250922252b8e8637ee8ee98 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright 2000 by PostgreSQL Global Development Group
+ * Copyright 2000-2002 by PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/describe.c,v 1.58 2002/08/09 18:06:57 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/describe.c,v 1.59 2002/08/10 03:56:24 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "describe.h"
 #include "print.h"
 #include "variables.h"
 
+#include 
+
 #define _(x) gettext((x))
 
+static bool describeOneTableDetails(const char *schemaname,
+                                   const char *relationname,
+                                   const char *oid,
+                                   bool verbose);
+static void processNamePattern(PQExpBuffer buf, const char *pattern,
+                              bool have_where, bool force_escape,
+                              const char *schemavar, const char *namevar,
+                              const char *altnamevar, const char *visibilityrule);
+
+
+static void *
+xmalloc(size_t size)
+{
+   void       *tmp;
+
+   tmp = malloc(size);
+   if (!tmp)
+   {
+       psql_error("out of memory\n");
+       exit(EXIT_FAILURE);
+   }
+   return tmp;
+}
+
 
 /*----------------
  * Handlers for various slash commands displaying some sort of list
 
 
 /* \da
- * takes an optional regexp to match specific aggregates by name
+ * Takes an optional regexp to select particular aggregates
  */
 bool
-describeAggregates(const char *name)
+describeAggregates(const char *pattern, bool verbose)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -45,21 +71,24 @@ describeAggregates(const char *name)
     * types and ones that work on all (denoted by input type = 0)
     */
    printfPQExpBuffer(&buf,
-            "SELECT p.proname AS \"%s\",\n"
+            "SELECT n.nspname as \"%s\",\n"
+            "  p.proname AS \"%s\",\n"
             "  CASE p.proargtypes[0]\n"
-            "    WHEN 0 THEN CAST('%s' AS text)\n"
-            "    ELSE format_type(p.proargtypes[0], NULL)\n"
+            "    WHEN 0 THEN CAST('%s' AS pg_catalog.text)\n"
+            "    ELSE pg_catalog.format_type(p.proargtypes[0], NULL)\n"
             "  END AS \"%s\",\n"
-            "  obj_description(p.oid, 'pg_proc') as \"%s\"\n"
-            "FROM pg_proc p\n"
+            "  pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
+            "FROM pg_catalog.pg_proc p\n"
+            "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
             "WHERE p.proisagg\n",
-            _("Name"), _("(all types)"),
+            _("Schema"), _("Name"), _("(all types)"),
             _("Data type"), _("Description"));
 
-   if (name)
-       appendPQExpBuffer(&buf, "  AND p.proname ~ '^%s'\n", name);
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "p.proname", NULL,
+                      "pg_catalog.pg_function_is_visible(p.oid)");
 
-   appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
+   appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3;");
 
    res = PSQLexec(buf.data);
    termPQExpBuffer(&buf);
@@ -77,10 +106,10 @@ describeAggregates(const char *name)
 
 
 /* \df
- * Takes an optional regexp to narrow down the function name
+ * Takes an optional regexp to select particular functions
  */
 bool
-describeFunctions(const char *name, bool verbose)
+describeFunctions(const char *pattern, bool verbose)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -88,15 +117,12 @@ describeFunctions(const char *name, bool verbose)
 
    initPQExpBuffer(&buf);
 
-   /*
-    * we skip in/out funcs by excluding functions that take some
-    * arguments, but have no types defined for those arguments
-    */
    printfPQExpBuffer(&buf,
-            "SELECT format_type(p.prorettype, NULL) as \"%s\",\n"
+            "SELECT pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n"
+            "  n.nspname as \"%s\",\n"
             "  p.proname as \"%s\",\n"
-            "  oidvectortypes(p.proargtypes) as \"%s\"",
-            _("Result data type"), _("Name"),
+            "  pg_catalog.oidvectortypes(p.proargtypes) as \"%s\"",
+            _("Result data type"), _("Schema"), _("Name"),
             _("Argument data types"));
 
    if (verbose)
@@ -104,23 +130,35 @@ describeFunctions(const char *name, bool verbose)
                 ",\n  u.usename as \"%s\",\n"
                 "  l.lanname as \"%s\",\n"
                 "  p.prosrc as \"%s\",\n"
-                "  obj_description(p.oid, 'pg_proc') as \"%s\"",
+                "  pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
                 _("Owner"), _("Language"),
                 _("Source code"), _("Description"));
 
    if (!verbose)
        appendPQExpBuffer(&buf,
-              "\nFROM pg_proc p\n"
-              "WHERE p.prorettype <> 0 AND (pronargs = 0 OR oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n");
+              "\nFROM pg_catalog.pg_proc p"
+              "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n");
    else
        appendPQExpBuffer(&buf,
-              "\nFROM pg_proc p,  pg_language l, pg_user u\n"
-              "WHERE p.prolang = l.oid AND p.proowner = u.usesysid\n"
-              "  AND p.prorettype <> 0 AND (pronargs = 0 OR oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n");
+              "\nFROM pg_catalog.pg_proc p"
+              "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace"
+              "\n     LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang"
+              "\n     LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner\n");
 
-   if (name)
-       appendPQExpBuffer(&buf, "  AND p.proname ~ '^%s'\n", name);
-   appendPQExpBuffer(&buf, "ORDER BY 2, 1, 3;");
+   /*
+    * we skip in/out funcs by excluding functions that take some
+    * arguments, but have no types defined for those arguments
+    */
+   appendPQExpBuffer(&buf,
+                     "WHERE p.prorettype <> 0\n"
+                     "      AND (p.pronargs = 0 OR pg_catalog.oidvectortypes(p.proargtypes) <> '')\n"
+                     "      AND NOT p.proisagg\n");
+
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "p.proname", NULL,
+                      "pg_catalog.pg_function_is_visible(p.oid)");
+
+   appendPQExpBuffer(&buf, "ORDER BY 2, 3, 1, 4;");
 
    res = PSQLexec(buf.data);
    termPQExpBuffer(&buf);
@@ -143,7 +181,7 @@ describeFunctions(const char *name, bool verbose)
  * describe types
  */
 bool
-describeTypes(const char *name, bool verbose)
+describeTypes(const char *pattern, bool verbose)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -152,31 +190,37 @@ describeTypes(const char *name, bool verbose)
    initPQExpBuffer(&buf);
 
    printfPQExpBuffer(&buf,
-            "SELECT format_type(t.oid, NULL) AS \"%s\",\n",
-            _("Name"));
+            "SELECT n.nspname as \"%s\",\n"
+            "  pg_catalog.format_type(t.oid, NULL) AS \"%s\",\n",
+            _("Schema"), _("Name"));
    if (verbose)
        appendPQExpBuffer(&buf,
                 "  t.typname AS \"%s\",\n"
                 "  CASE WHEN t.typlen = -1\n"
-                "    THEN CAST('var' AS text)\n"
-                "    ELSE CAST(t.typlen AS text)\n"
+                "    THEN CAST('var' AS pg_catalog.text)\n"
+                "    ELSE CAST(t.typlen AS pg_catalog.text)\n"
                 "  END AS \"%s\",\n",
                 _("Internal name"), _("Size"));
    appendPQExpBuffer(&buf,
-            "  obj_description(t.oid, 'pg_type') as \"%s\"\n",
+            "  pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
             _("Description"));
 
+   appendPQExpBuffer(&buf, "FROM pg_catalog.pg_type t\n"
+            "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n");
+
    /*
     * do not include array types (start with underscore), do not include
     * user relations (typrelid!=0)
     */
-   appendPQExpBuffer(&buf, "FROM pg_type t\nWHERE t.typrelid = 0 AND t.typname !~ '^_.*'\n");
+   appendPQExpBuffer(&buf, "WHERE t.typrelid = 0 AND t.typname !~ '^_'\n");
 
-   if (name)
-       /* accept either internal or external type name */
-       appendPQExpBuffer(&buf, "  AND (format_type(t.oid, NULL) ~ '^%s' OR t.typname ~ '^%s')\n", name, name);
+   /* Match name pattern against either internal or external name */
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "t.typname",
+                      "pg_catalog.format_type(t.oid, NULL)",
+                      "pg_catalog.pg_type_is_visible(t.oid)");
 
-   appendPQExpBuffer(&buf, "ORDER BY 1;");
+   appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
 
    res = PSQLexec(buf.data);
    termPQExpBuffer(&buf);
@@ -197,7 +241,7 @@ describeTypes(const char *name, bool verbose)
 /* \do
  */
 bool
-describeOperators(const char *name)
+describeOperators(const char *pattern)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -206,17 +250,22 @@ describeOperators(const char *name)
    initPQExpBuffer(&buf);
 
    printfPQExpBuffer(&buf,
-            "SELECT o.oprname AS \"%s\",\n"
-            "  CASE WHEN o.oprkind='l' THEN NULL ELSE format_type(o.oprleft, NULL) END AS \"%s\",\n"
-            "  CASE WHEN o.oprkind='r' THEN NULL ELSE format_type(o.oprright, NULL) END AS \"%s\",\n"
-            "  format_type(o.oprresult, NULL) AS \"%s\",\n"
-            "  coalesce(obj_description(o.oid, 'pg_operator'),"
-            "           obj_description(o.oprcode, 'pg_proc')) AS \"%s\"\n"
-            "FROM pg_operator o\n",
-            _("Name"), _("Left arg type"), _("Right arg type"),
+            "SELECT n.nspname as \"%s\",\n"
+            "  o.oprname AS \"%s\",\n"
+            "  CASE WHEN o.oprkind='l' THEN NULL ELSE pg_catalog.format_type(o.oprleft, NULL) END AS \"%s\",\n"
+            "  CASE WHEN o.oprkind='r' THEN NULL ELSE pg_catalog.format_type(o.oprright, NULL) END AS \"%s\",\n"
+            "  pg_catalog.format_type(o.oprresult, NULL) AS \"%s\",\n"
+            "  coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n"
+            "           pg_catalog.obj_description(o.oprcode, 'pg_proc')) AS \"%s\"\n"
+            "FROM pg_catalog.pg_operator o\n"
+            "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n",
+            _("Schema"), _("Name"),
+            _("Left arg type"), _("Right arg type"),
             _("Result type"), _("Description"));
-   if (name)
-       appendPQExpBuffer(&buf, "WHERE o.oprname = '%s'\n", name);
+
+   processNamePattern(&buf, pattern, false, true,
+                      "n.nspname", "o.oprname", NULL,
+                      "pg_catalog.pg_operator_is_visible(o.oid)");
 
    appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3, 4;");
 
@@ -255,15 +304,16 @@ listAllDbs(bool desc)
             _("Name"), _("Owner"));
 #ifdef MULTIBYTE
    appendPQExpBuffer(&buf,
-            ",\n       pg_encoding_to_char(d.encoding) as \"%s\"",
+            ",\n       pg_catalog.pg_encoding_to_char(d.encoding) as \"%s\"",
             _("Encoding"));
 #endif
    if (desc)
        appendPQExpBuffer(&buf,
-            ",\n       obj_description(d.oid, 'pg_database') as \"%s\"",
+            ",\n       pg_catalog.obj_description(d.oid, 'pg_database') as \"%s\"",
                 _("Description"));
    appendPQExpBuffer(&buf,
-   "\nFROM pg_database d LEFT JOIN pg_user u ON d.datdba = u.usesysid\n"
+                     "\nFROM pg_catalog.pg_database d"
+                     "\n  LEFT JOIN pg_catalog.pg_user u ON d.datdba = u.usesysid\n"
           "ORDER BY 1;");
 
    res = PSQLexec(buf.data);
@@ -286,7 +336,7 @@ listAllDbs(bool desc)
  * \z (now also \dp -- perhaps more mnemonic)
  */
 bool
-permissionsList(const char *name)
+permissionsList(const char *pattern)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -294,17 +344,29 @@ permissionsList(const char *name)
 
    initPQExpBuffer(&buf);
 
-   /* Currently, we ignore indexes since they have no meaningful rights */
+   /*
+    * we ignore indexes and toast tables since they have no meaningful rights
+    */
    printfPQExpBuffer(&buf,
-            "SELECT relname as \"%s\",\n"
-            "       relacl as \"%s\"\n"
-            "FROM   pg_class\n"
-            "WHERE  relkind in ('r', 'v', 'S') AND\n"
-            "       relname NOT LIKE 'pg$_%%' ESCAPE '$'\n",
-            _("Table"), _("Access privileges"));
-   if (name)
-       appendPQExpBuffer(&buf, "  AND relname ~ '^%s'\n", name);
-   appendPQExpBuffer(&buf, "ORDER BY 1;");
+            "SELECT n.nspname as \"%s\",\n"
+            "  c.relname as \"%s\",\n"
+            "  c.relacl as \"%s\"\n"
+            "FROM pg_catalog.pg_class c\n"
+            "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+            "WHERE c.relkind IN ('r', 'v', 'S')\n",
+            _("Schema"), _("Table"), _("Access privileges"));
+
+   /*
+    * Unless a schema pattern is specified, we suppress system and temp
+    * tables, since they normally aren't very interesting from a permissions
+    * point of view.  You can see 'em by explicit request though,
+    * eg with \z pg_catalog.*
+    */
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "c.relname", NULL,
+                      "pg_catalog.pg_table_is_visible(c.oid) AND n.nspname !~ '^pg_'");
+
+   appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
 
    res = PSQLexec(buf.data);
    if (!res)
@@ -335,7 +397,7 @@ permissionsList(const char *name)
  * lists of things, there are other \d? commands.
  */
 bool
-objectDescription(const char *object)
+objectDescription(const char *pattern)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -343,71 +405,123 @@ objectDescription(const char *object)
 
    initPQExpBuffer(&buf);
 
-   printfPQExpBuffer(&buf,
-            "SELECT DISTINCT tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n"
-            "FROM (\n"
+   appendPQExpBuffer(&buf,
+            "SELECT DISTINCT tt.nspname AS \"%s\", tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n"
+            "FROM (\n",
+             _("Schema"), _("Name"), _("Object"), _("Description"));
 
    /* Aggregate descriptions */
+   appendPQExpBuffer(&buf,
             "  SELECT p.oid as oid, p.tableoid as tableoid,\n"
-     "  CAST(p.proname AS text) as name, CAST('%s' AS text) as object\n"
-            "  FROM pg_proc p\n"
-       "  WHERE p.proisagg\n"
+            "  n.nspname as nspname,\n"
+            "  CAST(p.proname AS pg_catalog.text) as name,"
+            "  CAST('%s' AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_proc p\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
+            "  WHERE p.proisagg\n",
+            _("aggregate"));
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "p.proname", NULL,
+                      "pg_catalog.pg_function_is_visible(p.oid)");
 
    /* Function descriptions (except in/outs for datatypes) */
+   appendPQExpBuffer(&buf,
             "UNION ALL\n"
             "  SELECT p.oid as oid, p.tableoid as tableoid,\n"
-     "  CAST(p.proname AS text) as name, CAST('%s' AS text) as object\n"
-            "  FROM pg_proc p\n"
-       "  WHERE (p.pronargs = 0 or oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n"
+            "  n.nspname as nspname,\n"
+            "  CAST(p.proname AS pg_catalog.text) as name,"
+            "  CAST('%s' AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_proc p\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n"
+       "  WHERE (p.pronargs = 0 or pg_catalog.oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n",
+            _("function"));
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "p.proname", NULL,
+                      "pg_catalog.pg_function_is_visible(p.oid)");
 
    /* Operator descriptions (only if operator has its own comment) */
+   appendPQExpBuffer(&buf,
             "UNION ALL\n"
             "  SELECT o.oid as oid, o.tableoid as tableoid,\n"
-     "  CAST(o.oprname AS text) as name, CAST('%s' AS text) as object\n"
-            "  FROM pg_operator o\n"
+            "  n.nspname as nspname,\n"
+            "  CAST(o.oprname AS pg_catalog.text) as name,"
+            "  CAST('%s' AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_operator o\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n",
+            _("operator"));
+   processNamePattern(&buf, pattern, false, false,
+                      "n.nspname", "o.oprname", NULL,
+                      "pg_catalog.pg_operator_is_visible(o.oid)");
 
    /* Type description */
+   appendPQExpBuffer(&buf,
             "UNION ALL\n"
             "  SELECT t.oid as oid, t.tableoid as tableoid,\n"
-    "  format_type(t.oid, NULL) as name, CAST('%s' AS text) as object\n"
-            "  FROM pg_type t\n"
+            "  n.nspname as nspname,\n"
+            "  pg_catalog.format_type(t.oid, NULL) as name,"
+            "  CAST('%s' AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_type t\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n",
+            _("data type"));
+   processNamePattern(&buf, pattern, false, false,
+                      "n.nspname", "pg_catalog.format_type(t.oid, NULL)", NULL,
+                      "pg_catalog.pg_type_is_visible(t.oid)");
 
    /* Relation (tables, views, indexes, sequences) descriptions */
+   appendPQExpBuffer(&buf,
             "UNION ALL\n"
             "  SELECT c.oid as oid, c.tableoid as tableoid,\n"
-            "  CAST(c.relname AS text) as name,\n"
+            "  n.nspname as nspname,\n"
+            "  CAST(c.relname AS pg_catalog.text) as name,\n"
             "  CAST(\n"
             "    CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' END"
-            "  AS text) as object\n"
-            "  FROM pg_class c\n"
+            "  AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_class c\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+            "  WHERE c.relkind IN ('r', 'v', 'i', 'S')\n",
+            _("table"), _("view"), _("index"), _("sequence"));
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "c.relname", NULL,
+                      "pg_catalog.pg_table_is_visible(c.oid)");
 
    /* Rule description (ignore rules for views) */
+   appendPQExpBuffer(&buf,
             "UNION ALL\n"
             "  SELECT r.oid as oid, r.tableoid as tableoid,\n"
-            "  CAST(r.rulename AS text) as name, CAST('%s' AS text) as object\n"
-            "  FROM pg_rewrite r\n"
-            "  WHERE r.rulename != '_RETURN'\n"
+            "  n.nspname as nspname,\n"
+            "  CAST(r.rulename AS pg_catalog.text) as name,"
+            "  CAST('%s' AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_rewrite r\n"
+            "       JOIN pg_catalog.pg_class c ON c.oid = r.ev_class\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+            "  WHERE r.rulename != '_RETURN'\n",
+            _("rule"));
+   /* XXX not sure what to do about visibility rule here? */
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "r.rulename", NULL,
+                      "pg_catalog.pg_table_is_visible(c.oid)");
 
    /* Trigger description */
+   appendPQExpBuffer(&buf,
             "UNION ALL\n"
             "  SELECT t.oid as oid, t.tableoid as tableoid,\n"
-      "  CAST(t.tgname AS text) as name, CAST('%s' AS text) as object\n"
-            "  FROM pg_trigger t\n"
-
-            ") AS tt,\n"
-            "pg_description d\n"
-            "WHERE tt.oid = d.objoid and tt.tableoid = d.classoid and d.objsubid = 0\n",
-
-            _("Name"), _("Object"), _("Description"),
-            _("aggregate"), _("function"), _("operator"),
-            _("data type"), _("table"), _("view"),
-            _("index"), _("sequence"), _("rule"),
-            _("trigger")
-       );
-
-   if (object)
-       appendPQExpBuffer(&buf, "  AND tt.name ~ '^%s'\n", object);
-   appendPQExpBuffer(&buf, "ORDER BY 1;");
+            "  n.nspname as nspname,\n"
+            "  CAST(t.tgname AS pg_catalog.text) as name,"
+            "  CAST('%s' AS pg_catalog.text) as object\n"
+            "  FROM pg_catalog.pg_trigger t\n"
+            "       JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid\n"
+            "       LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n",
+            _("trigger"));
+   /* XXX not sure what to do about visibility rule here? */
+   processNamePattern(&buf, pattern, false, false,
+                      "n.nspname", "t.tgname", NULL,
+                      "pg_catalog.pg_table_is_visible(c.oid)");
+
+   appendPQExpBuffer(&buf,
+            ") AS tt\n"
+            "  JOIN pg_catalog.pg_description d ON (tt.oid = d.objoid and tt.tableoid = d.classoid and d.objsubid = 0)\n");
+
+   appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3;");
 
    res = PSQLexec(buf.data);
    termPQExpBuffer(&buf);
@@ -428,35 +542,84 @@ objectDescription(const char *object)
 /*
  * describeTableDetails (for \d)
  *
- * Unfortunately, the information presented here is so complicated that it cannot
- * be done in a single query. So we have to assemble the printed table by hand
- * and pass it to the underlying printTable() function.
- *
+ * This routine finds the tables to be displayed, and calls
+ * describeOneTableDetails for each one.
  */
-
-static void *
-xmalloc(size_t size)
+bool
+describeTableDetails(const char *pattern, bool verbose)
 {
-   void       *tmp;
+   PQExpBufferData buf;
+   PGresult   *res;
+   int         i;
 
-   tmp = malloc(size);
-   if (!tmp)
+   initPQExpBuffer(&buf);
+
+   printfPQExpBuffer(&buf,
+            "SELECT c.oid,\n"
+            "  n.nspname,\n"
+            "  c.relname\n"
+            "FROM pg_catalog.pg_class c\n"
+            "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n");
+
+   processNamePattern(&buf, pattern, false, false,
+                      "n.nspname", "c.relname", NULL,
+                      "pg_catalog.pg_table_is_visible(c.oid)");
+
+   appendPQExpBuffer(&buf, "ORDER BY 2, 3;");
+
+   res = PSQLexec(buf.data);
+   termPQExpBuffer(&buf);
+   if (!res)
+       return false;
+
+   if (PQntuples(res) == 0)
    {
-       psql_error("out of memory\n");
-       exit(EXIT_FAILURE);
+       if (!QUIET())
+           fprintf(stderr, _("Did not find any relation named \"%s\".\n"),
+                   pattern);
+       PQclear(res);
+       return false;
    }
-   return tmp;
-}
 
+   for (i = 0; i < PQntuples(res); i++)
+   {
+       const char *oid;
+       const char *nspname;
+       const char *relname;
+
+       oid = PQgetvalue(res, i, 0);
+       nspname = PQgetvalue(res, i, 1);
+       relname = PQgetvalue(res, i, 2);
 
-bool
-describeTableDetails(const char *name, bool desc)
+       if (!describeOneTableDetails(nspname, relname, oid, verbose))
+       {
+           PQclear(res);
+           return false;
+       }
+   }
+
+   PQclear(res);
+   return true;
+}
+
+/*
+ * describeOneTableDetails (for \d)
+ *
+ * Unfortunately, the information presented here is so complicated that it
+ * cannot be done in a single query. So we have to assemble the printed table
+ * by hand and pass it to the underlying printTable() function.
+ */
+static bool
+describeOneTableDetails(const char *schemaname,
+                       const char *relationname,
+                       const char *oid,
+                       bool verbose)
 {
    PQExpBufferData buf;
    PGresult   *res = NULL;
    printTableOpt myopt = pset.popt.topt;
    int         i;
-   const char *view_def = NULL;
+   char *view_def = NULL;
    const char *headers[5];
    char      **cells = NULL;
    char      **footers = NULL;
@@ -481,8 +644,8 @@ describeTableDetails(const char *name, bool desc)
    /* Get general table info */
    printfPQExpBuffer(&buf,
                      "SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n"
-                     "FROM pg_class WHERE relname='%s'",
-                     name);
+                     "FROM pg_catalog.pg_class WHERE oid = '%s'",
+                     oid);
    res = PSQLexec(buf.data);
    if (!res)
        goto error_return;
@@ -491,9 +654,8 @@ describeTableDetails(const char *name, bool desc)
    if (PQntuples(res) == 0)
    {
        if (!QUIET())
-           fprintf(stderr, _("Did not find any relation named \"%s\".\n"), name);
-       PQclear(res);
-       res = NULL;
+           fprintf(stderr, _("Did not find any relation with oid %s.\n"),
+                   oid);
        goto error_return;
    }
 
@@ -505,7 +667,6 @@ describeTableDetails(const char *name, bool desc)
    tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
    PQclear(res);
 
-
    headers[0] = _("Column");
    headers[1] = _("Type");
    cols = 2;
@@ -516,7 +677,7 @@ describeTableDetails(const char *name, bool desc)
        headers[cols - 1] = _("Modifiers");
    }
 
-   if (desc)
+   if (verbose)
    {
        cols++;
        headers[cols - 1] = _("Description");
@@ -524,19 +685,19 @@ describeTableDetails(const char *name, bool desc)
 
    headers[cols] = NULL;
 
-
    /* Get column info (index requires additional checks) */
    if (tableinfo.relkind == 'i')
-       printfPQExpBuffer(&buf, "SELECT\n  CASE i.indproc WHEN ('-'::regproc) THEN a.attname\n  ELSE SUBSTR(pg_get_indexdef(attrelid),\n  POSITION('(' in pg_get_indexdef(attrelid)))\n  END, ");
+       printfPQExpBuffer(&buf, "SELECT\n  CASE i.indproc WHEN ('-'::pg_catalog.regproc) THEN a.attname\n  ELSE SUBSTR(pg_catalog.pg_get_indexdef(attrelid),\n  POSITION('(' in pg_catalog.pg_get_indexdef(attrelid)))\n  END,");
    else
-       printfPQExpBuffer(&buf, "SELECT a.attname, ");
-   appendPQExpBuffer(&buf, "format_type(a.atttypid, a.atttypmod), a.attnotnull, a.atthasdef, a.attnum");
-   if (desc)
-       appendPQExpBuffer(&buf, ", col_description(a.attrelid, a.attnum)");
-   appendPQExpBuffer(&buf, "\nFROM pg_class c, pg_attribute a");
+       printfPQExpBuffer(&buf, "SELECT a.attname,");
+   appendPQExpBuffer(&buf, "\n  pg_catalog.format_type(a.atttypid, a.atttypmod),\n"
+                     "  a.attnotnull, a.atthasdef, a.attnum");
+   if (verbose)
+       appendPQExpBuffer(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
+   appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_attribute a");
    if (tableinfo.relkind == 'i')
-       appendPQExpBuffer(&buf, ", pg_index i");
-   appendPQExpBuffer(&buf, "\nWHERE c.relname = '%s'\n  AND a.attnum > 0 AND NOT a.attisdropped AND a.attrelid = c.oid", name);
+       appendPQExpBuffer(&buf, ", pg_catalog.pg_index i");
+   appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
    if (tableinfo.relkind == 'i')
        appendPQExpBuffer(&buf, " AND a.attrelid = i.indexrelid");
    appendPQExpBuffer(&buf, "\nORDER BY a.attnum");
@@ -546,11 +707,11 @@ describeTableDetails(const char *name, bool desc)
        goto error_return;
 
    /* Check if table is a view */
-   if (tableinfo.hasrules)
+   if (tableinfo.relkind == 'v')
    {
        PGresult   *result;
 
-       printfPQExpBuffer(&buf, "SELECT definition FROM pg_views WHERE viewname = '%s'", name);
+       printfPQExpBuffer(&buf, "SELECT pg_catalog.pg_get_viewdef('%s'::pg_catalog.oid)", oid);
        result = PSQLexec(buf.data);
        if (!result)
        {
@@ -561,10 +722,10 @@ describeTableDetails(const char *name, bool desc)
 
        if (PQntuples(result) > 0)
            view_def = xstrdup(PQgetvalue(result, 0, 0));
+
        PQclear(result);
    }
 
-
    /* Generate table cells to be printed */
    cells = xmalloc((PQntuples(res) * cols + 1) * sizeof(*cells));
    cells[PQntuples(res) * cols] = NULL;        /* end of list */
@@ -593,9 +754,9 @@ describeTableDetails(const char *name, bool desc)
                PGresult   *result;
 
                printfPQExpBuffer(&buf,
-                                 "SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c\n"
-                                 "WHERE c.relname = '%s' AND c.oid = d.adrelid AND d.adnum = %s",
-                                 name, PQgetvalue(res, i, 4));
+                                 "SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d\n"
+                                 "WHERE d.adrelid = '%s' AND d.adnum = %s",
+                                 oid, PQgetvalue(res, i, 4));
 
                result = PSQLexec(buf.data);
 
@@ -609,7 +770,7 @@ describeTableDetails(const char *name, bool desc)
        }
 
        /* Description */
-       if (desc)
+       if (verbose)
            cells[i * cols + cols - 1] = PQgetvalue(res, i, 5);
    }
 
@@ -617,25 +778,32 @@ describeTableDetails(const char *name, bool desc)
    switch (tableinfo.relkind)
    {
        case 'r':
-           printfPQExpBuffer(&title, _("Table \"%s\""), name);
+           printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+                             schemaname, relationname);
            break;
        case 'v':
-           printfPQExpBuffer(&title, _("View \"%s\""), name);
+           printfPQExpBuffer(&title, _("View \"%s.%s\""),
+                             schemaname, relationname);
            break;
        case 'S':
-           printfPQExpBuffer(&title, _("Sequence \"%s\""), name);
+           printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
+                             schemaname, relationname);
            break;
        case 'i':
-           printfPQExpBuffer(&title, _("Index \"%s\""), name);
+           printfPQExpBuffer(&title, _("Index \"%s.%s\""),
+                             schemaname, relationname);
            break;
        case 's':
-           printfPQExpBuffer(&title, _("Special relation \"%s\""), name);
+           printfPQExpBuffer(&title, _("Special relation \"%s.%s\""),
+                             schemaname, relationname);
            break;
        case 't':
-           printfPQExpBuffer(&title, _("TOAST table \"%s\""), name);
+           printfPQExpBuffer(&title, _("TOAST table \"%s.%s\""),
+                             schemaname, relationname);
            break;
        default:
-           printfPQExpBuffer(&title, _("?%c? \"%s\""), tableinfo.relkind, name);
+           printfPQExpBuffer(&title, _("?%c? \"%s.%s\""),
+                             tableinfo.relkind, schemaname, relationname);
            break;
    }
 
@@ -646,11 +814,11 @@ describeTableDetails(const char *name, bool desc)
        PGresult   *result;
        printfPQExpBuffer(&buf,
                          "SELECT i.indisunique, i.indisprimary, a.amname, c2.relname,\n"
-                         "pg_get_expr(i.indpred,i.indrelid)\n"
-                         "FROM pg_index i, pg_class c, pg_class c2, pg_am a\n"
-                         "WHERE i.indexrelid = c.oid AND c.relname = '%s' AND c.relam = a.oid\n"
+                         "  pg_catalog.pg_get_expr(i.indpred, i.indrelid)\n"
+                         "FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n"
+                         "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n"
                          "AND i.indrelid = c2.oid",
-                         name);
+                         oid);
 
        result = PSQLexec(buf.data);
        if (!result)
@@ -679,7 +847,10 @@ describeTableDetails(const char *name, bool desc)
                resetPQExpBuffer(&tmpbuf);
            appendPQExpBuffer(&tmpbuf, "%s, ", indamname);
 
-           appendPQExpBuffer(&tmpbuf, _("for table \"%s\""), indtable);
+           /* we assume here that index and table are in same schema */
+           appendPQExpBuffer(&tmpbuf, _("for table \"%s.%s\""),
+                             schemaname, indtable);
+
            if (strlen(indpred))
                appendPQExpBuffer(&tmpbuf, ", predicate %s", indpred);
 
@@ -697,15 +868,14 @@ describeTableDetails(const char *name, bool desc)
        int         rule_count = 0;
        int         count_footers = 0;
 
-       /* count rules */
+       /* count rules other than the view rule */
        if (tableinfo.hasrules)
        {
            printfPQExpBuffer(&buf,
                    "SELECT r.rulename\n"
-                   "FROM pg_rewrite r, pg_class c\n"
-                   "WHERE c.relname = '%s' AND c.oid = r.ev_class\n"
-                   "AND r.rulename != '_RETURN'",
-                   name);
+                   "FROM pg_catalog.pg_rewrite r\n"
+                   "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN'",
+                    oid);
            result = PSQLexec(buf.data);
            if (!result)
                goto error_return;
@@ -756,13 +926,12 @@ describeTableDetails(const char *name, bool desc)
        if (tableinfo.hasindex)
        {
            printfPQExpBuffer(&buf,
-                   "SELECT c2.relname, i.indisprimary, i.indisunique,\n"
-                   "SUBSTR(pg_get_indexdef(i.indexrelid),\n"
-                   "POSITION('USING ' IN pg_get_indexdef(i.indexrelid))+5)\n"
-                   "FROM pg_class c, pg_class c2, pg_index i\n"
-                   "WHERE c.relname = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
+                   "SELECT c2.relname, i.indisprimary, i.indisunique, "
+                   "pg_catalog.pg_get_indexdef(i.indexrelid)\n"
+                   "FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n"
+                   "WHERE c.oid = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
                    "ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname",
-                   name);
+                   oid);
            result1 = PSQLexec(buf.data);
            if (!result1)
                goto error_return;
@@ -775,10 +944,9 @@ describeTableDetails(const char *name, bool desc)
        {
            printfPQExpBuffer(&buf,
                    "SELECT consrc, conname\n"
-                   "FROM pg_constraint r, pg_class c\n"
-                   "WHERE c.relname='%s' AND c.oid = r.conrelid\n"
-                   "AND r.contype = 'c'",
-                   name);
+                   "FROM pg_catalog.pg_constraint r\n"
+                   "WHERE r.conrelid = '%s' AND r.contype = 'c'",
+                   oid);
            result2 = PSQLexec(buf.data);
            if (!result2)
                goto error_return;
@@ -791,9 +959,9 @@ describeTableDetails(const char *name, bool desc)
        {
            printfPQExpBuffer(&buf,
                    "SELECT r.rulename\n"
-                   "FROM pg_rewrite r, pg_class c\n"
-                   "WHERE c.relname='%s' AND c.oid = r.ev_class",
-                   name);
+                   "FROM pg_catalog.pg_rewrite r\n"
+                   "WHERE r.ev_class = '%s'",
+                   oid);
            result3 = PSQLexec(buf.data);
            if (!result3)
                goto error_return;
@@ -806,9 +974,9 @@ describeTableDetails(const char *name, bool desc)
        {
            printfPQExpBuffer(&buf,
                    "SELECT t.tgname\n"
-                   "FROM pg_trigger t, pg_class c\n"
-                   "WHERE c.relname='%s' AND c.oid = t.tgrelid",
-                   name);
+                   "FROM pg_catalog.pg_trigger t\n"
+                   "WHERE t.tgrelid = '%s'",
+                   oid);
            result4 = PSQLexec(buf.data);
            if (!result4)
                goto error_return;
@@ -823,11 +991,15 @@ describeTableDetails(const char *name, bool desc)
        for (i = 0; i < index_count; i++)
        {
            char       *s = _("Indexes");
+           const char *indexdef;
+           const char *usingpos;
    
            if (i == 0)
-               printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result1, i, 0));
+               printfPQExpBuffer(&buf, "%s: %s", s,
+                                 PQgetvalue(result1, i, 0));
            else
-               printfPQExpBuffer(&buf, "%*s  %s", (int) strlen(s), "", PQgetvalue(result1, i, 0));
+               printfPQExpBuffer(&buf, "%*s  %s", (int) strlen(s), "",
+                                 PQgetvalue(result1, i, 0));
 
            /* Label as primary key or unique (but not both) */
            appendPQExpBuffer(&buf,
@@ -838,7 +1010,12 @@ describeTableDetails(const char *name, bool desc)
                               : ""));
 
            /* Everything after "USING" is echoed verbatim */
-           appendPQExpBuffer(&buf, "%s", PQgetvalue(result1,i,3));
+           indexdef = PQgetvalue(result1, i, 3);
+           usingpos = strstr(indexdef, " USING ");
+           if (usingpos)
+               indexdef = usingpos + 7;
+
+           appendPQExpBuffer(&buf, " %s", indexdef);
 
            if (i < index_count - 1)
                appendPQExpBuffer(&buf, ",");
@@ -931,6 +1108,9 @@ error_return:
        free(footers);
    }
 
+   if (view_def)
+       free(view_def);
+
    if (res)
        PQclear(res);
 
@@ -939,13 +1119,12 @@ error_return:
 
 
 /*
- * \du [user]
+ * \du
  *
- * Describes users, possibly based on a simplistic prefix search on the
- * argument.
+ * Describes users.  Any schema portion of the pattern is ignored.
  */
 bool
-describeUsers(const char *name)
+describeUsers(const char *pattern)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -956,18 +1135,20 @@ describeUsers(const char *name)
    printfPQExpBuffer(&buf,
             "SELECT u.usename AS \"%s\",\n"
             "  u.usesysid AS \"%s\",\n"
-            "  CASE WHEN u.usesuper AND u.usecreatedb THEN CAST('%s' AS text)\n"
-            "       WHEN u.usesuper THEN CAST('%s' AS text)\n"
-            "       WHEN u.usecreatedb THEN CAST('%s' AS text)\n"
-            "       ELSE CAST('' AS text)\n"
+            "  CASE WHEN u.usesuper AND u.usecreatedb THEN CAST('%s' AS pg_catalog.text)\n"
+            "       WHEN u.usesuper THEN CAST('%s' AS pg_catalog.text)\n"
+            "       WHEN u.usecreatedb THEN CAST('%s' AS pg_catalog.text)\n"
+            "       ELSE CAST('' AS pg_catalog.text)\n"
             "  END AS \"%s\"\n"
-            "FROM pg_user u\n",
+            "FROM pg_catalog.pg_user u\n",
             _("User name"), _("User ID"),
             _("superuser, create database"),
             _("superuser"), _("create database"),
             _("Attributes"));
-   if (name)
-       appendPQExpBuffer(&buf, "WHERE u.usename ~ '^%s'\n", name);
+
+   processNamePattern(&buf, pattern, false, false,
+                      NULL, "u.usename", NULL, NULL);
+
    appendPQExpBuffer(&buf, "ORDER BY 1;");
 
    res = PSQLexec(buf.data);
@@ -990,26 +1171,22 @@ describeUsers(const char *name)
  *
  * handler for \d, \dt, etc.
  *
- * The infotype is an array of characters, specifying what info is desired:
+ * tabtypes is an array of characters, specifying what info is desired:
  * t - tables
  * i - indexes
  * v - views
  * s - sequences
- * S - systems tables (~ '^pg_')
+ * S - system tables (~ '^pg_')
  * (any order of the above is fine)
- *
- * Note: For some reason it always happens to people that their tables have owners
- * that are no longer in pg_user; consequently they wouldn't show up here. The code
- * tries to fix this the painful way, hopefully outer joins will be done sometime.
  */
 bool
-listTables(const char *infotype, const char *name, bool desc)
+listTables(const char *tabtypes, const char *pattern, bool verbose)
 {
-   bool        showTables = strchr(infotype, 't') != NULL;
-   bool        showIndexes = strchr(infotype, 'i') != NULL;
-   bool        showViews = strchr(infotype, 'v') != NULL;
-   bool        showSeq = strchr(infotype, 's') != NULL;
-   bool        showSystem = strchr(infotype, 'S') != NULL;
+   bool        showTables = strchr(tabtypes, 't') != NULL;
+   bool        showIndexes = strchr(tabtypes, 'i') != NULL;
+   bool        showViews = strchr(tabtypes, 'v') != NULL;
+   bool        showSeq = strchr(tabtypes, 's') != NULL;
+   bool        showSystem = strchr(tabtypes, 'S') != NULL;
 
    PQExpBufferData buf;
    PGresult   *res;
@@ -1025,28 +1202,31 @@ listTables(const char *infotype, const char *name, bool desc)
             "  c.relname as \"%s\",\n"
             "  CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN '%s' END as \"%s\",\n"
             "  u.usename as \"%s\"",
-            _("Schema"), _("Name"), _("table"), _("view"), _("index"), _("sequence"),
+            _("Schema"), _("Name"),
+            _("table"), _("view"), _("index"), _("sequence"),
             _("special"), _("Type"), _("Owner"));
 
-   if (desc)
+   if (verbose)
        appendPQExpBuffer(&buf,
-                ",\n  obj_description(c.oid, 'pg_class') as \"%s\"",
+                ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
                 _("Description"));
+
     if (showIndexes)
        appendPQExpBuffer(&buf,
                          ",\n c2.relname as \"%s\""
-                         "\nFROM pg_class c, pg_class c2, pg_index i, pg_user u, pg_namespace n\n"
-                         "WHERE c.relowner = u.usesysid\n"
-                         "AND c.relnamespace = n.oid\n"
-                         "AND i.indrelid = c2.oid AND i.indexrelid = c.oid\n",
+                         "\nFROM pg_catalog.pg_class c"
+                         "\n     JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid"
+                         "\n     JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid"
+                         "\n     LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner"
+                         "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n",
                          _("Table"));
    else
        appendPQExpBuffer(&buf,
-                         "\nFROM pg_class c, pg_user u, pg_namespace n\n"
-                         "WHERE c.relowner = u.usesysid\n"
-                         "AND c.relnamespace = n.oid\n");
+                         "\nFROM pg_catalog.pg_class c"
+                         "\n     LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner"
+                         "\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n");
 
-   appendPQExpBuffer(&buf, "AND c.relkind IN (");
+   appendPQExpBuffer(&buf, "WHERE c.relkind IN (");
    if (showTables)
        appendPQExpBuffer(&buf, "'r',");
    if (showViews)
@@ -1060,13 +1240,18 @@ listTables(const char *infotype, const char *name, bool desc)
    appendPQExpBuffer(&buf, "''");          /* dummy */
    appendPQExpBuffer(&buf, ")\n");
 
+   /*
+    * Unless showSystem is specified, we suppress system tables, ie, those
+    * in pg_catalog and pg_toast.  (We don't want to hide temp tables though.)
+    */
    if (showSystem)
-       appendPQExpBuffer(&buf, "  AND n.nspname ~ '^pg_'\n");
+       processNamePattern(&buf, pattern, true, false,
+                          "n.nspname", "c.relname", NULL,
+                          "pg_catalog.pg_table_is_visible(c.oid)");
    else
-       appendPQExpBuffer(&buf, "  AND n.nspname !~ '^pg_'\n");
-
-   if (name)
-       appendPQExpBuffer(&buf, "  AND c.relname ~ '^%s'\n", name);
+       processNamePattern(&buf, pattern, true, false,
+                          "n.nspname", "c.relname", NULL,
+                          "pg_catalog.pg_table_is_visible(c.oid) AND n.nspname <> 'pg_catalog' AND n.nspname <> 'pg_toast'");
 
    appendPQExpBuffer(&buf, "ORDER BY 1,2;");
 
@@ -1077,7 +1262,7 @@ listTables(const char *infotype, const char *name, bool desc)
 
    if (PQntuples(res) == 0 && !QUIET())
    {
-       if (name)
+       if (pattern)
            fprintf(pset.queryFout, _("No matching relations found.\n"));
        else
            fprintf(pset.queryFout, _("No relations found.\n"));
@@ -1096,13 +1281,12 @@ listTables(const char *infotype, const char *name, bool desc)
 
 
 /*
- * \dD [domain]
+ * \dD
  *
- * Describes domains, possibly based on a simplistic prefix search on the
- * argument.
+ * Describes domains.
  */
 bool
-listDomains(const char *name)
+listDomains(const char *pattern)
 {
    PQExpBufferData buf;
    PGresult   *res;
@@ -1111,21 +1295,27 @@ listDomains(const char *name)
    initPQExpBuffer(&buf);
 
    printfPQExpBuffer(&buf,
-        "SELECT t.typname as \"%s\",\n"
-        "       format_type(t.typbasetype, t.typtypmod) as \"%s\",\n"
+        "SELECT n.nspname as \"%s\",\n"
+        "       t.typname as \"%s\",\n"
+        "       pg_catalog.format_type(t.typbasetype, t.typtypmod) as \"%s\",\n"
         "       CASE WHEN t.typnotnull AND t.typdefault IS NOT NULL THEN 'not null default '||t.typdefault\n"
         "            WHEN t.typnotnull AND t.typdefault IS NULL THEN 'not null'\n"
         "            WHEN NOT t.typnotnull AND t.typdefault IS NOT NULL THEN 'default '||t.typdefault\n"
         "            ELSE ''\n"
         "       END as \"%s\"\n"
-        "FROM pg_type t\n"
+        "FROM pg_catalog.pg_type t\n"
+        "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace\n"
         "WHERE t.typtype = 'd'\n",
+        _("Schema"),
         _("Name"),
         _("Type"),
         _("Modifier"));
-   if (name)
-       appendPQExpBuffer(&buf, "AND t.typname ~ '^%s'\n", name);
-   appendPQExpBuffer(&buf, "ORDER BY 1;");
+
+   processNamePattern(&buf, pattern, true, false,
+                      "n.nspname", "t.typname", NULL,
+                      "pg_catalog.pg_type_is_visible(t.oid)");
+
+   appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
 
    res = PSQLexec(buf.data);
    termPQExpBuffer(&buf);
@@ -1140,3 +1330,184 @@ listDomains(const char *name)
    PQclear(res);
    return true;
 }
+
+/*
+ * processNamePattern
+ *
+ * Scan a wildcard-pattern option and generate appropriate WHERE clauses
+ * to limit the set of objects returned.  The WHERE clauses are appended
+ * to buf.
+ *
+ * pattern: user-specified pattern option to a \d command, or NULL if none.
+ * have_where: true if caller already emitted WHERE.
+ * force_escape: always quote regexp special characters, even outside quotes.
+ * schemavar: name of WHERE variable to match against a schema-name pattern.
+ * Can be NULL if no schema.
+ * namevar: name of WHERE variable to match against an object-name pattern.
+ * altnamevar: NULL, or name of an alternate variable to match against name.
+ * visibilityrule: clause to use if we want to restrict to visible objects
+ * (for example, "pg_catalog.pg_table_is_visible(p.oid)").  Can be NULL.
+ */
+static void
+processNamePattern(PQExpBuffer buf, const char *pattern,
+                  bool have_where, bool force_escape,
+                  const char *schemavar, const char *namevar,
+                  const char *altnamevar, const char *visibilityrule)
+{
+   PQExpBufferData schemabuf;
+   PQExpBufferData namebuf;
+   bool        inquotes;
+   const char *cp;
+   int         i;
+
+#define WHEREAND() \
+   (appendPQExpBuffer(buf, have_where ? "      AND " : "WHERE "), have_where = true)
+
+   if (pattern == NULL)
+   {
+       /* Default: select all visible objects */
+       if (visibilityrule)
+       {
+           WHEREAND();
+           appendPQExpBuffer(buf, "%s\n", visibilityrule);
+       }
+       return;
+   }
+
+   initPQExpBuffer(&schemabuf);
+   initPQExpBuffer(&namebuf);
+
+   /*
+    * Parse the pattern, converting quotes and lower-casing unquoted
+    * letters; we assume this was NOT done by scan_option.  Also, adjust
+    * shell-style wildcard characters into regexp notation.
+    */
+   inquotes = false;
+   cp = pattern;
+
+   while (*cp)
+   {
+       if (*cp == '"')
+       {
+           if (inquotes && cp[1] == '"')
+           {
+               /* emit one quote */
+               appendPQExpBufferChar(&namebuf, '"');
+               cp++;
+           }
+           inquotes = !inquotes;
+           cp++;
+       }
+       else if (!inquotes && isupper((unsigned char) *cp))
+       {
+           appendPQExpBufferChar(&namebuf,
+                                 tolower((unsigned char) *cp));
+           cp++;
+       }
+       else if (!inquotes && *cp == '*')
+       {
+           appendPQExpBuffer(&namebuf, ".*");
+           cp++;
+       }
+       else if (!inquotes && *cp == '?')
+       {
+           appendPQExpBufferChar(&namebuf, '.');
+           cp++;
+       }
+       else if (!inquotes && *cp == '.')
+       {
+           /* Found schema/name separator, move current pattern to schema */
+           resetPQExpBuffer(&schemabuf);
+           appendPQExpBufferStr(&schemabuf, namebuf.data);
+           resetPQExpBuffer(&namebuf);
+           cp++;
+       }
+       else
+       {
+           /*
+            * Ordinary data character, transfer to pattern
+            *
+            * Inside double quotes, or at all times if parsing an operator
+            * name, quote regexp special characters with a backslash to avoid
+            * regexp errors.  Outside quotes, however, let them pass through
+            * as-is; this lets knowledgeable users build regexp expressions
+            * that are more powerful than shell-style patterns.
+            */
+           if ((inquotes || force_escape) &&
+               strchr("|*+?()[]{}.^$\\", *cp))
+               appendPQExpBuffer(&namebuf, "\\\\");
+
+           /* Ensure chars special to string literals are passed properly */
+           if (*cp == '\'' || *cp == '\\')
+               appendPQExpBufferChar(&namebuf, *cp);
+
+           i = PQmblen(cp, pset.encoding);
+           while (i--)
+           {
+               appendPQExpBufferChar(&namebuf, *cp);
+               cp++;
+           }
+       }
+   }
+
+   /*
+    * Now decide what we need to emit.
+    */
+   if (schemabuf.len > 0)
+   {
+       /* We have a schema pattern, so constrain the schemavar */
+
+       appendPQExpBufferChar(&schemabuf, '$');
+       /* Optimize away ".*$", and possibly the whole pattern */
+       if (schemabuf.len >= 3 &&
+           strcmp(schemabuf.data + (schemabuf.len-3), ".*$") == 0)
+           schemabuf.data[schemabuf.len-3] = '\0';
+
+       if (schemabuf.data[0] && schemavar)
+       {
+           WHEREAND();
+           appendPQExpBuffer(buf, "%s ~ '^%s'\n",
+                             schemavar, schemabuf.data);
+       }
+   }
+   else
+   {
+       /* No schema pattern given, so select only visible objects */
+       if (visibilityrule)
+       {
+           WHEREAND();
+           appendPQExpBuffer(buf, "%s\n", visibilityrule);
+       }
+   }
+
+   if (namebuf.len > 0)
+   {
+       /* We have a name pattern, so constrain the namevar(s) */
+
+       appendPQExpBufferChar(&namebuf, '$');
+       /* Optimize away ".*$", and possibly the whole pattern */
+       if (namebuf.len >= 3 &&
+           strcmp(namebuf.data + (namebuf.len-3), ".*$") == 0)
+           namebuf.data[namebuf.len-3] = '\0';
+
+       if (namebuf.data[0])
+       {
+           WHEREAND();
+           if (altnamevar)
+               appendPQExpBuffer(buf,
+                                 "(%s ~ '^%s'\n"
+                                 "        OR %s ~ '^%s')\n",
+                                 namevar, namebuf.data,
+                                 altnamevar, namebuf.data);
+           else
+               appendPQExpBuffer(buf,
+                                 "%s ~ '^%s'\n",
+                                 namevar, namebuf.data);
+       }
+   }
+
+   termPQExpBuffer(&schemabuf);
+   termPQExpBuffer(&namebuf);
+
+#undef WHEREAND
+}
index 4c95733912a389fec3b4365fcb6853281631d3f3..41edcd98fbb9804b438b38a7e73259b1da42c448 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright 2000 by PostgreSQL Global Development Group
+ * Copyright 2000-2002 by PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/describe.h,v 1.16 2002/03/19 02:32:21 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/describe.h,v 1.17 2002/08/10 03:56:24 tgl Exp $
  */
 #ifndef DESCRIBE_H
 #define DESCRIBE_H
 #include "settings.h"
 
 /* \da */
-bool       describeAggregates(const char *name);
+bool       describeAggregates(const char *pattern, bool verbose);
 
 /* \df */
-bool       describeFunctions(const char *name, bool verbose);
+bool       describeFunctions(const char *pattern, bool verbose);
 
 /* \dT */
-bool       describeTypes(const char *name, bool verbose);
+bool       describeTypes(const char *pattern, bool verbose);
 
 /* \do */
-bool       describeOperators(const char *name);
+bool       describeOperators(const char *pattern);
 
 /* \du */
-bool       describeUsers(const char *name);
+bool       describeUsers(const char *pattern);
 
 /* \z (or \dp) */
-bool       permissionsList(const char *name);
+bool       permissionsList(const char *pattern);
 
 /* \dd */
-bool       objectDescription(const char *object);
+bool       objectDescription(const char *pattern);
 
 /* \d foo */
-bool       describeTableDetails(const char *name, bool desc);
+bool       describeTableDetails(const char *pattern, bool verbose);
 
 /* \l */
 bool       listAllDbs(bool desc);
 
 /* \dt, \di, \ds, \dS, etc. */
-bool       listTables(const char *infotype, const char *name, bool desc);
+bool       listTables(const char *tabtypes, const char *pattern, bool verbose);
 
 /* \dD */
-bool       listDomains(const char *name);
+bool       listDomains(const char *pattern);
 
 #endif   /* DESCRIBE_H */
index d134156477fb65aa9901ca04c2156f87e0877b7b..0e90fceb6c4711e5056706115e66a63e1e6fef1b 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright 2000 by PostgreSQL Global Development Group
+ * Copyright 2000-2002 by PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/large_obj.c,v 1.19 2002/03/06 06:10:31 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/large_obj.c,v 1.20 2002/08/10 03:56:24 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "large_obj.h"
@@ -209,9 +209,10 @@ do_lo_import(const char *filename_arg, const char *comment_arg)
            return false;
        }
        sprintf(cmdbuf,
-               "INSERT INTO pg_description VALUES ('%u', "
-          "(SELECT oid FROM pg_class WHERE relname = 'pg_largeobject'),"
-               " 0, '", loid);
+               "INSERT INTO pg_catalog.pg_description VALUES ('%u', "
+               "'pg_catalog.pg_largeobject'::regclass, "
+               "0, '",
+               loid);
        bufptr = cmdbuf + strlen(cmdbuf);
        for (i = 0; i < slen; i++)
        {
@@ -310,8 +311,8 @@ do_lo_unlink(const char *loid_arg)
    /* XXX ought to replace this with some kind of COMMENT command */
    if (pset.issuper)
    {
-       sprintf(buf, "DELETE FROM pg_description WHERE objoid = '%u' "
-               "AND classoid = (SELECT oid FROM pg_class WHERE relname = 'pg_largeobject')",
+       sprintf(buf, "DELETE FROM pg_catalog.pg_description WHERE objoid = '%u' "
+               "AND classoid = 'pg_catalog.pg_largeobject'::regclass",
                loid);
        if (!(res = PSQLexec(buf)))
        {
@@ -356,8 +357,8 @@ do_lo_list(void)
    printQueryOpt myopt = pset.popt;
 
    snprintf(buf, sizeof(buf),
-            "SELECT loid as \"ID\", obj_description(loid, 'pg_largeobject') as \"%s\"\n"
-            "FROM (SELECT DISTINCT loid FROM pg_largeobject) x\n"
+            "SELECT loid as \"ID\", pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n"
+            "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n"
             "ORDER BY \"ID\"",
             gettext("Description"));
 
index 3c0da27b759162c26fd1adcfd7bc66c7053f8bb7..e7fd3df45f95b1366f1473c4da3078ad4e7db97d 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright 2000 by PostgreSQL Global Development Group
+ * Copyright 2000-2002 by PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.55 2002/08/04 05:01:57 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.56 2002/08/10 03:56:24 tgl Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -118,11 +118,20 @@ initialize_readline(void)
 }
 
 
+/*
+ * Queries to get lists of names of various kinds of things, possibly
+ * restricted to names matching a partially entered name.  In these queries,
+ * the %s will be replaced by the text entered so far, the %d by its length.
+ */
+
+#define Query_for_list_of_tables "SELECT relname FROM pg_catalog.pg_class WHERE (relkind='r' or relkind='v') and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid)"
+#define Query_for_list_of_indexes "SELECT relname FROM pg_catalog.pg_class WHERE relkind='i' and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid)"
+#define Query_for_list_of_databases "SELECT datname FROM pg_catalog.pg_database WHERE substr(datname,1,%d)='%s'"
+#define Query_for_list_of_attributes "SELECT a.attname FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and not a.attisdropped and substr(a.attname,1,%d)='%s' and c.relname='%s' and pg_catalog.pg_table_is_visible(c.oid)"
+#define Query_for_list_of_users "SELECT usename FROM pg_catalog.pg_user WHERE substr(usename,1,%d)='%s'"
+
 /* This is a list of all "things" in Pgsql, which can show up after CREATE or
    DROP; and there is also a query to get a list of them.
-   The %s will be replaced by the text entered so far, the %d by its length.
-   If you change the order here or insert things, make sure to also adjust the
-   referencing macros below.
 */
 typedef struct
 {
@@ -131,37 +140,29 @@ typedef struct
 } pgsql_thing_t;
 
 pgsql_thing_t words_after_create[] = {
-   {"AGGREGATE", "SELECT distinct proname FROM pg_catalog.pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"},
-   {"DATABASE", "SELECT datname FROM pg_catalog.pg_database WHERE substr(datname,1,%d)='%s'"},
-   {"FUNCTION", "SELECT distinct proname FROM pg_catalog.pg_proc WHERE substr(proname,1,%d)='%s'"},
+   {"AGGREGATE", "SELECT DISTINCT proname FROM pg_catalog.pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"},
+   {"DATABASE", Query_for_list_of_databases},
+   {"FUNCTION", "SELECT DISTINCT proname FROM pg_catalog.pg_proc WHERE substr(proname,1,%d)='%s'"},
    {"GROUP", "SELECT groname FROM pg_catalog.pg_group WHERE substr(groname,1,%d)='%s'"},
-   {"INDEX", "SELECT relname FROM pg_catalog.pg_class WHERE relkind='i' and substr(relname,1,%d)='%s'"},
+   {"INDEX", Query_for_list_of_indexes},
    {"OPERATOR", NULL},         /* Querying for this is probably not such a good idea. */
    {"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE substr(rulename,1,%d)='%s'"},
    {"SCHEMA", "SELECT nspname FROM pg_catalog.pg_namespace WHERE substr(nspname,1,%d)='%s'"},
    {"SEQUENCE", "SELECT relname FROM pg_catalog.pg_class WHERE relkind='S' and substr(relname,1,%d)='%s'"},
-   {"TABLE", "SELECT relname FROM pg_catalog.pg_class WHERE (relkind='r' or relkind='v') and substr(relname,1,%d)='%s'"},
+   {"TABLE", Query_for_list_of_tables},
    {"TEMP", NULL},             /* for CREATE TEMP TABLE ... */
    {"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substr(tgname,1,%d)='%s'"},
    {"TYPE", "SELECT typname FROM pg_catalog.pg_type WHERE substr(typname,1,%d)='%s'"},
    {"UNIQUE", NULL},           /* for CREATE UNIQUE INDEX ... */
-   {"USER", "SELECT usename FROM pg_catalog.pg_user WHERE substr(usename,1,%d)='%s'"},
+   {"USER", Query_for_list_of_users},
    {"VIEW", "SELECT viewname FROM pg_catalog.pg_views WHERE substr(viewname,1,%d)='%s'"},
    {NULL, NULL}                /* end of list */
 };
 
 
-/* The query to get a list of tables and a list of indexes, which are used at
-   various places. */
-#define Query_for_list_of_tables words_after_create[9].query
-#define Query_for_list_of_indexes words_after_create[4].query
-#define Query_for_list_of_databases words_after_create[1].query
-#define Query_for_list_of_attributes "SELECT a.attname FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and not a.attisdropped and substr(a.attname,1,%d)='%s' and c.relname='%s'"
-#define Query_for_list_of_users words_after_create[14].query
-
 /* A couple of macros to ease typing. You can use these to complete the given
    string with
-   1) The results from a query you pass it. (Perhaps one of those right above?)
+   1) The results from a query you pass it. (Perhaps one of those above?)
    2) The items from a null-pointer-terminated list.
    3) A string constant
    4) The list of attributes to the given table.
@@ -375,7 +376,7 @@ psql_completion(char *text, int start, int end)
                                                 * queries. */
 
        if (snprintf(query_buffer, BUF_SIZE,
-                    "SELECT c1.relname FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s'",
+                    "SELECT c1.relname FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s' and pg_catalog.pg_table_is_visible(c2.oid)",
                     prev2_wd) == -1)
            ERROR_QUERY_TOO_LONG;
        else
@@ -389,7 +390,8 @@ psql_completion(char *text, int start, int end)
    {
        char       *list_COMMENT[] =
        {"DATABASE", "INDEX", "RULE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
-       "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", "TRIGGER", NULL};
+        "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", "TRIGGER", "CONSTRAINT",
+        "DOMAIN", NULL};
 
        COMPLETE_WITH_LIST(list_COMMENT);
    }
@@ -440,7 +442,7 @@ psql_completion(char *text, int start, int end)
    /* Complete USING with an index method */
    else if (strcasecmp(prev_wd, "USING") == 0)
    {
-       char       *index_mth[] = {"BTREE", "RTREE", "HASH", NULL};
+       char       *index_mth[] = {"BTREE", "RTREE", "HASH", "GIST", NULL};
 
        COMPLETE_WITH_LIST(index_mth);
    }
@@ -553,7 +555,7 @@ psql_completion(char *text, int start, int end)
    /* Complete GRANT/REVOKE with a list of privileges */
    else if (strcasecmp(prev_wd, "GRANT") == 0 || strcasecmp(prev_wd, "REVOKE") == 0)
    {
-       char       *list_privileg[] = {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "ALL", NULL};
+       char       *list_privileg[] = {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES", "TRIGGER", "CREATE", "TEMPORARY", "EXECUTE", "USAGE", "ALL", NULL};
 
        COMPLETE_WITH_LIST(list_privileg);
    }
@@ -563,14 +565,15 @@ psql_completion(char *text, int start, int end)
 
    /*
     * Complete GRANT/REVOKE  ON with a list of tables, views,
-    * schema, sequences, and indexes
+    * sequences, and indexes
+    *
+    * XXX should also offer DATABASE, FUNCTION, LANGUAGE, SCHEMA here
     */
    else if ((strcasecmp(prev3_wd, "GRANT") == 0 || strcasecmp(prev3_wd, "REVOKE") == 0) &&
             strcasecmp(prev_wd, "ON") == 0)
        COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class "
                            "WHERE relkind in ('r','i','S','v') AND "
-                           "substr(relname,1,%d)='%s' UNION "
-                           "SELECT nspname FROM pg_catalog.pg_namespace;");
+                           "substr(relname,1,%d)='%s' AND pg_catalog.pg_table_is_visible(oid)");
    /* Complete "GRANT * ON * " with "TO" */
    else if (strcasecmp(prev4_wd, "GRANT") == 0 && strcasecmp(prev2_wd, "ON") == 0)
        COMPLETE_WITH_CONST("TO");
@@ -745,7 +748,7 @@ psql_completion(char *text, int start, int end)
 
 /* UNLISTEN */
    else if (strcasecmp(prev_wd, "UNLISTEN") == 0)
-       COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_listener WHERE substr(relname,1,%d)='%s' UNION SELECT '*'::text");
+       COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_listener WHERE substr(relname,1,%d)='%s' UNION SELECT '*'::name");
 
 /* UPDATE */
    /* If prev. word is UPDATE suggest a list of tables */
@@ -765,7 +768,7 @@ psql_completion(char *text, int start, int end)
 
 /* VACUUM */
    else if (strcasecmp(prev_wd, "VACUUM") == 0)
-       COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class WHERE relkind='r' and substr(relname,1,%d)='%s' UNION SELECT 'FULL'::text UNION SELECT 'ANALYZE'::text");
+       COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class WHERE relkind='r' and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid) UNION SELECT 'FULL'::name UNION SELECT 'ANALYZE'::name");
    else if (strcasecmp(prev2_wd, "VACUUM") == 0 && (strcasecmp(prev_wd, "FULL") == 0 || strcasecmp(prev_wd, "ANALYZE") == 0))
        COMPLETE_WITH_QUERY(Query_for_list_of_tables);