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