diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index a1384409cf..5a66973e32 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -1,5 +1,5 @@ @@ -249,16 +249,17 @@ GRANT { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] } - Use 's \z command + Use 's \dp command to obtain information about existing privileges, for example: -lusitania=> \z mytable - Access privileges for database "lusitania" - Table | Access privileges ----------+--------------------------------------- - mytable | {=r,miriam=arwdRxt,"group todos=arw"} +lusitania=> \dp mytable + Access privileges for database "lusitania" + Schema | Table | Access privileges +--------+---------+--------------------------------------- + public | mytable | {=r,miriam=arwdRxt,"group todos=arw"} +(1 row) - The entries shown by \z are interpreted thus: + The entries shown by \dp are interpreted thus: =xxxx -- privileges granted to PUBLIC uname=xxxx -- privileges granted to a user diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index b9f8554abf..48370d1e21 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1,5 +1,5 @@ @@ -538,7 +538,7 @@ testdb=> - To include whitespace into an argument you must quote it with a + To include whitespace into an argument you may quote it with a single quote. To include a single quote into such an argument, precede it by a backslash. Anything contained in single quotes is furthermore subject to C-like substitutions for @@ -551,25 +551,24 @@ testdb=> If an unquoted argument begins with a colon (:), - it is taken as a variable and the value of the variable is taken as - the argument instead. + it is taken as a psql variable and the value of the + variable is used as the argument instead. Arguments that are quoted in backticks (`) are taken as a command line that is passed to - the shell. The output of the command (with a trailing newline + the shell. The output of the command (with any trailing newline removed) is taken as the argument value. The above escape sequences also apply in backticks. - Some commands take the name of an SQL identifier + Some commands take an SQL identifier (such as a table name) as argument. These arguments follow the syntax rules of SQL regarding double quotes: an - identifier without double quotes is coerced to lower-case. For all - other commands double quotes are not special and will become part of - the argument. + identifier without double quotes is coerced to lower-case, while + whitespace within double quotes is included in the argument. @@ -732,18 +731,17 @@ testdb=> - \d relation + \d [ pattern ] - Shows all columns of relation (which could be a - table, view, index, or sequence), their types, and any special + For each relation (table, view, index, or sequence) matching the + pattern, show all + columns, their types, and any special attributes such as NOT NULL or defaults, if - any. If the relation is, in fact, a table, any defined indices, - primary keys, unique constraints and check constraints are also - listed. If the relation is a view, the view definition is also - shown. + any. Associated indexes, constraints, rules, and triggers are + also shown, as is the view definition if the relation is a view. + (Matching the pattern is defined below.) @@ -753,7 +751,8 @@ testdb=> - If \d is called without any arguments, it is + If \d is used without a + pattern argument, it is equivalent to \dtvs which will show a list of all tables, views, and sequences. This is purely a convenience measure. @@ -776,34 +775,35 @@ testdb=> - \dd [ object ] + \dd [ pattern ] - Shows the descriptions of object (which can be a regular - expression), or of all objects if no argument is given. + Shows the descriptions of objects matching the pattern, or of all visible objects if + no argument is given. But in either case, only objects that have + a description are listed. (Object covers aggregates, functions, operators, types, relations (tables, views, indexes, sequences, large objects), rules, and triggers.) For example: => \dd version - Object descriptions - Name | What | Description ----------+----------+--------------------------- - version | function | PostgreSQL version string + Object descriptions + Schema | Name | Object | Description +------------+---------+----------+--------------------------- + pg_catalog | version | function | PostgreSQL version string (1 row) - Descriptions for objects can be generated with the + Descriptions for objects can be created with the COMMENT ON SQL command. PostgreSQL stores the object - descriptions in the pg_description system table. + descriptions in the pg_description system table. @@ -816,7 +816,7 @@ testdb=> Lists all available domains (derived types). If pattern (a regular expression) + class="parameter">pattern is specified, only matching domains are shown. @@ -830,7 +830,7 @@ testdb=> Lists available functions, together with their argument and return types. If pattern (a regular expression) + class="parameter">pattern is specified, only matching functions are shown. If the form \df+ is used, additional information about each function, including language and description, is shown. @@ -844,18 +844,17 @@ testdb=> - This is not the actual command name: The letters i, s, t, v, S + This is not the actual command name: the letters i, s, t, v, S stand for index, sequence, table, view, and system table, - respectively. You can specify any or all of them in any order to - obtain a listing of them, together with who the owner is. + respectively. You can specify any or all of these letters, in any + order, to obtain a listing of all the matching objects. + If + is appended to the command name, each object is + listed with its associated description, if any. - If pattern is - specified, it is a regular expression that restricts the listing - to those objects whose name matches. If one appends a - + to the command name, each object is listed with - its associated description, if any. + If a pattern is + specified, only objects whose name matches the pattern are listed. @@ -873,12 +872,12 @@ testdb=> - \do [ name ] + \do [ pattern ] Lists available operators with their operand and return types. - If name is - specified, only operators with that name will be shown. + If a pattern is + specified, only operators whose name matches the pattern are listed. @@ -888,9 +887,17 @@ testdb=> \dp [ pattern ] - This is an alias for \z which was included - for its greater mnemonic value (display - permissions). + Produces a list of all available tables with their + associated access permissions. + If a pattern is + specified, only tables whose name matches the pattern are listed. + + + + The commands and + + are used to set access permissions. See + for more information. @@ -912,7 +919,7 @@ testdb=> \du [ pattern ] - Lists all configured users or only those that match pattern. @@ -1608,57 +1615,23 @@ lo_import 152801 \z [ pattern ] - Produces a list of all tables in the database with their - appropriate access permissions listed. If an argument is given - it is taken as a regular expression which limits the listing to - those tables which match it. - - - - -test=> \z -Access permissions for database "test" - Relation | Access permissions -----------+------------------------------------- - my_table | {"=r","joe=arwR", "group staff=ar"} -(1 row ) - - Read this as follows: - - - - - "=r": PUBLIC has read - (SELECT) permission on the table. - - - - - - "joe=arwR": User joe has - read, write (UPDATE, - DELETE), append - (INSERT) permissions, and permission to - create rules on the table. - - - - - - "group staff=ar": Group - staff has SELECT and - INSERT permission. - - - + Produces a list of all available tables with their + associated access permissions. + If a pattern is + specified, only tables whose name matches the pattern are listed. The commands and - are used to set access permissions. + are used to set access permissions. See + for more information. + + This is an alias for \dp (display + permissions). + @@ -1688,6 +1661,46 @@ Access permissions for database "test" + + + The various \d commands accept a pattern parameter to specify the + object name(s) to be displayed. Patterns are interpreted similarly + to SQL identifiers, in that unquoted letters are forced to lowercase, + while double quotes (") protect letters from case conversion + and allow incorporation of whitespace into the identifier. Within + double quotes, paired double quotes reduce to a single double quote in + the resulting name. For example, FOO"BAR"BAZ is interpreted + as fooBARbaz, and "A weird"" name" becomes + A weird" name. + + + + More interestingly, \d patterns allow the use of + * to mean any sequence of characters, and + ? to mean any single character. (This notation + is comparable to Unix shell filename patterns.) Advanced users can + also use regular-expression notations such as character classes, for + example [0-9] to match any digit. To make any of + these pattern-matching characters be interpreted literally, surround it + with double quotes. + + + + A pattern that contains an (unquoted) dot is interpreted as a schema + name pattern followed by an object name pattern. For example, + \dt foo*.bar* displays all tables in schemas whose name + starts with foo and whose table name + starts with bar. If no dot appears, then the pattern + matches only objects that are visible in the current schema search path. + + + + Whenever the pattern parameter + is omitted completely, the \d commands display all objects + that are visible in the current schema search path. To see all objects + in the database, use the pattern *.*. + @@ -2402,11 +2415,12 @@ $ ./configure --with-includes=/opt/gnu/include --with-libs=/opt/gnu/lib ... - In some earlier life psql allowed the - first argument to start directly after the (single-letter) - command. For compatibility this is still supported to some extent + In an earlier life psql allowed the + first argument of a single-letter backslash command to start + directly after the command, without intervening whitespace. For + compatibility this is still supported to some extent, but I am not going to explain the details here as this use is - discouraged. But if you get strange messages, keep this in mind. + discouraged. If you get strange messages, keep this in mind. For example testdb=> \foo @@ -2421,7 +2435,8 @@ Field separator is "oo", psql only works smoothly with servers of the same version. That does not mean other combinations will fail outright, but subtle and not-so-subtle problems might come - up. + up. Backslash commands are particularly likely to fail if the + server is of a different version. diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index f99f909f28..9be47a90c9 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright 2000 by PostgreSQL Global Development Group + * Copyright 2000-2002 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.74 2002/07/18 02:02:30 ishii Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.75 2002/08/10 03:56:23 tgl Exp $ */ #include "postgres_fe.h" #include "command.h" @@ -54,7 +54,7 @@ enum option_type OT_NORMAL, /* normal case */ OT_SQLID, /* treat as SQL identifier */ OT_SQLIDHACK, /* SQL identifier, but don't downcase */ - OT_FILEPIPE /* it's a file or pipe */ + OT_FILEPIPE /* it's a filename or pipe */ }; static char *scan_option(char **string, enum option_type type, @@ -328,10 +328,11 @@ exec_command(const char *cmd, /* \d* commands */ else if (cmd[0] == 'd') { - char *name; + char *pattern; bool show_verbose; - name = scan_option(&string, OT_SQLID, NULL, true); + /* We don't do SQLID reduction on the pattern yet */ + pattern = scan_option(&string, OT_NORMAL, NULL, true); show_verbose = strchr(cmd, '+') ? true : false; @@ -339,51 +340,53 @@ exec_command(const char *cmd, { case '\0': case '+': - if (name) - success = describeTableDetails(name, show_verbose); + if (pattern) + success = describeTableDetails(pattern, show_verbose); else /* standard listing of interesting things */ success = listTables("tvs", NULL, show_verbose); break; case 'a': - success = describeAggregates(name); + success = describeAggregates(pattern, show_verbose); break; case 'd': - success = objectDescription(name); + success = objectDescription(pattern); break; case 'f': - success = describeFunctions(name, show_verbose); + success = describeFunctions(pattern, show_verbose); break; case 'l': success = do_lo_list(); break; case 'o': - success = describeOperators(name); + success = describeOperators(pattern); break; case 'p': - success = permissionsList(name); + success = permissionsList(pattern); break; case 'T': - success = describeTypes(name, show_verbose); + success = describeTypes(pattern, show_verbose); break; case 't': case 'v': case 'i': case 's': case 'S': - success = listTables(&cmd[1], name, show_verbose); + success = listTables(&cmd[1], pattern, show_verbose); break; case 'u': - success = describeUsers(name); + success = describeUsers(pattern); break; case 'D': - success = listDomains(name); + success = listDomains(pattern); break; default: status = CMD_UNKNOWN; } - free(name); + + if (pattern) + free(pattern); } @@ -815,13 +818,14 @@ exec_command(const char *cmd, success = do_pset("expanded", NULL, &pset.popt, quiet); - /* \z -- list table rights (grant/revoke) */ + /* \z -- list table rights (equivalent to \dp) */ else if (strcmp(cmd, "z") == 0) { - char *opt = scan_option(&string, OT_SQLID, NULL, true); + char *pattern = scan_option(&string, OT_NORMAL, NULL, true); - success = permissionsList(opt); - free(opt); + success = permissionsList(pattern); + if (pattern) + free(pattern); } /* \! -- shell escape */ @@ -881,11 +885,27 @@ exec_command(const char *cmd, /* * scan_option() + * + * *string points to possible option string on entry; on exit, it's updated + * to point past the option string (if any). + * + * type tells what processing, if any, to perform on the option string; + * for example, if it's a SQL identifier, we want to downcase any unquoted + * letters. + * + * if quote is not NULL, *quote is set to 0 if no quoting was found, else + * the quote symbol. + * + * if semicolon is true, trailing semicolon(s) that would otherwise be taken + * as part of the option string will be stripped. + * + * Return value is NULL if no option found, else a malloc'd copy of the + * processed option value. */ static char * scan_option(char **string, enum option_type type, char *quote, bool semicolon) { - unsigned int pos = 0; + unsigned int pos; char *options_string; char *return_val; @@ -897,82 +917,27 @@ scan_option(char **string, enum option_type type, char *quote, bool semicolon) options_string = *string; /* skip leading whitespace */ - pos += strspn(options_string + pos, " \t\n\r"); + pos = strspn(options_string, " \t\n\r"); switch (options_string[pos]) { /* - * Double quoted string + * End of line: no option present */ - case '"': - { - unsigned int jj; - unsigned short int bslash_count = 0; + case '\0': + *string = &options_string[pos]; + return NULL; - /* 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 @@ -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 = strlen(return_val) - 1; i && return_val[i] == ';'; i--); - if (i < strlen(return_val) - 1) + 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; + } + + 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); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 8deffacb5f..59369972d4 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright 2000 by PostgreSQL Global Development Group + * Copyright 2000-2002 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/describe.c,v 1.58 2002/08/09 18:06:57 tgl Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/describe.c,v 1.59 2002/08/10 03:56:24 tgl Exp $ */ #include "postgres_fe.h" #include "describe.h" @@ -16,8 +16,34 @@ #include "print.h" #include "variables.h" +#include + #define _(x) gettext((x)) +static bool describeOneTableDetails(const char *schemaname, + const char *relationname, + const char *oid, + bool verbose); +static void processNamePattern(PQExpBuffer buf, const char *pattern, + bool have_where, bool force_escape, + const char *schemavar, const char *namevar, + const char *altnamevar, const char *visibilityrule); + + +static void * +xmalloc(size_t size) +{ + void *tmp; + + tmp = malloc(size); + if (!tmp) + { + psql_error("out of memory\n"); + exit(EXIT_FAILURE); + } + return tmp; +} + /*---------------- * Handlers for various slash commands displaying some sort of list @@ -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" + " 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)"); - ") AS tt,\n" - "pg_description d\n" - "WHERE tt.oid = d.objoid and tt.tableoid = d.classoid and d.objsubid = 0\n", + 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"); - _("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;"); + 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); + + if (!describeOneTableDetails(nspname, relname, oid, verbose)) + { + PQclear(res); + return false; + } + } + + PQclear(res); + return true; } - -bool -describeTableDetails(const char *name, bool desc) +/* + * 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 +} diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 4c95733912..41edcd98fb 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright 2000 by PostgreSQL Global Development Group + * Copyright 2000-2002 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/describe.h,v 1.16 2002/03/19 02:32:21 momjian Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/describe.h,v 1.17 2002/08/10 03:56:24 tgl Exp $ */ #ifndef DESCRIBE_H #define DESCRIBE_H @@ -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 */ diff --git a/src/bin/psql/large_obj.c b/src/bin/psql/large_obj.c index d134156477..0e90fceb6c 100644 --- a/src/bin/psql/large_obj.c +++ b/src/bin/psql/large_obj.c @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright 2000 by PostgreSQL Global Development Group + * Copyright 2000-2002 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/large_obj.c,v 1.19 2002/03/06 06:10:31 momjian Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/large_obj.c,v 1.20 2002/08/10 03:56:24 tgl Exp $ */ #include "postgres_fe.h" #include "large_obj.h" @@ -209,9 +209,10 @@ do_lo_import(const char *filename_arg, const char *comment_arg) return false; } sprintf(cmdbuf, - "INSERT INTO pg_description VALUES ('%u', " - "(SELECT oid FROM pg_class WHERE relname = 'pg_largeobject')," - " 0, '", loid); + "INSERT INTO pg_catalog.pg_description VALUES ('%u', " + "'pg_catalog.pg_largeobject'::regclass, " + "0, '", + loid); bufptr = cmdbuf + strlen(cmdbuf); for (i = 0; i < slen; i++) { @@ -310,8 +311,8 @@ do_lo_unlink(const char *loid_arg) /* XXX ought to replace this with some kind of COMMENT command */ if (pset.issuper) { - sprintf(buf, "DELETE FROM pg_description WHERE objoid = '%u' " - "AND classoid = (SELECT oid FROM pg_class WHERE relname = 'pg_largeobject')", + sprintf(buf, "DELETE FROM pg_catalog.pg_description WHERE objoid = '%u' " + "AND classoid = 'pg_catalog.pg_largeobject'::regclass", loid); if (!(res = PSQLexec(buf))) { @@ -356,8 +357,8 @@ do_lo_list(void) printQueryOpt myopt = pset.popt; snprintf(buf, sizeof(buf), - "SELECT loid as \"ID\", obj_description(loid, 'pg_largeobject') as \"%s\"\n" - "FROM (SELECT DISTINCT loid FROM pg_largeobject) x\n" + "SELECT loid as \"ID\", pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n" + "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n" "ORDER BY \"ID\"", gettext("Description")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 3c0da27b75..e7fd3df45f 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright 2000 by PostgreSQL Global Development Group + * Copyright 2000-2002 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.55 2002/08/04 05:01:57 momjian Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.56 2002/08/10 03:56:24 tgl Exp $ */ /*---------------------------------------------------------------------- @@ -118,11 +118,20 @@ initialize_readline(void) } +/* + * Queries to get lists of names of various kinds of things, possibly + * restricted to names matching a partially entered name. In these queries, + * the %s will be replaced by the text entered so far, the %d by its length. + */ + +#define Query_for_list_of_tables "SELECT relname FROM pg_catalog.pg_class WHERE (relkind='r' or relkind='v') and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid)" +#define Query_for_list_of_indexes "SELECT relname FROM pg_catalog.pg_class WHERE relkind='i' and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid)" +#define Query_for_list_of_databases "SELECT datname FROM pg_catalog.pg_database WHERE substr(datname,1,%d)='%s'" +#define Query_for_list_of_attributes "SELECT a.attname FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and not a.attisdropped and substr(a.attname,1,%d)='%s' and c.relname='%s' and pg_catalog.pg_table_is_visible(c.oid)" +#define Query_for_list_of_users "SELECT usename FROM pg_catalog.pg_user WHERE substr(usename,1,%d)='%s'" + /* This is a list of all "things" in Pgsql, which can show up after CREATE or DROP; and there is also a query to get a list of them. - The %s will be replaced by the text entered so far, the %d by its length. - If you change the order here or insert things, make sure to also adjust the - referencing macros below. */ typedef struct { @@ -131,37 +140,29 @@ typedef struct } pgsql_thing_t; pgsql_thing_t words_after_create[] = { - {"AGGREGATE", "SELECT distinct proname FROM pg_catalog.pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"}, - {"DATABASE", "SELECT datname FROM pg_catalog.pg_database WHERE substr(datname,1,%d)='%s'"}, - {"FUNCTION", "SELECT distinct proname FROM pg_catalog.pg_proc WHERE substr(proname,1,%d)='%s'"}, + {"AGGREGATE", "SELECT DISTINCT proname FROM pg_catalog.pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"}, + {"DATABASE", Query_for_list_of_databases}, + {"FUNCTION", "SELECT DISTINCT proname FROM pg_catalog.pg_proc WHERE substr(proname,1,%d)='%s'"}, {"GROUP", "SELECT groname FROM pg_catalog.pg_group WHERE substr(groname,1,%d)='%s'"}, - {"INDEX", "SELECT relname FROM pg_catalog.pg_class WHERE relkind='i' and substr(relname,1,%d)='%s'"}, + {"INDEX", Query_for_list_of_indexes}, {"OPERATOR", NULL}, /* Querying for this is probably not such a good idea. */ {"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE substr(rulename,1,%d)='%s'"}, {"SCHEMA", "SELECT nspname FROM pg_catalog.pg_namespace WHERE substr(nspname,1,%d)='%s'"}, {"SEQUENCE", "SELECT relname FROM pg_catalog.pg_class WHERE relkind='S' and substr(relname,1,%d)='%s'"}, - {"TABLE", "SELECT relname FROM pg_catalog.pg_class WHERE (relkind='r' or relkind='v') and substr(relname,1,%d)='%s'"}, + {"TABLE", Query_for_list_of_tables}, {"TEMP", NULL}, /* for CREATE TEMP TABLE ... */ {"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substr(tgname,1,%d)='%s'"}, {"TYPE", "SELECT typname FROM pg_catalog.pg_type WHERE substr(typname,1,%d)='%s'"}, {"UNIQUE", NULL}, /* for CREATE UNIQUE INDEX ... */ - {"USER", "SELECT usename FROM pg_catalog.pg_user WHERE substr(usename,1,%d)='%s'"}, + {"USER", Query_for_list_of_users}, {"VIEW", "SELECT viewname FROM pg_catalog.pg_views WHERE substr(viewname,1,%d)='%s'"}, {NULL, NULL} /* end of list */ }; -/* The query to get a list of tables and a list of indexes, which are used at - various places. */ -#define Query_for_list_of_tables words_after_create[9].query -#define Query_for_list_of_indexes words_after_create[4].query -#define Query_for_list_of_databases words_after_create[1].query -#define Query_for_list_of_attributes "SELECT a.attname FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and not a.attisdropped and substr(a.attname,1,%d)='%s' and c.relname='%s'" -#define Query_for_list_of_users words_after_create[14].query - /* A couple of macros to ease typing. You can use these to complete the given string with - 1) The results from a query you pass it. (Perhaps one of those right above?) + 1) The results from a query you pass it. (Perhaps one of those above?) 2) The items from a null-pointer-terminated list. 3) A string constant 4) The list of attributes to the given table. @@ -375,7 +376,7 @@ psql_completion(char *text, int start, int end) * queries. */ if (snprintf(query_buffer, BUF_SIZE, - "SELECT c1.relname FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s'", + "SELECT c1.relname FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s' and pg_catalog.pg_table_is_visible(c2.oid)", prev2_wd) == -1) ERROR_QUERY_TOO_LONG; else @@ -389,7 +390,8 @@ psql_completion(char *text, int start, int end) { char *list_COMMENT[] = {"DATABASE", "INDEX", "RULE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", - "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", "TRIGGER", NULL}; + "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", "TRIGGER", "CONSTRAINT", + "DOMAIN", NULL}; COMPLETE_WITH_LIST(list_COMMENT); } @@ -440,7 +442,7 @@ psql_completion(char *text, int start, int end) /* Complete USING with an index method */ else if (strcasecmp(prev_wd, "USING") == 0) { - char *index_mth[] = {"BTREE", "RTREE", "HASH", NULL}; + char *index_mth[] = {"BTREE", "RTREE", "HASH", "GIST", NULL}; COMPLETE_WITH_LIST(index_mth); } @@ -553,7 +555,7 @@ psql_completion(char *text, int start, int end) /* Complete GRANT/REVOKE with a list of privileges */ else if (strcasecmp(prev_wd, "GRANT") == 0 || strcasecmp(prev_wd, "REVOKE") == 0) { - char *list_privileg[] = {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "ALL", NULL}; + char *list_privileg[] = {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES", "TRIGGER", "CREATE", "TEMPORARY", "EXECUTE", "USAGE", "ALL", NULL}; COMPLETE_WITH_LIST(list_privileg); } @@ -563,14 +565,15 @@ psql_completion(char *text, int start, int end) /* * Complete GRANT/REVOKE ON with a list of tables, views, - * schema, sequences, and indexes + * sequences, and indexes + * + * XXX should also offer DATABASE, FUNCTION, LANGUAGE, SCHEMA here */ else if ((strcasecmp(prev3_wd, "GRANT") == 0 || strcasecmp(prev3_wd, "REVOKE") == 0) && strcasecmp(prev_wd, "ON") == 0) COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class " "WHERE relkind in ('r','i','S','v') AND " - "substr(relname,1,%d)='%s' UNION " - "SELECT nspname FROM pg_catalog.pg_namespace;"); + "substr(relname,1,%d)='%s' AND pg_catalog.pg_table_is_visible(oid)"); /* Complete "GRANT * ON * " with "TO" */ else if (strcasecmp(prev4_wd, "GRANT") == 0 && strcasecmp(prev2_wd, "ON") == 0) COMPLETE_WITH_CONST("TO"); @@ -745,7 +748,7 @@ psql_completion(char *text, int start, int end) /* UNLISTEN */ else if (strcasecmp(prev_wd, "UNLISTEN") == 0) - COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_listener WHERE substr(relname,1,%d)='%s' UNION SELECT '*'::text"); + COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_listener WHERE substr(relname,1,%d)='%s' UNION SELECT '*'::name"); /* UPDATE */ /* If prev. word is UPDATE suggest a list of tables */ @@ -765,7 +768,7 @@ psql_completion(char *text, int start, int end) /* VACUUM */ else if (strcasecmp(prev_wd, "VACUUM") == 0) - COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class WHERE relkind='r' and substr(relname,1,%d)='%s' UNION SELECT 'FULL'::text UNION SELECT 'ANALYZE'::text"); + COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class WHERE relkind='r' and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid) UNION SELECT 'FULL'::name UNION SELECT 'ANALYZE'::name"); else if (strcasecmp(prev2_wd, "VACUUM") == 0 && (strcasecmp(prev_wd, "FULL") == 0 || strcasecmp(prev_wd, "ANALYZE") == 0)) COMPLETE_WITH_QUERY(Query_for_list_of_tables);