Commit 039cb479 authored by Tom Lane's avatar Tom Lane

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.
parent 6ce4a4e3
<!-- <!--
$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 ] } ...@@ -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
......
<!-- <!--
$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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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=> ...@@ -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 ...@@ -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" ...@@ -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 ... ...@@ -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", ...@@ -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>
......
/* /*
* 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 ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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, ...@@ -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) ...@@ -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; return NULL;
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 = '"';
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 * 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) ...@@ -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) ...@@ -1040,10 +1005,10 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon)
error = true; error = true;
} }
initPQExpBuffer(&output);
if (!error) if (!error)
{ {
initPQExpBuffer(&output);
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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 = 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 < token_len - 1)
if (i < strlen(return_val) - 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,
if (isupper((unsigned char) *cp)) * then we strip out excess double quotes and downcase
*cp = tolower((unsigned char) *cp); * 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; return return_val;
} }
} }
} }
...@@ -1429,7 +1465,7 @@ test_superuser(const char *username) ...@@ -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);
......
/* /*
* 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.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 "postgres_fe.h"
#include "describe.h" #include "describe.h"
...@@ -16,8 +16,34 @@ ...@@ -16,8 +16,34 @@
#include "print.h" #include "print.h"
#include "variables.h" #include "variables.h"
#include <ctype.h>
#define _(x) gettext((x)) #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 * Handlers for various slash commands displaying some sort of list
...@@ -29,10 +55,10 @@ ...@@ -29,10 +55,10 @@
/* \da /* \da
* takes an optional regexp to match specific aggregates by name * Takes an optional regexp to select particular aggregates
*/ */
bool bool
describeAggregates(const char *name) describeAggregates(const char *pattern, bool verbose)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -45,21 +71,24 @@ describeAggregates(const char *name) ...@@ -45,21 +71,24 @@ describeAggregates(const char *name)
* types and ones that work on all (denoted by input type = 0) * types and ones that work on all (denoted by input type = 0)
*/ */
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT p.proname AS \"%s\",\n" "SELECT n.nspname as \"%s\",\n"
" p.proname AS \"%s\",\n"
" CASE p.proargtypes[0]\n" " CASE p.proargtypes[0]\n"
" WHEN 0 THEN CAST('%s' AS text)\n" " WHEN 0 THEN CAST('%s' AS pg_catalog.text)\n"
" ELSE format_type(p.proargtypes[0], NULL)\n" " ELSE pg_catalog.format_type(p.proargtypes[0], NULL)\n"
" END AS \"%s\",\n" " END AS \"%s\",\n"
" obj_description(p.oid, 'pg_proc') as \"%s\"\n" " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n"
"FROM pg_proc p\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", "WHERE p.proisagg\n",
_("Name"), _("(all types)"), _("Schema"), _("Name"), _("(all types)"),
_("Data type"), _("Description")); _("Data type"), _("Description"));
if (name) processNamePattern(&buf, pattern, true, false,
appendPQExpBuffer(&buf, " AND p.proname ~ '^%s'\n", name); "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); res = PSQLexec(buf.data);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
...@@ -77,10 +106,10 @@ describeAggregates(const char *name) ...@@ -77,10 +106,10 @@ describeAggregates(const char *name)
/* \df /* \df
* Takes an optional regexp to narrow down the function name * Takes an optional regexp to select particular functions
*/ */
bool bool
describeFunctions(const char *name, bool verbose) describeFunctions(const char *pattern, bool verbose)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -88,15 +117,12 @@ describeFunctions(const char *name, bool verbose) ...@@ -88,15 +117,12 @@ describeFunctions(const char *name, bool verbose)
initPQExpBuffer(&buf); initPQExpBuffer(&buf);
/*
* we skip in/out funcs by excluding functions that take some
* arguments, but have no types defined for those arguments
*/
printfPQExpBuffer(&buf, 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" " p.proname as \"%s\",\n"
" oidvectortypes(p.proargtypes) as \"%s\"", " pg_catalog.oidvectortypes(p.proargtypes) as \"%s\"",
_("Result data type"), _("Name"), _("Result data type"), _("Schema"), _("Name"),
_("Argument data types")); _("Argument data types"));
if (verbose) if (verbose)
...@@ -104,23 +130,35 @@ describeFunctions(const char *name, bool verbose) ...@@ -104,23 +130,35 @@ describeFunctions(const char *name, bool verbose)
",\n u.usename as \"%s\",\n" ",\n u.usename as \"%s\",\n"
" l.lanname as \"%s\",\n" " l.lanname as \"%s\",\n"
" p.prosrc 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"), _("Owner"), _("Language"),
_("Source code"), _("Description")); _("Source code"), _("Description"));
if (!verbose) if (!verbose)
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
"\nFROM pg_proc p\n" "\nFROM pg_catalog.pg_proc p"
"WHERE p.prorettype <> 0 AND (pronargs = 0 OR oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n"); "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n");
else else
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
"\nFROM pg_proc p, pg_language l, pg_user u\n" "\nFROM pg_catalog.pg_proc p"
"WHERE p.prolang = l.oid AND p.proowner = u.usesysid\n" "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace"
" AND p.prorettype <> 0 AND (pronargs = 0 OR oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n"); "\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); * we skip in/out funcs by excluding functions that take some
appendPQExpBuffer(&buf, "ORDER BY 2, 1, 3;"); * 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); res = PSQLexec(buf.data);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
...@@ -143,7 +181,7 @@ describeFunctions(const char *name, bool verbose) ...@@ -143,7 +181,7 @@ describeFunctions(const char *name, bool verbose)
* describe types * describe types
*/ */
bool bool
describeTypes(const char *name, bool verbose) describeTypes(const char *pattern, bool verbose)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -152,31 +190,37 @@ describeTypes(const char *name, bool verbose) ...@@ -152,31 +190,37 @@ describeTypes(const char *name, bool verbose)
initPQExpBuffer(&buf); initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT format_type(t.oid, NULL) AS \"%s\",\n", "SELECT n.nspname as \"%s\",\n"
_("Name")); " pg_catalog.format_type(t.oid, NULL) AS \"%s\",\n",
_("Schema"), _("Name"));
if (verbose) if (verbose)
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
" t.typname AS \"%s\",\n" " t.typname AS \"%s\",\n"
" CASE WHEN t.typlen = -1\n" " CASE WHEN t.typlen = -1\n"
" THEN CAST('var' AS text)\n" " THEN CAST('var' AS pg_catalog.text)\n"
" ELSE CAST(t.typlen AS text)\n" " ELSE CAST(t.typlen AS pg_catalog.text)\n"
" END AS \"%s\",\n", " END AS \"%s\",\n",
_("Internal name"), _("Size")); _("Internal name"), _("Size"));
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
" obj_description(t.oid, 'pg_type') as \"%s\"\n", " pg_catalog.obj_description(t.oid, 'pg_type') as \"%s\"\n",
_("Description")); _("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 * do not include array types (start with underscore), do not include
* user relations (typrelid!=0) * 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) /* Match name pattern against either internal or external name */
/* accept either internal or external type name */ processNamePattern(&buf, pattern, true, false,
appendPQExpBuffer(&buf, " AND (format_type(t.oid, NULL) ~ '^%s' OR t.typname ~ '^%s')\n", name, name); "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); res = PSQLexec(buf.data);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
...@@ -197,7 +241,7 @@ describeTypes(const char *name, bool verbose) ...@@ -197,7 +241,7 @@ describeTypes(const char *name, bool verbose)
/* \do /* \do
*/ */
bool bool
describeOperators(const char *name) describeOperators(const char *pattern)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -206,17 +250,22 @@ describeOperators(const char *name) ...@@ -206,17 +250,22 @@ describeOperators(const char *name)
initPQExpBuffer(&buf); initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT o.oprname AS \"%s\",\n" "SELECT n.nspname as \"%s\",\n"
" CASE WHEN o.oprkind='l' THEN NULL ELSE format_type(o.oprleft, NULL) END AS \"%s\",\n" " o.oprname AS \"%s\",\n"
" CASE WHEN o.oprkind='r' THEN NULL ELSE format_type(o.oprright, NULL) END AS \"%s\",\n" " CASE WHEN o.oprkind='l' THEN NULL ELSE pg_catalog.format_type(o.oprleft, NULL) END AS \"%s\",\n"
" format_type(o.oprresult, NULL) AS \"%s\",\n" " CASE WHEN o.oprkind='r' THEN NULL ELSE pg_catalog.format_type(o.oprright, NULL) END AS \"%s\",\n"
" coalesce(obj_description(o.oid, 'pg_operator')," " pg_catalog.format_type(o.oprresult, NULL) AS \"%s\",\n"
" obj_description(o.oprcode, 'pg_proc')) AS \"%s\"\n" " coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n"
"FROM pg_operator o\n", " pg_catalog.obj_description(o.oprcode, 'pg_proc')) AS \"%s\"\n"
_("Name"), _("Left arg type"), _("Right arg type"), "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")); _("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;"); appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3, 4;");
...@@ -255,15 +304,16 @@ listAllDbs(bool desc) ...@@ -255,15 +304,16 @@ listAllDbs(bool desc)
_("Name"), _("Owner")); _("Name"), _("Owner"));
#ifdef MULTIBYTE #ifdef MULTIBYTE
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
",\n pg_encoding_to_char(d.encoding) as \"%s\"", ",\n pg_catalog.pg_encoding_to_char(d.encoding) as \"%s\"",
_("Encoding")); _("Encoding"));
#endif #endif
if (desc) if (desc)
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
",\n obj_description(d.oid, 'pg_database') as \"%s\"", ",\n pg_catalog.obj_description(d.oid, 'pg_database') as \"%s\"",
_("Description")); _("Description"));
appendPQExpBuffer(&buf, 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;"); "ORDER BY 1;");
res = PSQLexec(buf.data); res = PSQLexec(buf.data);
...@@ -286,7 +336,7 @@ listAllDbs(bool desc) ...@@ -286,7 +336,7 @@ listAllDbs(bool desc)
* \z (now also \dp -- perhaps more mnemonic) * \z (now also \dp -- perhaps more mnemonic)
*/ */
bool bool
permissionsList(const char *name) permissionsList(const char *pattern)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -294,17 +344,29 @@ permissionsList(const char *name) ...@@ -294,17 +344,29 @@ permissionsList(const char *name)
initPQExpBuffer(&buf); 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, printfPQExpBuffer(&buf,
"SELECT relname as \"%s\",\n" "SELECT n.nspname as \"%s\",\n"
" relacl as \"%s\"\n" " c.relname as \"%s\",\n"
"FROM pg_class\n" " c.relacl as \"%s\"\n"
"WHERE relkind in ('r', 'v', 'S') AND\n" "FROM pg_catalog.pg_class c\n"
" relname NOT LIKE 'pg$_%%' ESCAPE '$'\n", " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
_("Table"), _("Access privileges")); "WHERE c.relkind IN ('r', 'v', 'S')\n",
if (name) _("Schema"), _("Table"), _("Access privileges"));
appendPQExpBuffer(&buf, " AND relname ~ '^%s'\n", name);
appendPQExpBuffer(&buf, "ORDER BY 1;"); /*
* 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); res = PSQLexec(buf.data);
if (!res) if (!res)
...@@ -335,7 +397,7 @@ permissionsList(const char *name) ...@@ -335,7 +397,7 @@ permissionsList(const char *name)
* lists of things, there are other \d? commands. * lists of things, there are other \d? commands.
*/ */
bool bool
objectDescription(const char *object) objectDescription(const char *pattern)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -343,71 +405,123 @@ objectDescription(const char *object) ...@@ -343,71 +405,123 @@ objectDescription(const char *object)
initPQExpBuffer(&buf); initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, appendPQExpBuffer(&buf,
"SELECT DISTINCT tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n" "SELECT DISTINCT tt.nspname AS \"%s\", tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n"
"FROM (\n" "FROM (\n",
_("Schema"), _("Name"), _("Object"), _("Description"));
/* Aggregate descriptions */ /* Aggregate descriptions */
appendPQExpBuffer(&buf,
" SELECT p.oid as oid, p.tableoid as tableoid,\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" " n.nspname as nspname,\n"
" FROM pg_proc p\n" " CAST(p.proname AS pg_catalog.text) as name,"
" WHERE p.proisagg\n" " 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) */ /* Function descriptions (except in/outs for datatypes) */
appendPQExpBuffer(&buf,
"UNION ALL\n" "UNION ALL\n"
" SELECT p.oid as oid, p.tableoid as tableoid,\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" " n.nspname as nspname,\n"
" FROM pg_proc p\n" " CAST(p.proname AS pg_catalog.text) as name,"
" WHERE (p.pronargs = 0 or oidvectortypes(p.proargtypes) <> '') AND NOT p.proisagg\n" " 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) */ /* Operator descriptions (only if operator has its own comment) */
appendPQExpBuffer(&buf,
"UNION ALL\n" "UNION ALL\n"
" SELECT o.oid as oid, o.tableoid as tableoid,\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" " n.nspname as nspname,\n"
" FROM pg_operator o\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 */ /* Type description */
appendPQExpBuffer(&buf,
"UNION ALL\n" "UNION ALL\n"
" SELECT t.oid as oid, t.tableoid as tableoid,\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" " n.nspname as nspname,\n"
" FROM pg_type t\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 */ /* Relation (tables, views, indexes, sequences) descriptions */
appendPQExpBuffer(&buf,
"UNION ALL\n" "UNION ALL\n"
" SELECT c.oid as oid, c.tableoid as tableoid,\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" " CAST(\n"
" CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' END" " 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" " AS pg_catalog.text) as object\n"
" FROM pg_class c\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) */ /* Rule description (ignore rules for views) */
appendPQExpBuffer(&buf,
"UNION ALL\n" "UNION ALL\n"
" SELECT r.oid as oid, r.tableoid as tableoid,\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" " n.nspname as nspname,\n"
" FROM pg_rewrite r\n" " CAST(r.rulename AS pg_catalog.text) as name,"
" WHERE r.rulename != '_RETURN'\n" " 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 */ /* Trigger description */
appendPQExpBuffer(&buf,
"UNION ALL\n" "UNION ALL\n"
" SELECT t.oid as oid, t.tableoid as tableoid,\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" " n.nspname as nspname,\n"
" FROM pg_trigger t\n" " CAST(t.tgname AS pg_catalog.text) as name,"
" CAST('%s' AS pg_catalog.text) as object\n"
") AS tt,\n" " FROM pg_catalog.pg_trigger t\n"
"pg_description d\n" " JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid\n"
"WHERE tt.oid = d.objoid and tt.tableoid = d.classoid and d.objsubid = 0\n", " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n",
_("trigger"));
_("Name"), _("Object"), _("Description"), /* XXX not sure what to do about visibility rule here? */
_("aggregate"), _("function"), _("operator"), processNamePattern(&buf, pattern, false, false,
_("data type"), _("table"), _("view"), "n.nspname", "t.tgname", NULL,
_("index"), _("sequence"), _("rule"), "pg_catalog.pg_table_is_visible(c.oid)");
_("trigger")
); appendPQExpBuffer(&buf,
") AS tt\n"
if (object) " JOIN pg_catalog.pg_description d ON (tt.oid = d.objoid and tt.tableoid = d.classoid and d.objsubid = 0)\n");
appendPQExpBuffer(&buf, " AND tt.name ~ '^%s'\n", object);
appendPQExpBuffer(&buf, "ORDER BY 1;"); appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3;");
res = PSQLexec(buf.data); res = PSQLexec(buf.data);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
...@@ -428,35 +542,84 @@ objectDescription(const char *object) ...@@ -428,35 +542,84 @@ objectDescription(const char *object)
/* /*
* describeTableDetails (for \d) * describeTableDetails (for \d)
* *
* Unfortunately, the information presented here is so complicated that it cannot * This routine finds the tables to be displayed, and calls
* be done in a single query. So we have to assemble the printed table by hand * describeOneTableDetails for each one.
* and pass it to the underlying printTable() function.
*
*/ */
bool
static void * describeTableDetails(const char *pattern, bool verbose)
xmalloc(size_t size)
{ {
void *tmp; PQExpBufferData buf;
PGresult *res;
int i;
tmp = malloc(size); initPQExpBuffer(&buf);
if (!tmp)
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"); if (!QUIET())
exit(EXIT_FAILURE); 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 if (!describeOneTableDetails(nspname, relname, oid, verbose))
describeTableDetails(const char *name, bool desc) {
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; PQExpBufferData buf;
PGresult *res = NULL; PGresult *res = NULL;
printTableOpt myopt = pset.popt.topt; printTableOpt myopt = pset.popt.topt;
int i; int i;
const char *view_def = NULL; char *view_def = NULL;
const char *headers[5]; const char *headers[5];
char **cells = NULL; char **cells = NULL;
char **footers = NULL; char **footers = NULL;
...@@ -481,8 +644,8 @@ describeTableDetails(const char *name, bool desc) ...@@ -481,8 +644,8 @@ describeTableDetails(const char *name, bool desc)
/* Get general table info */ /* Get general table info */
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n" "SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n"
"FROM pg_class WHERE relname='%s'", "FROM pg_catalog.pg_class WHERE oid = '%s'",
name); oid);
res = PSQLexec(buf.data); res = PSQLexec(buf.data);
if (!res) if (!res)
goto error_return; goto error_return;
...@@ -491,9 +654,8 @@ describeTableDetails(const char *name, bool desc) ...@@ -491,9 +654,8 @@ describeTableDetails(const char *name, bool desc)
if (PQntuples(res) == 0) if (PQntuples(res) == 0)
{ {
if (!QUIET()) if (!QUIET())
fprintf(stderr, _("Did not find any relation named \"%s\".\n"), name); fprintf(stderr, _("Did not find any relation with oid %s.\n"),
PQclear(res); oid);
res = NULL;
goto error_return; goto error_return;
} }
...@@ -505,7 +667,6 @@ describeTableDetails(const char *name, bool desc) ...@@ -505,7 +667,6 @@ describeTableDetails(const char *name, bool desc)
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 4), "t") == 0; tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
PQclear(res); PQclear(res);
headers[0] = _("Column"); headers[0] = _("Column");
headers[1] = _("Type"); headers[1] = _("Type");
cols = 2; cols = 2;
...@@ -516,7 +677,7 @@ describeTableDetails(const char *name, bool desc) ...@@ -516,7 +677,7 @@ describeTableDetails(const char *name, bool desc)
headers[cols - 1] = _("Modifiers"); headers[cols - 1] = _("Modifiers");
} }
if (desc) if (verbose)
{ {
cols++; cols++;
headers[cols - 1] = _("Description"); headers[cols - 1] = _("Description");
...@@ -524,19 +685,19 @@ describeTableDetails(const char *name, bool desc) ...@@ -524,19 +685,19 @@ describeTableDetails(const char *name, bool desc)
headers[cols] = NULL; headers[cols] = NULL;
/* Get column info (index requires additional checks) */ /* Get column info (index requires additional checks) */
if (tableinfo.relkind == 'i') 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 else
printfPQExpBuffer(&buf, "SELECT a.attname, "); printfPQExpBuffer(&buf, "SELECT a.attname,");
appendPQExpBuffer(&buf, "format_type(a.atttypid, a.atttypmod), a.attnotnull, a.atthasdef, a.attnum"); appendPQExpBuffer(&buf, "\n pg_catalog.format_type(a.atttypid, a.atttypmod),\n"
if (desc) " a.attnotnull, a.atthasdef, a.attnum");
appendPQExpBuffer(&buf, ", col_description(a.attrelid, a.attnum)"); if (verbose)
appendPQExpBuffer(&buf, "\nFROM pg_class c, pg_attribute a"); appendPQExpBuffer(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_attribute a");
if (tableinfo.relkind == 'i') if (tableinfo.relkind == 'i')
appendPQExpBuffer(&buf, ", pg_index i"); appendPQExpBuffer(&buf, ", pg_catalog.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, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
if (tableinfo.relkind == 'i') if (tableinfo.relkind == 'i')
appendPQExpBuffer(&buf, " AND a.attrelid = i.indexrelid"); appendPQExpBuffer(&buf, " AND a.attrelid = i.indexrelid");
appendPQExpBuffer(&buf, "\nORDER BY a.attnum"); appendPQExpBuffer(&buf, "\nORDER BY a.attnum");
...@@ -546,11 +707,11 @@ describeTableDetails(const char *name, bool desc) ...@@ -546,11 +707,11 @@ describeTableDetails(const char *name, bool desc)
goto error_return; goto error_return;
/* Check if table is a view */ /* Check if table is a view */
if (tableinfo.hasrules) if (tableinfo.relkind == 'v')
{ {
PGresult *result; 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); result = PSQLexec(buf.data);
if (!result) if (!result)
{ {
...@@ -561,10 +722,10 @@ describeTableDetails(const char *name, bool desc) ...@@ -561,10 +722,10 @@ describeTableDetails(const char *name, bool desc)
if (PQntuples(result) > 0) if (PQntuples(result) > 0)
view_def = xstrdup(PQgetvalue(result, 0, 0)); view_def = xstrdup(PQgetvalue(result, 0, 0));
PQclear(result); PQclear(result);
} }
/* Generate table cells to be printed */ /* Generate table cells to be printed */
cells = xmalloc((PQntuples(res) * cols + 1) * sizeof(*cells)); cells = xmalloc((PQntuples(res) * cols + 1) * sizeof(*cells));
cells[PQntuples(res) * cols] = NULL; /* end of list */ cells[PQntuples(res) * cols] = NULL; /* end of list */
...@@ -593,9 +754,9 @@ describeTableDetails(const char *name, bool desc) ...@@ -593,9 +754,9 @@ describeTableDetails(const char *name, bool desc)
PGresult *result; PGresult *result;
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c\n" "SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d\n"
"WHERE c.relname = '%s' AND c.oid = d.adrelid AND d.adnum = %s", "WHERE d.adrelid = '%s' AND d.adnum = %s",
name, PQgetvalue(res, i, 4)); oid, PQgetvalue(res, i, 4));
result = PSQLexec(buf.data); result = PSQLexec(buf.data);
...@@ -609,7 +770,7 @@ describeTableDetails(const char *name, bool desc) ...@@ -609,7 +770,7 @@ describeTableDetails(const char *name, bool desc)
} }
/* Description */ /* Description */
if (desc) if (verbose)
cells[i * cols + cols - 1] = PQgetvalue(res, i, 5); cells[i * cols + cols - 1] = PQgetvalue(res, i, 5);
} }
...@@ -617,25 +778,32 @@ describeTableDetails(const char *name, bool desc) ...@@ -617,25 +778,32 @@ describeTableDetails(const char *name, bool desc)
switch (tableinfo.relkind) switch (tableinfo.relkind)
{ {
case 'r': case 'r':
printfPQExpBuffer(&title, _("Table \"%s\""), name); printfPQExpBuffer(&title, _("Table \"%s.%s\""),
schemaname, relationname);
break; break;
case 'v': case 'v':
printfPQExpBuffer(&title, _("View \"%s\""), name); printfPQExpBuffer(&title, _("View \"%s.%s\""),
schemaname, relationname);
break; break;
case 'S': case 'S':
printfPQExpBuffer(&title, _("Sequence \"%s\""), name); printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
schemaname, relationname);
break; break;
case 'i': case 'i':
printfPQExpBuffer(&title, _("Index \"%s\""), name); printfPQExpBuffer(&title, _("Index \"%s.%s\""),
schemaname, relationname);
break; break;
case 's': case 's':
printfPQExpBuffer(&title, _("Special relation \"%s\""), name); printfPQExpBuffer(&title, _("Special relation \"%s.%s\""),
schemaname, relationname);
break; break;
case 't': case 't':
printfPQExpBuffer(&title, _("TOAST table \"%s\""), name); printfPQExpBuffer(&title, _("TOAST table \"%s.%s\""),
schemaname, relationname);
break; break;
default: default:
printfPQExpBuffer(&title, _("?%c? \"%s\""), tableinfo.relkind, name); printfPQExpBuffer(&title, _("?%c? \"%s.%s\""),
tableinfo.relkind, schemaname, relationname);
break; break;
} }
...@@ -646,11 +814,11 @@ describeTableDetails(const char *name, bool desc) ...@@ -646,11 +814,11 @@ describeTableDetails(const char *name, bool desc)
PGresult *result; PGresult *result;
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT i.indisunique, i.indisprimary, a.amname, c2.relname,\n" "SELECT i.indisunique, i.indisprimary, a.amname, c2.relname,\n"
"pg_get_expr(i.indpred,i.indrelid)\n" " pg_catalog.pg_get_expr(i.indpred, i.indrelid)\n"
"FROM pg_index i, pg_class c, pg_class c2, pg_am a\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.relname = '%s' AND c.relam = a.oid\n" "WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n"
"AND i.indrelid = c2.oid", "AND i.indrelid = c2.oid",
name); oid);
result = PSQLexec(buf.data); result = PSQLexec(buf.data);
if (!result) if (!result)
...@@ -679,7 +847,10 @@ describeTableDetails(const char *name, bool desc) ...@@ -679,7 +847,10 @@ describeTableDetails(const char *name, bool desc)
resetPQExpBuffer(&tmpbuf); resetPQExpBuffer(&tmpbuf);
appendPQExpBuffer(&tmpbuf, "%s, ", indamname); 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)) if (strlen(indpred))
appendPQExpBuffer(&tmpbuf, ", predicate %s", indpred); appendPQExpBuffer(&tmpbuf, ", predicate %s", indpred);
...@@ -697,15 +868,14 @@ describeTableDetails(const char *name, bool desc) ...@@ -697,15 +868,14 @@ describeTableDetails(const char *name, bool desc)
int rule_count = 0; int rule_count = 0;
int count_footers = 0; int count_footers = 0;
/* count rules */ /* count rules other than the view rule */
if (tableinfo.hasrules) if (tableinfo.hasrules)
{ {
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT r.rulename\n" "SELECT r.rulename\n"
"FROM pg_rewrite r, pg_class c\n" "FROM pg_catalog.pg_rewrite r\n"
"WHERE c.relname = '%s' AND c.oid = r.ev_class\n" "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN'",
"AND r.rulename != '_RETURN'", oid);
name);
result = PSQLexec(buf.data); result = PSQLexec(buf.data);
if (!result) if (!result)
goto error_return; goto error_return;
...@@ -756,13 +926,12 @@ describeTableDetails(const char *name, bool desc) ...@@ -756,13 +926,12 @@ describeTableDetails(const char *name, bool desc)
if (tableinfo.hasindex) if (tableinfo.hasindex)
{ {
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT c2.relname, i.indisprimary, i.indisunique,\n" "SELECT c2.relname, i.indisprimary, i.indisunique, "
"SUBSTR(pg_get_indexdef(i.indexrelid),\n" "pg_catalog.pg_get_indexdef(i.indexrelid)\n"
"POSITION('USING ' IN pg_get_indexdef(i.indexrelid))+5)\n" "FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n"
"FROM pg_class c, pg_class c2, pg_index i\n" "WHERE c.oid = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
"WHERE c.relname = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
"ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname", "ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname",
name); oid);
result1 = PSQLexec(buf.data); result1 = PSQLexec(buf.data);
if (!result1) if (!result1)
goto error_return; goto error_return;
...@@ -775,10 +944,9 @@ describeTableDetails(const char *name, bool desc) ...@@ -775,10 +944,9 @@ describeTableDetails(const char *name, bool desc)
{ {
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT consrc, conname\n" "SELECT consrc, conname\n"
"FROM pg_constraint r, pg_class c\n" "FROM pg_catalog.pg_constraint r\n"
"WHERE c.relname='%s' AND c.oid = r.conrelid\n" "WHERE r.conrelid = '%s' AND r.contype = 'c'",
"AND r.contype = 'c'", oid);
name);
result2 = PSQLexec(buf.data); result2 = PSQLexec(buf.data);
if (!result2) if (!result2)
goto error_return; goto error_return;
...@@ -791,9 +959,9 @@ describeTableDetails(const char *name, bool desc) ...@@ -791,9 +959,9 @@ describeTableDetails(const char *name, bool desc)
{ {
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT r.rulename\n" "SELECT r.rulename\n"
"FROM pg_rewrite r, pg_class c\n" "FROM pg_catalog.pg_rewrite r\n"
"WHERE c.relname='%s' AND c.oid = r.ev_class", "WHERE r.ev_class = '%s'",
name); oid);
result3 = PSQLexec(buf.data); result3 = PSQLexec(buf.data);
if (!result3) if (!result3)
goto error_return; goto error_return;
...@@ -806,9 +974,9 @@ describeTableDetails(const char *name, bool desc) ...@@ -806,9 +974,9 @@ describeTableDetails(const char *name, bool desc)
{ {
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT t.tgname\n" "SELECT t.tgname\n"
"FROM pg_trigger t, pg_class c\n" "FROM pg_catalog.pg_trigger t\n"
"WHERE c.relname='%s' AND c.oid = t.tgrelid", "WHERE t.tgrelid = '%s'",
name); oid);
result4 = PSQLexec(buf.data); result4 = PSQLexec(buf.data);
if (!result4) if (!result4)
goto error_return; goto error_return;
...@@ -823,11 +991,15 @@ describeTableDetails(const char *name, bool desc) ...@@ -823,11 +991,15 @@ describeTableDetails(const char *name, bool desc)
for (i = 0; i < index_count; i++) for (i = 0; i < index_count; i++)
{ {
char *s = _("Indexes"); char *s = _("Indexes");
const char *indexdef;
const char *usingpos;
if (i == 0) if (i == 0)
printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result1, i, 0)); printfPQExpBuffer(&buf, "%s: %s", s,
PQgetvalue(result1, i, 0));
else 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) */ /* Label as primary key or unique (but not both) */
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
...@@ -838,7 +1010,12 @@ describeTableDetails(const char *name, bool desc) ...@@ -838,7 +1010,12 @@ describeTableDetails(const char *name, bool desc)
: "")); : ""));
/* Everything after "USING" is echoed verbatim */ /* 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) if (i < index_count - 1)
appendPQExpBuffer(&buf, ","); appendPQExpBuffer(&buf, ",");
...@@ -931,6 +1108,9 @@ error_return: ...@@ -931,6 +1108,9 @@ error_return:
free(footers); free(footers);
} }
if (view_def)
free(view_def);
if (res) if (res)
PQclear(res); PQclear(res);
...@@ -939,13 +1119,12 @@ error_return: ...@@ -939,13 +1119,12 @@ error_return:
/* /*
* \du [user] * \du
* *
* Describes users, possibly based on a simplistic prefix search on the * Describes users. Any schema portion of the pattern is ignored.
* argument.
*/ */
bool bool
describeUsers(const char *name) describeUsers(const char *pattern)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -956,18 +1135,20 @@ describeUsers(const char *name) ...@@ -956,18 +1135,20 @@ describeUsers(const char *name)
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT u.usename AS \"%s\",\n" "SELECT u.usename AS \"%s\",\n"
" u.usesysid AS \"%s\",\n" " u.usesysid AS \"%s\",\n"
" CASE WHEN u.usesuper AND u.usecreatedb THEN CAST('%s' 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 text)\n" " WHEN u.usesuper THEN CAST('%s' AS pg_catalog.text)\n"
" WHEN u.usecreatedb THEN CAST('%s' AS text)\n" " WHEN u.usecreatedb THEN CAST('%s' AS pg_catalog.text)\n"
" ELSE CAST('' AS text)\n" " ELSE CAST('' AS pg_catalog.text)\n"
" END AS \"%s\"\n" " END AS \"%s\"\n"
"FROM pg_user u\n", "FROM pg_catalog.pg_user u\n",
_("User name"), _("User ID"), _("User name"), _("User ID"),
_("superuser, create database"), _("superuser, create database"),
_("superuser"), _("create database"), _("superuser"), _("create database"),
_("Attributes")); _("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;"); appendPQExpBuffer(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data); res = PSQLexec(buf.data);
...@@ -990,26 +1171,22 @@ describeUsers(const char *name) ...@@ -990,26 +1171,22 @@ describeUsers(const char *name)
* *
* handler for \d, \dt, etc. * 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 * t - tables
* i - indexes * i - indexes
* v - views * v - views
* s - sequences * s - sequences
* S - systems tables (~ '^pg_') * S - system tables (~ '^pg_')
* (any order of the above is fine) * (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 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 showTables = strchr(tabtypes, 't') != NULL;
bool showIndexes = strchr(infotype, 'i') != NULL; bool showIndexes = strchr(tabtypes, 'i') != NULL;
bool showViews = strchr(infotype, 'v') != NULL; bool showViews = strchr(tabtypes, 'v') != NULL;
bool showSeq = strchr(infotype, 's') != NULL; bool showSeq = strchr(tabtypes, 's') != NULL;
bool showSystem = strchr(infotype, 'S') != NULL; bool showSystem = strchr(tabtypes, 'S') != NULL;
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -1025,28 +1202,31 @@ listTables(const char *infotype, const char *name, bool desc) ...@@ -1025,28 +1202,31 @@ listTables(const char *infotype, const char *name, bool desc)
" c.relname as \"%s\",\n" " 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" " 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\"", " u.usename as \"%s\"",
_("Schema"), _("Name"), _("table"), _("view"), _("index"), _("sequence"), _("Schema"), _("Name"),
_("table"), _("view"), _("index"), _("sequence"),
_("special"), _("Type"), _("Owner")); _("special"), _("Type"), _("Owner"));
if (desc) if (verbose)
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
",\n obj_description(c.oid, 'pg_class') as \"%s\"", ",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
_("Description")); _("Description"));
if (showIndexes) if (showIndexes)
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
",\n c2.relname as \"%s\"" ",\n c2.relname as \"%s\""
"\nFROM pg_class c, pg_class c2, pg_index i, pg_user u, pg_namespace n\n" "\nFROM pg_catalog.pg_class c"
"WHERE c.relowner = u.usesysid\n" "\n JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid"
"AND c.relnamespace = n.oid\n" "\n JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid"
"AND i.indrelid = c2.oid AND i.indexrelid = c.oid\n", "\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")); _("Table"));
else else
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
"\nFROM pg_class c, pg_user u, pg_namespace n\n" "\nFROM pg_catalog.pg_class c"
"WHERE c.relowner = u.usesysid\n" "\n LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner"
"AND c.relnamespace = n.oid\n"); "\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) if (showTables)
appendPQExpBuffer(&buf, "'r',"); appendPQExpBuffer(&buf, "'r',");
if (showViews) if (showViews)
...@@ -1060,13 +1240,18 @@ listTables(const char *infotype, const char *name, bool desc) ...@@ -1060,13 +1240,18 @@ listTables(const char *infotype, const char *name, bool desc)
appendPQExpBuffer(&buf, "''"); /* dummy */ appendPQExpBuffer(&buf, "''"); /* dummy */
appendPQExpBuffer(&buf, ")\n"); 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) 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 else
appendPQExpBuffer(&buf, " AND n.nspname !~ '^pg_'\n"); processNamePattern(&buf, pattern, true, false,
"n.nspname", "c.relname", NULL,
if (name) "pg_catalog.pg_table_is_visible(c.oid) AND n.nspname <> 'pg_catalog' AND n.nspname <> 'pg_toast'");
appendPQExpBuffer(&buf, " AND c.relname ~ '^%s'\n", name);
appendPQExpBuffer(&buf, "ORDER BY 1,2;"); appendPQExpBuffer(&buf, "ORDER BY 1,2;");
...@@ -1077,7 +1262,7 @@ listTables(const char *infotype, const char *name, bool desc) ...@@ -1077,7 +1262,7 @@ listTables(const char *infotype, const char *name, bool desc)
if (PQntuples(res) == 0 && !QUIET()) if (PQntuples(res) == 0 && !QUIET())
{ {
if (name) if (pattern)
fprintf(pset.queryFout, _("No matching relations found.\n")); fprintf(pset.queryFout, _("No matching relations found.\n"));
else else
fprintf(pset.queryFout, _("No relations found.\n")); fprintf(pset.queryFout, _("No relations found.\n"));
...@@ -1096,13 +1281,12 @@ listTables(const char *infotype, const char *name, bool desc) ...@@ -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 * Describes domains.
* argument.
*/ */
bool bool
listDomains(const char *name) listDomains(const char *pattern)
{ {
PQExpBufferData buf; PQExpBufferData buf;
PGresult *res; PGresult *res;
...@@ -1111,21 +1295,27 @@ listDomains(const char *name) ...@@ -1111,21 +1295,27 @@ listDomains(const char *name)
initPQExpBuffer(&buf); initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT t.typname as \"%s\",\n" "SELECT n.nspname as \"%s\",\n"
" format_type(t.typbasetype, t.typtypmod) 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" " 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 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" " WHEN NOT t.typnotnull AND t.typdefault IS NOT NULL THEN 'default '||t.typdefault\n"
" ELSE ''\n" " ELSE ''\n"
" END as \"%s\"\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", "WHERE t.typtype = 'd'\n",
_("Schema"),
_("Name"), _("Name"),
_("Type"), _("Type"),
_("Modifier")); _("Modifier"));
if (name)
appendPQExpBuffer(&buf, "AND t.typname ~ '^%s'\n", name); processNamePattern(&buf, pattern, true, false,
appendPQExpBuffer(&buf, "ORDER BY 1;"); "n.nspname", "t.typname", NULL,
"pg_catalog.pg_type_is_visible(t.oid)");
appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data); res = PSQLexec(buf.data);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
...@@ -1140,3 +1330,184 @@ listDomains(const char *name) ...@@ -1140,3 +1330,184 @@ listDomains(const char *name)
PQclear(res); PQclear(res);
return true; 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
}
/* /*
* 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 @@ ...@@ -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 */
/* /*
* 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) ...@@ -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) ...@@ -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) ...@@ -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"));
......
/* /*
* 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) ...@@ -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 ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment