postgresql/src/bin/psql/describe.c

1139 lines
28 KiB
C
Raw Normal View History

2000-01-19 00:30:24 +01:00
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright 2000 by PostgreSQL Global Development Group
2000-01-19 00:30:24 +01:00
*
* $Header: /cvsroot/pgsql/src/bin/psql/describe.c,v 1.54 2002/05/13 17:45:30 tgl Exp $
2000-01-19 00:30:24 +01:00
*/
#include "postgres_fe.h"
#include "describe.h"
2000-02-16 14:15:26 +01:00
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "common.h"
#include "settings.h"
#include "print.h"
#include "variables.h"
#define _(x) gettext((x))
/*----------------
* Handlers for various slash commands displaying some sort of list
* of things in the database.
*
* If you add something here, try to format the query to look nice in -E output.
*----------------
*/
/* \da
* takes an optional regexp to match specific aggregates by name
*/
bool
describeAggregates(const char *name)
{
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
initPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
/*
* There are two kinds of aggregates: ones that work on particular
* types and ones that work on all (denoted by input type = 0)
1999-11-05 00:14:30 +01:00
*/
printfPQExpBuffer(&buf,
"SELECT 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"
" END AS \"%s\",\n"
" obj_description(p.oid, 'pg_proc') as \"%s\"\n"
"FROM pg_proc p\n"
"WHERE p.proisagg\n",
_("Name"), _("(all types)"),
_("Data type"), _("Description"));
1999-11-05 00:14:30 +01:00
if (name)
appendPQExpBuffer(&buf, " AND p.proname ~ '^%s'\n", name);
1999-11-05 00:14:30 +01:00
appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
1999-11-05 00:14:30 +01:00
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("List of aggregate functions");
1999-11-05 00:14:30 +01:00
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/* \df
* Takes an optional regexp to narrow down the function name
*/
bool
describeFunctions(const char *name, bool verbose)
{
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
initPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
/*
* 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"
" p.proname as \"%s\",\n"
" oidvectortypes(p.proargtypes) as \"%s\"",
_("Result data type"), _("Name"),
_("Argument data types"));
if (verbose)
appendPQExpBuffer(&buf,
",\n u.usename as \"%s\",\n"
" l.lanname as \"%s\",\n"
" p.prosrc as \"%s\",\n"
" 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");
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");
1999-11-05 00:14:30 +01:00
if (name)
appendPQExpBuffer(&buf, " AND p.proname ~ '^%s'\n", name);
appendPQExpBuffer(&buf, "ORDER BY 2, 1, 3;");
1999-11-05 00:14:30 +01:00
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("List of functions");
1999-11-05 00:14:30 +01:00
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/*
* \dT
* describe types
*/
bool
describeTypes(const char *name, bool verbose)
{
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT format_type(t.oid, NULL) AS \"%s\",\n",
_("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"
" END AS \"%s\",\n",
_("Internal name"), _("Size"));
appendPQExpBuffer(&buf,
" obj_description(t.oid, 'pg_type') as \"%s\"\n",
_("Description"));
/*
* 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");
1999-11-05 00:14:30 +01:00
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);
1999-11-05 00:14:30 +01:00
appendPQExpBuffer(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("List of data types");
1999-11-05 00:14:30 +01:00
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/* \do
*/
bool
describeOperators(const char *name)
{
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
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"),
_("Result type"), _("Description"));
1999-11-05 00:14:30 +01:00
if (name)
appendPQExpBuffer(&buf, "WHERE o.oprname = '%s'\n", name);
1999-11-05 00:14:30 +01:00
appendPQExpBuffer(&buf, "ORDER BY 1, 2, 3, 4;");
1999-11-05 00:14:30 +01:00
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("List of operators");
1999-11-05 00:14:30 +01:00
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/*
* listAllDbs
*
* for \l, \list, and -l switch
*/
bool
listAllDbs(bool desc)
{
1999-11-05 00:14:30 +01:00
PGresult *res;
PQExpBufferData buf;
printQueryOpt myopt = pset.popt;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT d.datname as \"%s\",\n"
" u.usename as \"%s\"",
_("Name"), _("Owner"));
#ifdef MULTIBYTE
appendPQExpBuffer(&buf,
",\n pg_encoding_to_char(d.encoding) as \"%s\"",
_("Encoding"));
#endif
if (desc)
appendPQExpBuffer(&buf,
",\n 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"
"ORDER BY 1;");
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
1999-11-05 00:14:30 +01:00
myopt.nullPrint = NULL;
myopt.title = _("List of databases");
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/*
* List Tables Grant/Revoke Permissions
* \z (now also \dp -- perhaps more mnemonic)
*/
bool
permissionsList(const char *name)
{
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
initPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
/* Currently, we ignore indexes 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"));
1999-11-05 00:14:30 +01:00
if (name)
appendPQExpBuffer(&buf, " AND relname ~ '^%s'\n", name);
appendPQExpBuffer(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
1999-11-05 00:14:30 +01:00
if (!res)
{
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
return false;
}
1999-11-05 00:14:30 +01:00
myopt.nullPrint = NULL;
printfPQExpBuffer(&buf, _("Access privileges for database \"%s\""), PQdb(pset.db));
myopt.title = buf.data;
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/*
* Get object comments
*
* \dd [foo]
*
* Note: This only lists things that actually have a description. For complete
* lists of things, there are other \d? commands.
*/
bool
objectDescription(const char *object)
{
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT DISTINCT tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n"
"FROM (\n"
/* Aggregate descriptions */
" 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"
/* Function descriptions (except in/outs for datatypes) */
"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"
/* Operator descriptions (only if operator has its own comment) */
"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"
/* Type description */
"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"
/* Relation (tables, views, indexes, sequences) descriptions */
"UNION ALL\n"
" SELECT c.oid as oid, c.tableoid as tableoid,\n"
" CAST(c.relname AS 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"
/* Rule description (ignore rules for views) */
"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"
/* Trigger description */
"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")
);
1999-11-05 00:14:30 +01:00
if (object)
appendPQExpBuffer(&buf, " AND tt.name ~ '^%s'\n", object);
appendPQExpBuffer(&buf, "ORDER BY 1;");
1999-11-05 00:14:30 +01:00
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("Object descriptions");
1999-11-05 00:14:30 +01:00
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/*
* 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.
*
*/
1999-11-05 00:14:30 +01:00
static void *
xmalloc(size_t size)
{
1999-11-05 00:14:30 +01:00
void *tmp;
tmp = malloc(size);
if (!tmp)
{
psql_error("out of memory\n");
1999-11-05 00:14:30 +01:00
exit(EXIT_FAILURE);
}
return tmp;
}
bool
describeTableDetails(const char *name, bool desc)
{
PQExpBufferData buf;
PGresult *res = NULL;
printTableOpt myopt = pset.popt.topt;
1999-11-05 00:14:30 +01:00
int i;
const char *view_def = NULL;
const char *headers[5];
1999-11-05 00:14:30 +01:00
char **cells = NULL;
char **footers = NULL;
char **ptr;
PQExpBufferData title;
unsigned int cols = 0;
struct
{
bool hasindex;
char relkind;
int16 checks;
int16 triggers;
bool hasrules;
} tableinfo;
bool retval;
retval = false;
1999-11-05 00:14:30 +01:00
initPQExpBuffer(&buf);
initPQExpBuffer(&title);
1999-11-05 00:14:30 +01:00
/* Get general table info */
printfPQExpBuffer(&buf,
"SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n"
"FROM pg_class WHERE relname='%s'",
name);
res = PSQLexec(buf.data);
if (!res)
goto error_return;
1999-11-05 00:14:30 +01:00
/* Did we get anything? */
if (PQntuples(res) == 0)
{
if (!QUIET())
fprintf(stderr, _("Did not find any relation named \"%s\".\n"), name);
1999-11-05 00:14:30 +01:00
PQclear(res);
res = NULL;
goto error_return;
1999-11-05 00:14:30 +01:00
}
/* FIXME: check for null pointers here? */
tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 0), "t") == 0;
tableinfo.relkind = *(PQgetvalue(res, 0, 1));
tableinfo.checks = atoi(PQgetvalue(res, 0, 2));
tableinfo.triggers = atoi(PQgetvalue(res, 0, 3));
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
PQclear(res);
1999-11-05 00:14:30 +01:00
headers[0] = _("Column");
headers[1] = _("Type");
cols = 2;
1999-11-05 00:14:30 +01:00
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
{
cols++;
headers[cols - 1] = _("Modifiers");
}
1999-11-05 00:14:30 +01:00
if (desc)
1999-11-05 00:14:30 +01:00
{
cols++;
headers[cols - 1] = _("Description");
1999-11-05 00:14:30 +01:00
}
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, ");
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");
if (tableinfo.relkind == 'i')
appendPQExpBuffer(&buf, ", pg_index i");
appendPQExpBuffer(&buf, "\nWHERE c.relname = '%s'\n AND a.attnum > 0 AND a.attrelid = c.oid", name);
if (tableinfo.relkind == 'i')
appendPQExpBuffer(&buf, " AND a.attrelid = i.indexrelid");
appendPQExpBuffer(&buf, "\nORDER BY a.attnum");
res = PSQLexec(buf.data);
if (!res)
goto error_return;
/* Check if table is a view */
if (tableinfo.hasrules)
{
PGresult *result;
printfPQExpBuffer(&buf, "SELECT definition FROM pg_views WHERE viewname = '%s'", name);
result = PSQLexec(buf.data);
if (!result)
{
PQclear(res);
PQclear(result);
goto error_return;
}
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 */
1999-11-05 00:14:30 +01:00
for (i = 0; i < PQntuples(res); i++)
{
/* Name */
cells[i * cols + 0] = PQgetvalue(res, i, 0); /* don't free this
* afterwards */
1999-11-05 00:14:30 +01:00
/* Type */
cells[i * cols + 1] = PQgetvalue(res, i, 1); /* don't free this
2001-03-22 05:01:46 +01:00
* either */
/* Extra: not null and default */
/* (I'm cutting off the 'default' string at 128) */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
{
cells[i * cols + 2] = xmalloc(128 + 128);
cells[i * cols + 2][0] = '\0';
if (strcmp(PQgetvalue(res, i, 2), "t") == 0)
strcat(cells[i * cols + 2], "not null");
/* handle "default" here */
if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
{
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));
result = PSQLexec(buf.data);
if (cells[i * cols + 2][0])
strcat(cells[i * cols + 2], " ");
strcat(cells[i * cols + 2], "default ");
strcat(cells[i * cols + 2], result ? PQgetvalue(result, 0, 0) : "?");
PQclear(result);
}
}
1999-11-05 00:14:30 +01:00
/* Description */
if (desc)
cells[i * cols + cols - 1] = PQgetvalue(res, i, 5);
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
/* Make title */
switch (tableinfo.relkind)
{
case 'r':
printfPQExpBuffer(&title, _("Table \"%s\""), name);
break;
case 'v':
printfPQExpBuffer(&title, _("View \"%s\""), name);
break;
case 'S':
printfPQExpBuffer(&title, _("Sequence \"%s\""), name);
break;
case 'i':
printfPQExpBuffer(&title, _("Index \"%s\""), name);
break;
case 's':
printfPQExpBuffer(&title, _("Special relation \"%s\""), name);
break;
case 't':
printfPQExpBuffer(&title, _("TOAST table \"%s\""), name);
break;
default:
printfPQExpBuffer(&title, _("?%c? \"%s\""), tableinfo.relkind, name);
break;
}
1999-11-05 00:14:30 +01:00
/* Make footers */
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
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"
"AND i.indrelid = c2.oid",
name);
result = PSQLexec(buf.data);
if (!result)
goto error_return;
else if (PQntuples(result) != 1)
{
PQclear(result);
goto error_return;
}
else
{
2002-04-24 08:17:04 +02:00
PQExpBufferData tmpbuf;
char *indisunique = PQgetvalue(result, 0, 0);
char *indisprimary = PQgetvalue(result, 0, 1);
char *indamname = PQgetvalue(result, 0, 2);
char *indtable = PQgetvalue(result, 0, 3);
char *indpred = PQgetvalue(result, 0, 4);
2002-04-24 08:17:04 +02:00
initPQExpBuffer(&tmpbuf);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
else if (strcmp(indisunique, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("unique, "));
else
resetPQExpBuffer(&tmpbuf);
appendPQExpBuffer(&tmpbuf, "%s, ", indamname);
appendPQExpBuffer(&tmpbuf, _("for table \"%s\""), indtable);
if (strlen(indpred))
appendPQExpBuffer(&tmpbuf, ", predicate %s", indpred);
footers = xmalloc(2 * sizeof(*footers));
2002-04-24 08:17:04 +02:00
footers[0] = xstrdup(tmpbuf.data);
footers[1] = NULL;
2002-04-24 08:17:04 +02:00
termPQExpBuffer(&tmpbuf);
}
PQclear(result);
}
else if (view_def)
1999-11-05 00:14:30 +01:00
{
PGresult *result = NULL;
int rule_count = 0;
int count_footers = 0;
/* count rules */
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);
result = PSQLexec(buf.data);
if (!result)
goto error_return;
else
rule_count = PQntuples(result);
}
/* Footer information about a view */
footers = xmalloc((rule_count + 2) * sizeof(*footers));
footers[count_footers] = xmalloc(64 + strlen(view_def));
snprintf(footers[count_footers], 64 + strlen(view_def),
_("View definition: %s"), view_def);
count_footers++;
/* print rules */
for (i = 0; i < rule_count; i++)
{
char *s = _("Rules");
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result, i, 0));
else
printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result, i, 0));
if (i < rule_count - 1)
appendPQExpBuffer(&buf, ",");
footers[count_footers++] = xstrdup(buf.data);
}
PQclear(result);
footers[count_footers] = NULL;
1999-11-05 00:14:30 +01:00
}
else if (tableinfo.relkind == 'r')
1999-11-05 00:14:30 +01:00
{
/* Footer information about a table */
PGresult *result1 = NULL,
*result2 = NULL,
*result3 = NULL,
*result4 = NULL;
int index_count = 0,
constr_count = 0,
rule_count = 0,
trigger_count = 0;
int count_footers = 0;
/* count indexes */
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"
"ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname",
name);
result1 = PSQLexec(buf.data);
if (!result1)
goto error_return;
else
index_count = PQntuples(result1);
}
/* count table (and column) constraints */
if (tableinfo.checks)
{
printfPQExpBuffer(&buf,
"SELECT rcsrc, rcname\n"
"FROM pg_relcheck r, pg_class c\n"
"WHERE c.relname='%s' AND c.oid = r.rcrelid",
name);
result2 = PSQLexec(buf.data);
if (!result2)
goto error_return;
else
constr_count = PQntuples(result2);
}
/* count rules */
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",
name);
result3 = PSQLexec(buf.data);
if (!result3)
goto error_return;
else
rule_count = PQntuples(result3);
}
/* count triggers */
if (tableinfo.triggers)
{
printfPQExpBuffer(&buf,
"SELECT t.tgname\n"
"FROM pg_trigger t, pg_class c\n"
"WHERE c.relname='%s' AND c.oid = t.tgrelid",
name);
result4 = PSQLexec(buf.data);
if (!result4)
goto error_return;
else
trigger_count = PQntuples(result4);
}
footers = xmalloc((index_count + constr_count + rule_count + trigger_count + 1)
* sizeof(*footers));
/* print indexes */
for (i = 0; i < index_count; i++)
{
char *s = _("Indexes");
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result1, i, 0));
else
printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result1, i, 0));
/* Label as primary key or unique (but not both) */
appendPQExpBuffer(&buf,
strcmp(PQgetvalue(result1,i,1),"t") == 0
? _(" primary key") :
(strcmp(PQgetvalue(result1,i,2),"t") == 0
? _(" unique")
: ""));
/* Everything after "USING" is echoed verbatim */
appendPQExpBuffer(&buf, "%s", PQgetvalue(result1,i,3));
if (i < index_count - 1)
appendPQExpBuffer(&buf, ",");
footers[count_footers++] = xstrdup(buf.data);
}
/* print constraints */
for (i = 0; i < constr_count; i++)
{
char *s = _("Check constraints");
if (i == 0)
printfPQExpBuffer(&buf, _("%s: \"%s\" %s"),
s,
PQgetvalue(result2, i, 1),
PQgetvalue(result2, i, 0));
else
printfPQExpBuffer(&buf, _("%*s \"%s\" %s"),
(int) strlen(s), "",
PQgetvalue(result2, i, 1),
PQgetvalue(result2, i, 0));
footers[count_footers++] = xstrdup(buf.data);
}
/* print rules */
for (i = 0; i < rule_count; i++)
{
char *s = _("Rules");
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result3, i, 0));
else
printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result3, i, 0));
if (i < rule_count - 1)
appendPQExpBuffer(&buf, ",");
footers[count_footers++] = xstrdup(buf.data);
}
/* print triggers */
for (i = 0; i < trigger_count; i++)
{
char *s = _("Triggers");
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s", s, PQgetvalue(result4, i, 0));
else
printfPQExpBuffer(&buf, "%*s %s", (int) strlen(s), "", PQgetvalue(result4, i, 0));
if (i < trigger_count - 1)
appendPQExpBuffer(&buf, ",");
footers[count_footers++] = xstrdup(buf.data);
}
/* end of list marker */
footers[count_footers] = NULL;
PQclear(result1);
PQclear(result2);
PQclear(result3);
PQclear(result4);
}
printTable(title.data, headers,
(const char **) cells, (const char **) footers,
"llll", &myopt, pset.queryFout);
retval = true;
error_return:
1999-11-05 00:14:30 +01:00
/* clean up */
termPQExpBuffer(&buf);
termPQExpBuffer(&title);
if (cells)
1999-11-05 00:14:30 +01:00
{
for (i = 0; i < PQntuples(res); i++)
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
free(cells[i * cols + 2]);
free(cells);
1999-11-05 00:14:30 +01:00
}
if (footers)
{
for (ptr = footers; *ptr; ptr++)
free(*ptr);
free(footers);
}
if (res)
PQclear(res);
return retval;
}
/*
* \du [user]
*
* Describes users, possibly based on a simplistic prefix search on the
* argument.
*/
bool
describeUsers(const char *name)
{
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
initPQExpBuffer(&buf);
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"
" END AS \"%s\"\n"
"FROM 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);
appendPQExpBuffer(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("List of database users");
printQuery(res, &myopt, pset.queryFout);
PQclear(res);
return true;
}
/*
* listTables()
*
* handler for \d, \dt, etc.
*
* The infotype is an array of characters, specifying what info is desired:
* t - tables
* i - indexes
* v - views
* s - sequences
* S - systems 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)
{
1999-11-05 00:14:30 +01:00
bool showTables = strchr(infotype, 't') != NULL;
bool showIndexes = strchr(infotype, 'i') != NULL;
1999-11-05 00:14:30 +01:00
bool showViews = strchr(infotype, 'v') != NULL;
bool showSeq = strchr(infotype, 's') != NULL;
bool showSystem = strchr(infotype, 'S') != NULL;
PQExpBufferData buf;
1999-11-05 00:14:30 +01:00
PGresult *res;
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
if (showSystem && !(showSeq || showIndexes || showViews || showTables))
showTables = showViews = showSeq = true;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT 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\"",
_("Name"), _("table"), _("view"), _("index"), _("sequence"),
_("special"), _("Type"), _("Owner"));
if (desc)
appendPQExpBuffer(&buf,
",\n 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\n"
"WHERE c.relowner = u.usesysid\n"
"AND i.indrelid = c2.oid AND i.indexrelid = c.oid\n",
_("Table"));
else
appendPQExpBuffer(&buf,
"\nFROM pg_class c, pg_user u\n"
"WHERE c.relowner = u.usesysid\n");
appendPQExpBuffer(&buf, "AND c.relkind IN (");
if (showTables)
appendPQExpBuffer(&buf, "'r',");
1999-11-05 00:14:30 +01:00
if (showViews)
appendPQExpBuffer(&buf, "'v',");
if (showIndexes)
appendPQExpBuffer(&buf, "'i',");
if (showSeq)
appendPQExpBuffer(&buf, "'S',");
1999-11-05 00:14:30 +01:00
if (showSystem && showTables)
appendPQExpBuffer(&buf, "'s',");
appendPQExpBuffer(&buf, "''"); /* dummy */
appendPQExpBuffer(&buf, ")\n");
1999-11-05 00:14:30 +01:00
if (showSystem)
appendPQExpBuffer(&buf, " AND c.relname ~ '^pg_'\n");
else
appendPQExpBuffer(&buf, " AND c.relname !~ '^pg_'\n");
if (name)
appendPQExpBuffer(&buf, " AND c.relname ~ '^%s'\n", name);
appendPQExpBuffer(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
if (PQntuples(res) == 0 && !QUIET())
{
if (name)
fprintf(pset.queryFout, _("No matching relations found.\n"));
else
fprintf(pset.queryFout, _("No relations found.\n"));
}
1999-11-05 00:14:30 +01:00
else
{
myopt.nullPrint = NULL;
myopt.title = _("List of relations");
printQuery(res, &myopt, pset.queryFout);
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}
/*
* \dD [domain]
*
* Describes domains, possibly based on a simplistic prefix search on the
* argument.
*/
bool
listDomains(const char *name)
{
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
"SELECT t.typname as \"%s\",\n"
" 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"
"WHERE t.typtype = 'd'\n",
_("Name"),
_("Type"),
_("Modifier"));
if (name)
appendPQExpBuffer(&buf, "AND t.typname ~ '^%s'\n", name);
appendPQExpBuffer(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
if (!res)
return false;
myopt.nullPrint = NULL;
myopt.title = _("List of domains");
printQuery(res, &myopt, pset.queryFout);
PQclear(res);
return true;
}