psql backslash commands are schema-aware. Pattern matching behavior

follows recent pghackers discussion.  This commit includes all the
relevant fixes from Greg Mullane's patch of 24-June.
This commit is contained in:
Tom Lane 2002-08-10 03:56:24 +00:00
parent 6ce4a4e3e1
commit 039cb47988
7 changed files with 944 additions and 517 deletions

View File

@ -1,5 +1,5 @@
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/grant.sgml,v 1.26 2002/05/14 18:47:58 tgl Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/ref/grant.sgml,v 1.27 2002/08/10 03:56:23 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
@ -249,16 +249,17 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
</para> </para>
<para> <para>
Use <xref linkend="app-psql">'s <command>\z</command> command Use <xref linkend="app-psql">'s <command>\dp</command> command
to obtain information about existing privileges, for example: to obtain information about existing privileges, for example:
<programlisting> <programlisting>
lusitania=> \z mytable lusitania=> \dp mytable
Access privileges for database "lusitania" Access privileges for database "lusitania"
Table | Access privileges Schema | Table | Access privileges
---------+--------------------------------------- --------+---------+---------------------------------------
mytable | {=r,miriam=arwdRxt,"group todos=arw"} public | mytable | {=r,miriam=arwdRxt,"group todos=arw"}
(1 row)
</programlisting> </programlisting>
The entries shown by <command>\z</command> are interpreted thus: The entries shown by <command>\dp</command> are interpreted thus:
<programlisting> <programlisting>
=xxxx -- privileges granted to PUBLIC =xxxx -- privileges granted to PUBLIC
uname=xxxx -- privileges granted to a user uname=xxxx -- privileges granted to a user

View File

@ -1,5 +1,5 @@
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.69 2002/07/28 15:22:21 petere Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.70 2002/08/10 03:56:23 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
@ -538,7 +538,7 @@ testdb=>
</para> </para>
<para> <para>
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, single quote. To include a single quote into such an argument,
precede it by a backslash. Anything contained in single quotes is precede it by a backslash. Anything contained in single quotes is
furthermore subject to C-like substitutions for furthermore subject to C-like substitutions for
@ -551,25 +551,24 @@ testdb=>
<para> <para>
If an unquoted argument begins with a colon (<literal>:</literal>), If an unquoted argument begins with a colon (<literal>:</literal>),
it is taken as a variable and the value of the variable is taken as it is taken as a <application>psql</> variable and the value of the
the argument instead. variable is used as the argument instead.
</para> </para>
<para> <para>
Arguments that are quoted in <quote>backticks</quote> Arguments that are quoted in <quote>backticks</quote>
(<literal>`</literal>) are taken as a command line that is passed to (<literal>`</literal>) 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 removed) is taken as the argument value. The above escape sequences
also apply in backticks. also apply in backticks.
</para> </para>
<para> <para>
Some commands take the name of an <acronym>SQL</acronym> identifier Some commands take an <acronym>SQL</acronym> identifier
(such as a table name) as argument. These arguments follow the (such as a table name) as argument. These arguments follow the
syntax rules of <acronym>SQL</acronym> regarding double quotes: an syntax rules of <acronym>SQL</acronym> regarding double quotes: an
identifier without double quotes is coerced to lower-case. For all identifier without double quotes is coerced to lower-case, while
other commands double quotes are not special and will become part of whitespace within double quotes is included in the argument.
the argument.
</para> </para>
<para> <para>
@ -732,18 +731,17 @@ testdb=>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><literal>\d</literal> <replaceable class="parameter">relation</replaceable> </term> <term><literal>\d</literal> [ <replaceable class="parameter">pattern</replaceable> ]</term>
<listitem> <listitem>
<para> <para>
Shows all columns of <replaceable For each relation (table, view, index, or sequence) matching the
class="parameter">relation</replaceable> (which could be a <replaceable class="parameter">pattern</replaceable>, show all
table, view, index, or sequence), their types, and any special columns, their types, and any special
attributes such as <literal>NOT NULL</literal> or defaults, if attributes such as <literal>NOT NULL</literal> or defaults, if
any. If the relation is, in fact, a table, any defined indices, any. Associated indexes, constraints, rules, and triggers are
primary keys, unique constraints and check constraints are also also shown, as is the view definition if the relation is a view.
listed. If the relation is a view, the view definition is also (<quote>Matching the pattern</> is defined below.)
shown.
</para> </para>
<para> <para>
@ -753,7 +751,8 @@ testdb=>
<note> <note>
<para> <para>
If <command>\d</command> is called without any arguments, it is If <command>\d</command> is used without a
<replaceable class="parameter">pattern</replaceable> argument, it is
equivalent to <command>\dtvs</command> which will show a list of equivalent to <command>\dtvs</command> which will show a list of
all tables, views, and sequences. This is purely a convenience all tables, views, and sequences. This is purely a convenience
measure. measure.
@ -776,34 +775,35 @@ testdb=>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><literal>\dd</literal> [ <replaceable class="parameter">object</replaceable> ]</term> <term><literal>\dd</literal> [ <replaceable class="parameter">pattern</replaceable> ]</term>
<listitem> <listitem>
<para> <para>
Shows the descriptions of <replaceable Shows the descriptions of objects matching the <replaceable
class="parameter">object</replaceable> (which can be a regular class="parameter">pattern</replaceable>, or of all visible objects if
expression), or of all objects if no argument is given. no argument is given. But in either case, only objects that have
a description are listed.
(<quote>Object</quote> covers aggregates, functions, operators, (<quote>Object</quote> covers aggregates, functions, operators,
types, relations (tables, views, indexes, sequences, large types, relations (tables, views, indexes, sequences, large
objects), rules, and triggers.) For example: objects), rules, and triggers.) For example:
<programlisting> <programlisting>
=> <userinput>\dd version</userinput> => <userinput>\dd version</userinput>
Object descriptions Object descriptions
Name | What | Description Schema | Name | Object | Description
---------+----------+--------------------------- ------------+---------+----------+---------------------------
version | function | PostgreSQL version string pg_catalog | version | function | PostgreSQL version string
(1 row) (1 row)
</programlisting> </programlisting>
</para> </para>
<para> <para>
Descriptions for objects can be generated with the Descriptions for objects can be created with the
<command>COMMENT ON</command> <acronym>SQL</acronym> command. <command>COMMENT ON</command> <acronym>SQL</acronym> command.
</para> </para>
<note> <note>
<para> <para>
<productname>PostgreSQL</productname> stores the object <productname>PostgreSQL</productname> stores the object
descriptions in the pg_description system table. descriptions in the <structname>pg_description</> system table.
</para> </para>
</note> </note>
@ -816,7 +816,7 @@ testdb=>
<listitem> <listitem>
<para> <para>
Lists all available domains (derived types). If <replaceable Lists all available domains (derived types). If <replaceable
class="parameter">pattern</replaceable> (a regular expression) class="parameter">pattern</replaceable>
is specified, only matching domains are shown. is specified, only matching domains are shown.
</para> </para>
</listitem> </listitem>
@ -830,7 +830,7 @@ testdb=>
<para> <para>
Lists available functions, together with their argument and Lists available functions, together with their argument and
return types. If <replaceable return types. If <replaceable
class="parameter">pattern</replaceable> (a regular expression) class="parameter">pattern</replaceable>
is specified, only matching functions are shown. If the form is specified, only matching functions are shown. If the form
<literal>\df+</literal> is used, additional information about <literal>\df+</literal> is used, additional information about
each function, including language and description, is shown. each function, including language and description, is shown.
@ -844,18 +844,17 @@ testdb=>
<listitem> <listitem>
<para> <para>
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, stand for index, sequence, table, view, and system table,
respectively. You can specify any or all of them in any order to respectively. You can specify any or all of these letters, in any
obtain a listing of them, together with who the owner is. order, to obtain a listing of all the matching objects.
If <quote>+</quote> is appended to the command name, each object is
listed with its associated description, if any.
</para> </para>
<para> <para>
If <replaceable class="parameter">pattern</replaceable> is If a <replaceable class="parameter">pattern</replaceable> is
specified, it is a regular expression that restricts the listing specified, only objects whose name matches the pattern are listed.
to those objects whose name matches. If one appends a
<quote>+</quote> to the command name, each object is listed with
its associated description, if any.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -873,12 +872,12 @@ testdb=>
<varlistentry> <varlistentry>
<term><literal>\do [ <replaceable class="parameter">name</replaceable> ]</literal></term> <term><literal>\do [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
<listitem> <listitem>
<para> <para>
Lists available operators with their operand and return types. Lists available operators with their operand and return types.
If <replaceable class="parameter">name</replaceable> is If a <replaceable class="parameter">pattern</replaceable> is
specified, only operators with that name will be shown. specified, only operators whose name matches the pattern are listed.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -888,9 +887,17 @@ testdb=>
<term><literal>\dp</literal> [ <replaceable class="parameter">pattern</replaceable> ]</term> <term><literal>\dp</literal> [ <replaceable class="parameter">pattern</replaceable> ]</term>
<listitem> <listitem>
<para> <para>
This is an alias for <command>\z</command> which was included Produces a list of all available tables with their
for its greater mnemonic value (<quote>display associated access permissions.
permissions</quote>). If a <replaceable class="parameter">pattern</replaceable> is
specified, only tables whose name matches the pattern are listed.
</para>
<para>
The commands <xref linkend="SQL-GRANT"> and
<xref linkend="SQL-REVOKE">
are used to set access permissions. See <xref linkend="SQL-GRANT">
for more information.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -912,7 +919,7 @@ testdb=>
<term><literal>\du [ <replaceable class="parameter">pattern</replaceable> ]</literal></term> <term><literal>\du [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
<listitem> <listitem>
<para> <para>
Lists all configured users or only those that match <replaceable Lists all database users, or only those that match <replaceable
class="parameter">pattern</replaceable>. class="parameter">pattern</replaceable>.
</para> </para>
</listitem> </listitem>
@ -1608,57 +1615,23 @@ lo_import 152801
<term><literal>\z</literal> [ <replaceable class="parameter">pattern</replaceable> ]</term> <term><literal>\z</literal> [ <replaceable class="parameter">pattern</replaceable> ]</term>
<listitem> <listitem>
<para> <para>
Produces a list of all tables in the database with their Produces a list of all available tables with their
appropriate access permissions listed. If an argument is given associated access permissions.
it is taken as a regular expression which limits the listing to If a <replaceable class="parameter">pattern</replaceable> is
those tables which match it. specified, only tables whose name matches the pattern are listed.
</para>
<para>
<programlisting>
test=&gt; <userinput>\z</userinput>
Access permissions for database "test"
Relation | Access permissions
----------+-------------------------------------
my_table | {"=r","joe=arwR", "group staff=ar"}
(1 row )
</programlisting>
Read this as follows:
<itemizedlist>
<listitem>
<para>
<literal>"=r"</literal>: <literal>PUBLIC</literal> has read
(<command>SELECT</command>) permission on the table.
</para>
</listitem>
<listitem>
<para>
<literal>"joe=arwR"</literal>: User <literal>joe</literal> has
read, write (<command>UPDATE</command>,
<command>DELETE</command>), <quote>append</quote>
(<command>INSERT</command>) permissions, and permission to
create rules on the table.
</para>
</listitem>
<listitem>
<para>
<literal>"group staff=ar"</literal>: Group
<literal>staff</literal> has <command>SELECT</command> and
<command>INSERT</command> permission.
</para>
</listitem>
</itemizedlist>
</para> </para>
<para> <para>
The commands <xref linkend="SQL-GRANT"> and The commands <xref linkend="SQL-GRANT"> and
<xref linkend="SQL-REVOKE"> <xref linkend="SQL-REVOKE">
are used to set access permissions. are used to set access permissions. See <xref linkend="SQL-GRANT">
for more information.
</para> </para>
<para>
This is an alias for <command>\dp</command> (<quote>display
permissions</quote>).
</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -1688,6 +1661,46 @@ Access permissions for database "test"
</variablelist> </variablelist>
</para> </para>
<para>
The various <literal>\d</> commands accept a <replaceable
class="parameter">pattern</replaceable> 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 (<literal>"</>) 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, <literal>FOO"BAR"BAZ</> is interpreted
as <literal>fooBARbaz</>, and <literal>"A weird"" name"</> becomes
<literal>A weird" name</>.
</para>
<para>
More interestingly, <literal>\d</> patterns allow the use of
<literal>*</> to mean <quote>any sequence of characters</>, and
<literal>?</> to mean <quote>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 <literal>[0-9]</> to match <quote>any digit</>. To make any of
these pattern-matching characters be interpreted literally, surround it
with double quotes.
</para>
<para>
A pattern that contains an (unquoted) dot is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal> \dt foo*.bar*</> displays all tables in schemas whose name
starts with <literal>foo</> and whose table name
starts with <literal>bar</>. If no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
</para>
<para>
Whenever the <replaceable class="parameter">pattern</replaceable> parameter
is omitted completely, the <literal>\d</> commands display all objects
that are visible in the current schema search path. To see all objects
in the database, use the pattern <literal>*.*</>.
</para>
</refsect2> </refsect2>
<refsect2> <refsect2>
@ -2402,11 +2415,12 @@ $ ./configure --with-includes=/opt/gnu/include --with-libs=/opt/gnu/lib ...
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
In some earlier life <application>psql</application> allowed the In an earlier life <application>psql</application> allowed the
first argument to start directly after the (single-letter) first argument of a single-letter backslash command to start
command. For compatibility this is still supported to some extent 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 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 For example
<programlisting> <programlisting>
testdb=> <userinput>\foo</userinput> testdb=> <userinput>\foo</userinput>
@ -2421,7 +2435,8 @@ Field separator is "oo",
<application>psql</application> only works smoothly with servers <application>psql</application> only works smoothly with servers
of the same version. That does not mean other combinations will of the same version. That does not mean other combinations will
fail outright, but subtle and not-so-subtle problems might come 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.
</para> </para>
</listitem> </listitem>

View File

@ -1,9 +1,9 @@
/* /*
* psql - the PostgreSQL interactive terminal * 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 "postgres_fe.h"
#include "command.h" #include "command.h"
@ -54,7 +54,7 @@ enum option_type
OT_NORMAL, /* normal case */ OT_NORMAL, /* normal case */
OT_SQLID, /* treat as SQL identifier */ OT_SQLID, /* treat as SQL identifier */
OT_SQLIDHACK, /* SQL identifier, but don't downcase */ 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, static char *scan_option(char **string, enum option_type type,
@ -328,10 +328,11 @@ exec_command(const char *cmd,
/* \d* commands */ /* \d* commands */
else if (cmd[0] == 'd') else if (cmd[0] == 'd')
{ {
char *name; char *pattern;
bool show_verbose; 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; show_verbose = strchr(cmd, '+') ? true : false;
@ -339,51 +340,53 @@ exec_command(const char *cmd,
{ {
case '\0': case '\0':
case '+': case '+':
if (name) if (pattern)
success = describeTableDetails(name, show_verbose); success = describeTableDetails(pattern, show_verbose);
else else
/* standard listing of interesting things */ /* standard listing of interesting things */
success = listTables("tvs", NULL, show_verbose); success = listTables("tvs", NULL, show_verbose);
break; break;
case 'a': case 'a':
success = describeAggregates(name); success = describeAggregates(pattern, show_verbose);
break; break;
case 'd': case 'd':
success = objectDescription(name); success = objectDescription(pattern);
break; break;
case 'f': case 'f':
success = describeFunctions(name, show_verbose); success = describeFunctions(pattern, show_verbose);
break; break;
case 'l': case 'l':
success = do_lo_list(); success = do_lo_list();
break; break;
case 'o': case 'o':
success = describeOperators(name); success = describeOperators(pattern);
break; break;
case 'p': case 'p':
success = permissionsList(name); success = permissionsList(pattern);
break; break;
case 'T': case 'T':
success = describeTypes(name, show_verbose); success = describeTypes(pattern, show_verbose);
break; break;
case 't': case 't':
case 'v': case 'v':
case 'i': case 'i':
case 's': case 's':
case 'S': case 'S':
success = listTables(&cmd[1], name, show_verbose); success = listTables(&cmd[1], pattern, show_verbose);
break; break;
case 'u': case 'u':
success = describeUsers(name); success = describeUsers(pattern);
break; break;
case 'D': case 'D':
success = listDomains(name); success = listDomains(pattern);
break; break;
default: default:
status = CMD_UNKNOWN; 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); 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) 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); success = permissionsList(pattern);
free(opt); if (pattern)
free(pattern);
} }
/* \! -- shell escape */ /* \! -- shell escape */
@ -881,11 +885,27 @@ exec_command(const char *cmd,
/* /*
* scan_option() * 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 * static char *
scan_option(char **string, enum option_type type, char *quote, bool semicolon) scan_option(char **string, enum option_type type, char *quote, bool semicolon)
{ {
unsigned int pos = 0; unsigned int pos;
char *options_string; char *options_string;
char *return_val; char *return_val;
@ -897,82 +917,27 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
options_string = *string; options_string = *string;
/* skip leading whitespace */ /* skip leading whitespace */
pos += strspn(options_string + pos, " \t\n\r"); pos = strspn(options_string, " \t\n\r");
switch (options_string[pos]) switch (options_string[pos])
{ {
/* /*
* Double quoted string * End of line: no option present
*/ */
case '"': case '\0':
{ *string = &options_string[pos];
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 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 * Next command: treat like end of line
* then we strip out the double quotes *
* 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 '\\':
if (type == OT_SQLID || type == OT_SQLIDHACK) *string = &options_string[pos];
{ return NULL;
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 = '"';
return return_val;
}
/* /*
* A single quote has a psql internal meaning, such as for * 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 '`': case '`':
{ {
bool error = false; bool error = false;
FILE *fd = NULL; FILE *fd;
char *file; char *file;
PQExpBufferData output; PQExpBufferData output;
char buf[512]; char buf[512];
@ -1040,10 +1005,10 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
error = true; error = true;
} }
if (!error)
{
initPQExpBuffer(&output); initPQExpBuffer(&output);
if (!error)
{
do do
{ {
result = fread(buf, 1, 512, fd); 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); appendBinaryPQExpBuffer(&output, buf, result);
} while (!feof(fd)); } while (!feof(fd));
appendPQExpBufferChar(&output, '\0'); appendPQExpBufferChar(&output, '\0');
}
if (pclose(fd) == -1) if (fd && pclose(fd) == -1)
{ {
psql_error("%s: %s\n", file, strerror(errno)); psql_error("%s: %s\n", file, strerror(errno));
error = true; error = true;
} }
}
if (!error) if (!error)
{ {
if (output.data[strlen(output.data) - 1] == '\n') if (output.data[strlen(output.data) - 1] == '\n')
output.data[strlen(output.data) - 1] = '\0'; output.data[strlen(output.data) - 1] = '\0';
}
if (!error)
return_val = output.data; return_val = output.data;
}
else else
{ {
return_val = xstrdup(""); return_val = xstrdup("");
termPQExpBuffer(&output); termPQExpBuffer(&output);
} }
options_string[pos + 1 + len] = '`'; options_string[pos + 1 + len] = '`';
*string = options_string + pos + len + 2; *string = options_string + pos + len + 2;
if (quote) if (quote)
@ -1084,13 +1048,6 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
return return_val; return return_val;
} }
/*
* end of line
*/
case 0:
*string = &options_string[pos];
return NULL;
/* /*
* Variable substitution * Variable substitution
*/ */
@ -1109,17 +1066,10 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
return_val = xstrdup(value); return_val = xstrdup(value);
options_string[pos + token_end + 1] = save_char; options_string[pos + token_end + 1] = save_char;
*string = &options_string[pos + token_end + 1]; *string = &options_string[pos + token_end + 1];
/* XXX should we set *quote to ':' here? */
return return_val; 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 * | could be the beginning of a pipe if so, take rest of line
* as command * as command
@ -1127,49 +1077,135 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
case '|': case '|':
if (type == OT_FILEPIPE) if (type == OT_FILEPIPE)
{ {
*string += strlen(options_string + pos); *string += strlen(*string);
return xstrdup(options_string + pos); return xstrdup(options_string + pos);
break;
} }
/* fallthrough for other option types */ /* 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: default:
{ {
size_t token_end; bool inquotes = false;
size_t token_len;
char *cp; char *cp;
token_end = strcspn(&options_string[pos], " \t\n\r"); /* Find end of option */
return_val = malloc(token_end + 1);
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) if (!return_val)
{ {
psql_error("out of memory\n"); psql_error("out of memory\n");
exit(EXIT_FAILURE); 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) if (semicolon)
{ {
int i; int i;
for (i = strlen(return_val) - 1; i && return_val[i] == ';'; i--); for (i = token_len - 1;
if (i < strlen(return_val) - 1) i >= 0 && return_val[i] == ';';
i--)
/* skip */;
if (i < 0)
{
/* nothing left after stripping the semicolon... */
free(return_val);
return NULL;
}
if (i < token_len - 1)
return_val[i + 1] = '\0'; return_val[i + 1] = '\0';
} }
if (type == OT_SQLID) /*
for (cp = return_val; *cp; cp += PQmblen(cp, pset.encoding)) * 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)) if (isupper((unsigned char) *cp))
*cp = tolower((unsigned char) *cp); *cp = tolower((unsigned char) *cp);
}
*string = &options_string[pos + token_end]; cp += PQmblen(cp, pset.encoding);
return return_val; }
}
} }
return return_val;
}
} }
} }
@ -1429,7 +1465,7 @@ test_superuser(const char *username)
return false; return false;
initPQExpBuffer(&buf); 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); res = PSQLexec(buf.data);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
/* /*
* psql - the PostgreSQL interactive terminal * 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 #ifndef DESCRIBE_H
#define DESCRIBE_H #define DESCRIBE_H
@ -11,36 +11,36 @@
#include "settings.h" #include "settings.h"
/* \da */ /* \da */
bool describeAggregates(const char *name); bool describeAggregates(const char *pattern, bool verbose);
/* \df */ /* \df */
bool describeFunctions(const char *name, bool verbose); bool describeFunctions(const char *pattern, bool verbose);
/* \dT */ /* \dT */
bool describeTypes(const char *name, bool verbose); bool describeTypes(const char *pattern, bool verbose);
/* \do */ /* \do */
bool describeOperators(const char *name); bool describeOperators(const char *pattern);
/* \du */ /* \du */
bool describeUsers(const char *name); bool describeUsers(const char *pattern);
/* \z (or \dp) */ /* \z (or \dp) */
bool permissionsList(const char *name); bool permissionsList(const char *pattern);
/* \dd */ /* \dd */
bool objectDescription(const char *object); bool objectDescription(const char *pattern);
/* \d foo */ /* \d foo */
bool describeTableDetails(const char *name, bool desc); bool describeTableDetails(const char *pattern, bool verbose);
/* \l */ /* \l */
bool listAllDbs(bool desc); bool listAllDbs(bool desc);
/* \dt, \di, \ds, \dS, etc. */ /* \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 */ /* \dD */
bool listDomains(const char *name); bool listDomains(const char *pattern);
#endif /* DESCRIBE_H */ #endif /* DESCRIBE_H */

View File

@ -1,9 +1,9 @@
/* /*
* psql - the PostgreSQL interactive terminal * 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 "postgres_fe.h"
#include "large_obj.h" #include "large_obj.h"
@ -209,9 +209,10 @@ do_lo_import(const char *filename_arg, const char *comment_arg)
return false; return false;
} }
sprintf(cmdbuf, sprintf(cmdbuf,
"INSERT INTO pg_description VALUES ('%u', " "INSERT INTO pg_catalog.pg_description VALUES ('%u', "
"(SELECT oid FROM pg_class WHERE relname = 'pg_largeobject')," "'pg_catalog.pg_largeobject'::regclass, "
" 0, '", loid); "0, '",
loid);
bufptr = cmdbuf + strlen(cmdbuf); bufptr = cmdbuf + strlen(cmdbuf);
for (i = 0; i < slen; i++) 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 */ /* XXX ought to replace this with some kind of COMMENT command */
if (pset.issuper) if (pset.issuper)
{ {
sprintf(buf, "DELETE FROM pg_description WHERE objoid = '%u' " sprintf(buf, "DELETE FROM pg_catalog.pg_description WHERE objoid = '%u' "
"AND classoid = (SELECT oid FROM pg_class WHERE relname = 'pg_largeobject')", "AND classoid = 'pg_catalog.pg_largeobject'::regclass",
loid); loid);
if (!(res = PSQLexec(buf))) if (!(res = PSQLexec(buf)))
{ {
@ -356,8 +357,8 @@ do_lo_list(void)
printQueryOpt myopt = pset.popt; printQueryOpt myopt = pset.popt;
snprintf(buf, sizeof(buf), snprintf(buf, sizeof(buf),
"SELECT loid as \"ID\", obj_description(loid, 'pg_largeobject') as \"%s\"\n" "SELECT loid as \"ID\", pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n"
"FROM (SELECT DISTINCT loid FROM pg_largeobject) x\n" "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n"
"ORDER BY \"ID\"", "ORDER BY \"ID\"",
gettext("Description")); gettext("Description"));

View File

@ -1,9 +1,9 @@
/* /*
* psql - the PostgreSQL interactive terminal * 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 /* 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. 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 typedef struct
{ {
@ -131,37 +140,29 @@ typedef struct
} pgsql_thing_t; } pgsql_thing_t;
pgsql_thing_t words_after_create[] = { pgsql_thing_t words_after_create[] = {
{"AGGREGATE", "SELECT distinct proname FROM pg_catalog.pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"}, {"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'"}, {"DATABASE", Query_for_list_of_databases},
{"FUNCTION", "SELECT distinct proname FROM pg_catalog.pg_proc WHERE substr(proname,1,%d)='%s'"}, {"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'"}, {"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. */ {"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'"}, {"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'"}, {"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'"}, {"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 ... */ {"TEMP", NULL}, /* for CREATE TEMP TABLE ... */
{"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substr(tgname,1,%d)='%s'"}, {"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'"}, {"TYPE", "SELECT typname FROM pg_catalog.pg_type WHERE substr(typname,1,%d)='%s'"},
{"UNIQUE", NULL}, /* for CREATE UNIQUE INDEX ... */ {"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'"}, {"VIEW", "SELECT viewname FROM pg_catalog.pg_views WHERE substr(viewname,1,%d)='%s'"},
{NULL, NULL} /* end of list */ {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 /* A couple of macros to ease typing. You can use these to complete the given
string with 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. 2) The items from a null-pointer-terminated list.
3) A string constant 3) A string constant
4) The list of attributes to the given table. 4) The list of attributes to the given table.
@ -375,7 +376,7 @@ psql_completion(char *text, int start, int end)
* queries. */ * queries. */
if (snprintf(query_buffer, BUF_SIZE, 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) prev2_wd) == -1)
ERROR_QUERY_TOO_LONG; ERROR_QUERY_TOO_LONG;
else else
@ -389,7 +390,8 @@ psql_completion(char *text, int start, int end)
{ {
char *list_COMMENT[] = char *list_COMMENT[] =
{"DATABASE", "INDEX", "RULE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", {"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); COMPLETE_WITH_LIST(list_COMMENT);
} }
@ -440,7 +442,7 @@ psql_completion(char *text, int start, int end)
/* Complete USING with an index method */ /* Complete USING with an index method */
else if (strcasecmp(prev_wd, "USING") == 0) 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); 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 */ /* Complete GRANT/REVOKE with a list of privileges */
else if (strcasecmp(prev_wd, "GRANT") == 0 || strcasecmp(prev_wd, "REVOKE") == 0) 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); COMPLETE_WITH_LIST(list_privileg);
} }
@ -563,14 +565,15 @@ psql_completion(char *text, int start, int end)
/* /*
* Complete GRANT/REVOKE <sth> ON with a list of tables, views, * Complete GRANT/REVOKE <sth> 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) && else if ((strcasecmp(prev3_wd, "GRANT") == 0 || strcasecmp(prev3_wd, "REVOKE") == 0) &&
strcasecmp(prev_wd, "ON") == 0) strcasecmp(prev_wd, "ON") == 0)
COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class " COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class "
"WHERE relkind in ('r','i','S','v') AND " "WHERE relkind in ('r','i','S','v') AND "
"substr(relname,1,%d)='%s' UNION " "substr(relname,1,%d)='%s' AND pg_catalog.pg_table_is_visible(oid)");
"SELECT nspname FROM pg_catalog.pg_namespace;");
/* Complete "GRANT * ON * " with "TO" */ /* Complete "GRANT * ON * " with "TO" */
else if (strcasecmp(prev4_wd, "GRANT") == 0 && strcasecmp(prev2_wd, "ON") == 0) else if (strcasecmp(prev4_wd, "GRANT") == 0 && strcasecmp(prev2_wd, "ON") == 0)
COMPLETE_WITH_CONST("TO"); COMPLETE_WITH_CONST("TO");
@ -745,7 +748,7 @@ psql_completion(char *text, int start, int end)
/* UNLISTEN */ /* UNLISTEN */
else if (strcasecmp(prev_wd, "UNLISTEN") == 0) 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 */ /* UPDATE */
/* If prev. word is UPDATE suggest a list of tables */ /* If prev. word is UPDATE suggest a list of tables */
@ -765,7 +768,7 @@ psql_completion(char *text, int start, int end)
/* VACUUM */ /* VACUUM */
else if (strcasecmp(prev_wd, "VACUUM") == 0) 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)) 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); COMPLETE_WITH_QUERY(Query_for_list_of_tables);