Thread: Showing index details with \d on psql

Showing index details with \d on psql

From
"Greg Sabino Mullane"
Date:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

While hacking around with psql, I came up with the idea of
identifying the columns in a table as to which index they
are in (and conversly, showing which columns an index
contains). I find this useful because a normal listing that
only tells you the name of the index is not very helpful
and usually needs a separate \d index_name entry. The only
concern is how to do it:
as a separate "index" column?
appending the name of the index to the Description column?
numbering the indexes and using the number to save space?
(The latter is used in the enclosed patch and
example below). Which columns the index affects follows the
name, and it also tells you if an index is a primary key. Here
is some sample output:

data=> \d mytable
Table "mytable"

                          Table "mytable"
 Attribute |           Type        |         Modifier

- -----------+-----------------------+-----------------------------
 post      | integer               | not null (index #1)
 thread    | smallint              | (index #2) (index #3)
 reply     | smallint              | not null
 subject   | character varying(60) | default 'foo' (index #2)
Indices:   1. mytable_foobar (1) (PRIMARY KEY)
           2. alphabet (4 2)
           3. badname (2)


The numbers at the end of the index names are ugly, but it does
show you instantly the composition and order of the index. I
think once you get used to it, it can be very valuable and
save on calls to \d index_name. My big concern is the size that
each "(index #x)" takes up, but having them separate does make
them stand out more, and in most cases, columns will not belong
to a lot of indices.

The attached (rough) patch is against 7.1.2. Feedback
welcome, as always. :)

Greg Sabino Mullane
greg@turnstep.com
PGP Key: 0x14964AC8 200110062052

-----BEGIN PGP SIGNATURE-----
Comment: http://www.turnstep.com/pgp.html

iQA/AwUBO7+rPrybkGcUlkrIEQKz7gCcCyPUDAGGwMbwPa09Rc2pqMbD0cYAn1qY
Yw6/kJdux/vwdN4waU5rdPmH
=/PN6
-----END PGP SIGNATURE-----
*** ./src/bin/psql/describe.c.orig    Wed Mar 21 23:00:19 2001
--- ./src/bin/psql/describe.c    Sat Oct  6 20:46:45 2001
***************
*** 748,754 ****
          /* count indices */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname\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 c2.relname",
--- 748,754 ----
          /* count indices */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname, i.indkey, i.indisprimary\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 c2.relname",
***************
*** 810,823 ****
          /* print indices */
          for (i = 0; i < index_count; i++)
          {
!             sprintf(buf, "%s %s",
!                     index_count == 1 ? "Index:" : (i == 0 ? "Indices:" : "        "),
!                     PQgetvalue(result1, i, 0)
                  );
-             if (i < index_count - 1)
-                 strcat(buf, ",");

              footers[count_footers++] = xstrdup(buf);
          }

          /* print contraints */
--- 810,845 ----
          /* print indices */
          for (i = 0; i < index_count; i++)
          {
!           char *indexname, *indexlist;
!           indexname = PQgetvalue(result1, i, 0);
!           indexlist = PQgetvalue(result1, i, 1);
!             sprintf(buf, "%s %3d. %s (%s)%s",
!                     index_count == 1 ? "Index:" : (i == 0 ? "Indices:" : "        "),i+1,
!                     indexname,indexlist,
!                     strcmp(PQgetvalue(result1, i, 2), "t") == 0 ? " (PRIMARY KEY)" : ""
                  );

              footers[count_footers++] = xstrdup(buf);
+
+             /* strtokx is overkill here */
+             int j;
+             char dummy[6]; /* Should be plenty */
+             char showindex[10+31];
+             int bar=0;
+             for (j=0; j<=strlen(indexlist); j++) {
+               if (indexlist[j]==0 || indexlist[j]==32) {
+                 bar = atoi(dummy);
+                 if (bar>0) /* pg_class has a -2! */
+                 {
+                     sprintf(showindex, "(index #%d)", i+1);
+                     if (cells[(bar-1) * cols + 2][0])
+                         strcat(cells[(bar-1) * cols + 2], " ");
+                     strcat(cells[(bar-1) * cols + 2], showindex);
+                 }
+                 dummy[0] = '\0';
+               }
+               else { strcat(dummy,&indexlist[j]); }
+             }
          }

          /* print contraints */

Re: Showing index details with \d on psql

From
Bruce Momjian
Date:
I like the idea of this but the format, as you say, could use
improvement.  Can I recommend having a "*" next to the columns involved
in the index and have the column names listed next to the index names?
That would look better and be clearer, I think. Perhaps:

                           Table "mytable"
  Attribute |           Type        |         Modifier

 - -----------+-----------------------+-----------------------------
  post      | integer               | not null *
  thread    | smallint              | *
  reply     | smallint              | not null *
  subject   | character varying(60) | default 'foo' *
 *Indices:  1. mytable_foobar (post) (PRIMARY KEY)
            2. alphabet (subject, thread)
            3. badname (thread)


---------------------------------------------------------------------------

>
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> While hacking around with psql, I came up with the idea of
> identifying the columns in a table as to which index they
> are in (and conversly, showing which columns an index
> contains). I find this useful because a normal listing that
> only tells you the name of the index is not very helpful
> and usually needs a separate \d index_name entry. The only
> concern is how to do it:
> as a separate "index" column?
> appending the name of the index to the Description column?
> numbering the indexes and using the number to save space?
> (The latter is used in the enclosed patch and
> example below). Which columns the index affects follows the
> name, and it also tells you if an index is a primary key. Here
> is some sample output:
>
> data=> \d mytable
> Table "mytable"
>
>                           Table "mytable"
>  Attribute |           Type        |         Modifier
>
> - -----------+-----------------------+-----------------------------
>  post      | integer               | not null (index #1)
>  thread    | smallint              | (index #2) (index #3)
>  reply     | smallint              | not null
>  subject   | character varying(60) | default 'foo' (index #2)
> Indices:   1. mytable_foobar (1) (PRIMARY KEY)
>            2. alphabet (4 2)
>            3. badname (2)
>
>
> The numbers at the end of the index names are ugly, but it does
> show you instantly the composition and order of the index. I
> think once you get used to it, it can be very valuable and
> save on calls to \d index_name. My big concern is the size that
> each "(index #x)" takes up, but having them separate does make
> them stand out more, and in most cases, columns will not belong
> to a lot of indices.
>
> The attached (rough) patch is against 7.1.2. Feedback
> welcome, as always. :)
>
> Greg Sabino Mullane
> greg@turnstep.com
> PGP Key: 0x14964AC8 200110062052
>
> -----BEGIN PGP SIGNATURE-----
> Comment: http://www.turnstep.com/pgp.html
>
> iQA/AwUBO7+rPrybkGcUlkrIEQKz7gCcCyPUDAGGwMbwPa09Rc2pqMbD0cYAn1qY
> Yw6/kJdux/vwdN4waU5rdPmH
> =/PN6
> -----END PGP SIGNATURE-----

> *** ./src/bin/psql/describe.c.orig    Wed Mar 21 23:00:19 2001
> --- ./src/bin/psql/describe.c    Sat Oct  6 20:46:45 2001
> ***************
> *** 748,754 ****
>           /* count indices */
>           if (!error && tableinfo.hasindex)
>           {
> !             sprintf(buf, "SELECT c2.relname\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 c2.relname",
> --- 748,754 ----
>           /* count indices */
>           if (!error && tableinfo.hasindex)
>           {
> !             sprintf(buf, "SELECT c2.relname, i.indkey, i.indisprimary\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 c2.relname",
> ***************
> *** 810,823 ****
>           /* print indices */
>           for (i = 0; i < index_count; i++)
>           {
> !             sprintf(buf, "%s %s",
> !                     index_count == 1 ? "Index:" : (i == 0 ? "Indices:" : "        "),
> !                     PQgetvalue(result1, i, 0)
>                   );
> -             if (i < index_count - 1)
> -                 strcat(buf, ",");
>
>               footers[count_footers++] = xstrdup(buf);
>           }
>
>           /* print contraints */
> --- 810,845 ----
>           /* print indices */
>           for (i = 0; i < index_count; i++)
>           {
> !           char *indexname, *indexlist;
> !           indexname = PQgetvalue(result1, i, 0);
> !           indexlist = PQgetvalue(result1, i, 1);
> !             sprintf(buf, "%s %3d. %s (%s)%s",
> !                     index_count == 1 ? "Index:" : (i == 0 ? "Indices:" : "        "),i+1,
> !                     indexname,indexlist,
> !                     strcmp(PQgetvalue(result1, i, 2), "t") == 0 ? " (PRIMARY KEY)" : ""
>                   );
>
>               footers[count_footers++] = xstrdup(buf);
> +
> +             /* strtokx is overkill here */
> +             int j;
> +             char dummy[6]; /* Should be plenty */
> +             char showindex[10+31];
> +             int bar=0;
> +             for (j=0; j<=strlen(indexlist); j++) {
> +               if (indexlist[j]==0 || indexlist[j]==32) {
> +                 bar = atoi(dummy);
> +                 if (bar>0) /* pg_class has a -2! */
> +                 {
> +                     sprintf(showindex, "(index #%d)", i+1);
> +                     if (cells[(bar-1) * cols + 2][0])
> +                         strcat(cells[(bar-1) * cols + 2], " ");
> +                     strcat(cells[(bar-1) * cols + 2], showindex);
> +                 }
> +                 dummy[0] = '\0';
> +               }
> +               else { strcat(dummy,&indexlist[j]); }
> +             }
>           }
>
>           /* print contraints */

>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: if posting/reading through Usenet, please send an appropriate
> subscribe-nomail command to majordomo@postgresql.org so that your
> message can get through to the mailing list cleanly

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Showing index details with \d on psql

From
"Greg Sabino Mullane"
Date:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

> I like the idea of this but the format, as you say, could use
> improvement.  Can I recommend having a "*" next to the columns
> involved in the index and have the column names listed next to
> the index names? That would look better and be clearer, I think.

Agreed - it's probably more intuitive for someone to ask
"is this column in an index?" and "what does this index cover?"
rather than "which index is this column in?" Attached patch is
against 7.1.2. As a side benfit, it does multiple stars for
columns that belong to multiple indices, which is a nice feature
to be able to spot right away.

Greg Sabino Mullane
greg@turnstep.com
PGP Key: 0x14964AC8 200110122041


-----BEGIN PGP SIGNATURE-----
Comment: http://www.turnstep.com/pgp.html

iQA/AwUBO8eqrbybkGcUlkrIEQI+BwCeMkkc2UfyJs+vaJNWS9BFSdZInvAAnjIF
vRuRPuHJWmQ8dqxorK72wLgM
=VdAI
-----END PGP SIGNATURE-----

*** ./src/bin/psql/describe.c.orig    Wed Mar 21 23:00:19 2001
--- ./src/bin/psql/describe.c    Fri Oct 12 22:27:25 2001
***************
*** 748,754 ****
          /* count indices */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname\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 c2.relname",
--- 748,754 ----
          /* count indices */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname, i.indkey, i.indisprimary, i.indisunique\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 c2.relname",
***************
*** 810,826 ****
          /* print indices */
          for (i = 0; i < index_count; i++)
          {
!             sprintf(buf, "%s %s",
!                     index_count == 1 ? "Index:" : (i == 0 ? "Indices:" : "        "),
!                     PQgetvalue(result1, i, 0)
                  );
!             if (i < index_count - 1)
!                 strcat(buf, ",");
!
              footers[count_footers++] = xstrdup(buf);
          }
!
!         /* print contraints */
          for (i = 0; i < constr_count; i++)
          {
              sprintf(buf, "%s %s",
--- 810,847 ----
          /* print indices */
          for (i = 0; i < index_count; i++)
          {
!             char *indexname, *indexlist;
!             indexname = PQgetvalue(result1, i, 0);
!             sprintf(buf, "%s %s%s%s",
!                     index_count == 1 ? "*Index:  " : (i == 0 ? "*Indices:" : "         "),
!                     indexname,
!                     strcmp(PQgetvalue(result1, i, 2), "t") == 0 ? " [PRIMARY KEY]" : "",
!                     strcmp(PQgetvalue(result1, i, 3), "t") == 0 ? " [UNIQUE]" : ""
                  );
!
!             /* strtokx is overkill here */
!             char indexchar[5]; /* Should be plenty */
!             int indexnumber=0;
!             indexlist = PQgetvalue(result1, i, 1);
!             int j,found;
!             for (j=0,found=0;j<=strlen(indexlist); j++) {
!                 if (indexlist[j] == 0 || indexlist[j] == 32) {
!                     indexnumber = atoi(indexchar);
!                     if (indexnumber>0) /* pg_class has a -2! */
!                     {
!                         strcat(cells[(indexnumber-1) * cols + 2], cells[(indexnumber-1) * cols +2][0] ? " *" : "*");
!                         strcat(buf, ++found==1 ? " (" : ", ");
!                         strcat(buf, cells[(indexnumber-1) * cols]);
!                     }
!                     indexchar[0] = '\0';
!                 }
!                 else { strcat(indexchar,&indexlist[j]); }
!             }
!             strcat(buf, ")");
              footers[count_footers++] = xstrdup(buf);
          }
!
!            /* print contraints */
          for (i = 0; i < constr_count; i++)
          {
              sprintf(buf, "%s %s",




Re: Showing index details with \d on psql

From
Bruce Momjian
Date:
>
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> > I like the idea of this but the format, as you say, could use
> > improvement.  Can I recommend having a "*" next to the columns
> > involved in the index and have the column names listed next to
> > the index names? That would look better and be clearer, I think.
>
> Agreed - it's probably more intuitive for someone to ask
> "is this column in an index?" and "what does this index cover?"
> rather than "which index is this column in?" Attached patch is
> against 7.1.2. As a side benfit, it does multiple stars for
> columns that belong to multiple indices, which is a nice feature
> to be able to spot right away.

Looks very good.  I have attached the describe.c file from current CVS.
Can I ask you to submit a patch against this file?  Seems there have
been some coding style improvements since 7.2 and I would like your
patch to match.  Notice the use of the _() macro.  Also, looks like we
have a PRIMARY KEY section in the code now.  Please figure out how your
patch mixes in with that.

Also, can you send over sample output from the new patch.  Thanks.

I am uncertain if I can get this into 7.2 because we are so close to
beta.  This is shaping up into a nice psql addition.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright 2000 by PostgreSQL Global Development Group
 *
 * $Header: /cvsroot/pgsql/src/bin/psql/describe.c,v 1.40 2001/10/06 14:41:17 momjian Exp $
 */
#include "postgres_fe.h"
#include "describe.h"

#include "libpq-fe.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.
 *----------------
 */

/* the maximal size of regular expression we'll accept here */
/* (it is safe to just change this here) */
#define REGEXP_CUTOFF (10 * NAMEDATALEN)


/* \da
 * takes an optional regexp to match specific aggregates by name
 */
bool
describeAggregates(const char *name)
{
    char        buf[384 + REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    /*
     * There are two kinds of aggregates: ones that work on particular
     * types ones that work on all
     */
    snprintf(buf, sizeof(buf),
             "SELECT a.aggname AS \"%s\",\n"
             "  CASE a.aggbasetype\n"
             "    WHEN 0 THEN CAST('%s' AS text)\n"
             "    ELSE format_type(a.aggbasetype, NULL)\n"
             "  END AS \"%s\",\n"
             "  obj_description(a.oid, 'pg_aggregate') as \"%s\"\n"
             "FROM pg_aggregate a\n",
             _("Name"), _("(all types)"),
             _("Data type"), _("Description") );

    if (name)
    {
        strcat(buf, "WHERE a.aggname ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "'\n");
    }

    strcat(buf, "ORDER BY 1, 2;");

    res = PSQLexec(buf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    myopt.title = _("List of aggregate functions");

    printQuery(res, &myopt, pset.queryFout);

    PQclear(res);
    return true;
}


/* \df
 * Takes an optional regexp to narrow down the function name
 */
bool
describeFunctions(const char *name, bool verbose)
{
    char        buf[384 + REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    /*
     * we skip in/out funcs by excluding functions that take some
     * arguments, but have no types defined for those arguments
     */
    snprintf(buf, sizeof(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)
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(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)
        strcat(buf,
               "\nFROM pg_proc p\n"
               "WHERE p.prorettype <> 0 AND (pronargs = 0 OR oidvectortypes(p.proargtypes) <> '')\n");
    else
        strcat(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) <> '')\n");

    if (name)
    {
        strcat(buf, "  AND p.proname ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "'\n");
    }
    strcat(buf, "ORDER BY 2, 1, 3;");

    res = PSQLexec(buf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    myopt.title = _("List of functions");

    printQuery(res, &myopt, pset.queryFout);

    PQclear(res);
    return true;
}



/*
 * \dT
 * describe types
 */
bool
describeTypes(const char *name, bool verbose)
{
    char        buf[384 + 2 * REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    snprintf(buf, sizeof(buf),
             "SELECT format_type(t.oid, NULL) AS \"%s\",\n",
             _("Name") );
    if (verbose)
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(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") );
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(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)
     */
    strcat(buf, "FROM pg_type t\nWHERE t.typrelid = 0 AND t.typname !~ '^_.*'\n");

    if (name)
    {
        /* accept either internal or external type name */
        strcat(buf, "  AND (format_type(t.oid, NULL) ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "' OR t.typname ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "')\n");
    }
    strcat(buf, "ORDER BY 1;");

    res = PSQLexec(buf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    myopt.title = _("List of data types");

    printQuery(res, &myopt, pset.queryFout);

    PQclear(res);
    return true;
}



/* \do
 */
bool
describeOperators(const char *name)
{
    char        buf[384 + REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    snprintf(buf, sizeof(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(p.prorettype, NULL) AS \"%s\",\n"
             "  obj_description(p.oid, 'pg_proc') as \"%s\"\n"
             "FROM pg_proc p, pg_operator o\n"
             "WHERE RegprocToOid(o.oprcode) = p.oid\n",
             _("Name"), _("Left arg type"), _("Right arg type"),
             _("Result type"), _("Description") );
    if (name)
    {
        strcat(buf, "  AND o.oprname = '");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "'\n");
    }

    strcat(buf, "ORDER BY 1, 2, 3, 4;");

    res = PSQLexec(buf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    myopt.title = _("List of operators");

    printQuery(res, &myopt, pset.queryFout);

    PQclear(res);
    return true;
}


/*
 * listAllDbs
 *
 * for \l, \list, and -l switch
 */
bool
listAllDbs(bool desc)
{
    PGresult   *res;
    char        buf[1024];
    printQueryOpt myopt = pset.popt;

    snprintf(buf, sizeof(buf),
             "SELECT d.datname as \"%s\",\n"
             "       u.usename as \"%s\"",
             _("Name"), _("Owner"));
#ifdef MULTIBYTE
    snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
             ",\n       pg_encoding_to_char(d.encoding) as \"%s\"",
             _("Encoding"));
#endif
    if (desc)
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                 ",\n       obj_description(d.oid, 'pg_database') as \"%s\"",
                 _("Description"));
    strcat(buf,
           "\nFROM pg_database d LEFT JOIN pg_user u ON d.datdba = u.usesysid\n"
           "ORDER BY 1;");

    res = PSQLexec(buf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    myopt.title = _("List of databases");

    printQuery(res, &myopt, pset.queryFout);

    PQclear(res);
    return true;
}


/*
 * List Tables Grant/Revoke Permissions
 * \z (now also \dp -- perhaps more mnemonic)
 */
bool
permissionsList(const char *name)
{
    char        buf[256 + REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    /* Currently, we ignore indexes since they have no meaningful rights */
    snprintf(buf, sizeof(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)
    {
        strcat(buf, "  AND relname ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "'\n");
    }
    strcat(buf, "ORDER BY 1;");

    res = PSQLexec(buf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    sprintf(buf, _("Access privileges for database \"%s\""), PQdb(pset.db));
    myopt.title = buf;

    printQuery(res, &myopt, pset.queryFout);

    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)
{
    char        descbuf[2048 + REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    snprintf(descbuf, sizeof(descbuf),
             "SELECT DISTINCT tt.name AS \"%s\", tt.object AS \"%s\", d.description AS \"%s\"\n"
             "FROM (\n"

             /* Aggregate descriptions */
             "  SELECT a.oid as oid, a.tableoid as tableoid,\n"
             "  CAST(a.aggname AS text) as name, CAST('%s' AS text) as object\n"
             "  FROM pg_aggregate a\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) <> ''\n"

             /* Operator descriptions (must get comment via associated function) */
             "UNION ALL\n"
             "  SELECT RegprocToOid(o.oprcode) as oid,\n"
             "  (SELECT oid FROM pg_class WHERE relname = 'pg_proc') 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 !~ '^_RET'\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")
        );

    if (object)
    {
        strcat(descbuf, "  AND tt.name ~ '^");
        strncat(descbuf, object, REGEXP_CUTOFF);
        strcat(descbuf, "'\n");
    }
    strcat(descbuf, "ORDER BY 1;");


    res = PSQLexec(descbuf);
    if (!res)
        return false;

    myopt.nullPrint = NULL;
    myopt.title = _("Object descriptions");

    printQuery(res, &myopt, pset.queryFout);

    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.
 *
 */

static void *
xmalloc(size_t size)
{
    void       *tmp;

    tmp = malloc(size);
    if (!tmp)
    {
        psql_error("out of memory\n");
        exit(EXIT_FAILURE);
    }
    return tmp;
}


bool
describeTableDetails(const char *name, bool desc)
{
    char        buf[512 + INDEX_MAX_KEYS * NAMEDATALEN];
    PGresult   *res = NULL;
    printTableOpt myopt = pset.popt.topt;
    int            i;
    const char *view_def = NULL;
    const char *headers[5];
    char      **cells = NULL;
    char       *title = NULL;
    char      **footers = NULL;
    char      **ptr;
    unsigned int cols;
    struct
    {
        bool        hasindex;
        char        relkind;
        int16        checks;
        int16        triggers;
        bool        hasrules;
    }            tableinfo;
    bool        error = false;

    /* truncate table name */
    if (strlen(name) > NAMEDATALEN)
    {
        char       *my_name = xmalloc(NAMEDATALEN + 1);

        strncpy(my_name, name, NAMEDATALEN);
        my_name[NAMEDATALEN] = '\0';
        name = my_name;
    }

    /* Get general table info */
    sprintf(buf,
     "SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n"
            "FROM pg_class WHERE relname='%s'",
            name);
    res = PSQLexec(buf);
    if (!res)
        return false;

    /* Did we get anything? */
    if (PQntuples(res) == 0)
    {
        if (!QUIET())
            fprintf(stderr, _("Did not find any relation named \"%s\".\n"), name);
        PQclear(res);
        return false;
    }

    /* 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);


    headers[0] = _("Column");
    headers[1] = _("Type");
    cols = 2;

    if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
    {
        cols++;
        headers[cols - 1] = _("Modifiers");
    }

    if (desc)
    {
        cols++;
        headers[cols - 1] = _("Description");
    }

    headers[cols] = NULL;


    /* Get column info */
    strcpy(buf, "SELECT a.attname, format_type(a.atttypid, a.atttypmod), a.attnotnull, a.atthasdef, a.attnum");
    if (desc)
        strcat(buf, ", col_description(a.attrelid, a.attnum)");
    strcat(buf, "\nFROM pg_class c, pg_attribute a\n"
           "WHERE c.relname = '");
    strncat(buf, name, NAMEDATALEN);
    strcat(buf, "'\n  AND a.attnum > 0 AND a.attrelid = c.oid\n"
           "ORDER BY a.attnum");

    res = PSQLexec(buf);
    if (!res)
        return false;

    /* Check if table is a view */
    if (tableinfo.hasrules)
    {
        PGresult   *result;

        sprintf(buf, "SELECT definition FROM pg_views WHERE viewname = '%s'", name);
        result = PSQLexec(buf);
        if (!result)
        {
            PQclear(res);
            PQclear(result);
            return false;
        }

        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 */

    for (i = 0; i < PQntuples(res); i++)
    {
        /* Name */
        cells[i * cols + 0] = PQgetvalue(res, i, 0);    /* don't free this
                                                         * afterwards */
        /* Type */
        cells[i * cols + 1] = PQgetvalue(res, i, 1);    /* don't free this
                                                         * 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;

                sprintf(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);
                if (!result)
                    error = true;
                else
                {
                    if (cells[i * cols + 2][0])
                        strcat(cells[i * cols + 2], " ");
                    strcat(cells[i * cols + 2], "default ");
                    strcat(cells[i * cols + 2], PQgetvalue(result, 0, 0));
                    PQclear(result);
                }
            }
        }

        if (error)
            break;

        /* Description */
        if (desc)
            cells[i * cols + cols - 1] = PQgetvalue(res, i, 5);
    }

    /* Make title */
    title = xmalloc(32 + NAMEDATALEN);
    switch (tableinfo.relkind)
    {
        case 'r':
            snprintf(title, 32 + NAMEDATALEN, _("Table \"%s\""), name);
            break;
        case 'v':
            snprintf(title, 32 + NAMEDATALEN, _("View \"%s\""), name);
            break;
        case 'S':
            snprintf(title, 32 + NAMEDATALEN, _("Sequence \"%s\""), name);
            break;
        case 'i':
            snprintf(title, 32 + NAMEDATALEN, _("Index \"%s\""), name);
            break;
        case 's':
            snprintf(title, 32 + NAMEDATALEN, _("Special relation \"%s\""), name);
            break;
        case 't':
            snprintf(title, 32 + NAMEDATALEN, _("TOAST table \"%s\""), name);
            break;
        default:
            snprintf(title, 32 + NAMEDATALEN, _("?%c? \"%s\""), tableinfo.relkind, name);
            break;
    }

    /* Make footers */
    if (tableinfo.relkind == 'i')
    {
        /* Footer information about an index */
        PGresult   *result;

        sprintf(buf, "SELECT i.indisunique, i.indisprimary, a.amname,\n"
                "       pg_get_expr(i.indpred, i.indrelid) as indpred\n"
                "FROM pg_index i, pg_class c, pg_am a\n"
                "WHERE i.indexrelid = c.oid AND c.relname = '%s' AND c.relam = a.oid",
                name);

        result = PSQLexec(buf);
        if (!result || PQntuples(result) != 1)
            error = true;
        else
        {
            char   *indisunique = PQgetvalue(result, 0, 0);
            char   *indisprimary = PQgetvalue(result, 0, 1);
            char   *indamname = PQgetvalue(result, 0, 2);
            char   *indpred = PQgetvalue(result, 0, 3);

            footers = xmalloc(3 * sizeof(*footers));
            /* XXX This construction is poorly internationalized. */
            footers[0] = xmalloc(NAMEDATALEN + 128);
            snprintf(footers[0], NAMEDATALEN + 128, "%s%s",
                     strcmp(indisunique, "t") == 0 ? _("unique ") : "",
                     indamname);
            if (strcmp(indisprimary, "t") == 0)
                snprintf(footers[0] + strlen(footers[0]),
                         NAMEDATALEN + 128 - strlen(footers[0]),
                         _(" (primary key)"));
            if (strlen(indpred) > 0)
            {
                footers[1] = xmalloc(64 + strlen(indpred));
                snprintf(footers[1], 64 + strlen(indpred),
                         _("Index predicate: %s"), indpred);
                footers[2] = NULL;
            }
            else
                footers[1] = NULL;
        }

        PQclear(result);
    }
    else if (view_def)
    {
        /* Footer information about a view */
        footers = xmalloc(2 * sizeof(*footers));
        footers[0] = xmalloc(64 + strlen(view_def));
        snprintf(footers[0], 64 + strlen(view_def),
                 _("View definition: %s"), view_def);
        footers[1] = NULL;
    }
    else if (tableinfo.relkind == 'r')
    {
        /* Footer information about a table */
        PGresult        *result1 = NULL,
                        *result2 = NULL,
                        *result3 = NULL,
                        *result4 = NULL,
                        *result5 = NULL,
                        *result6 = NULL;
        int            index_count = 0,
                        primary_count = 0,
                        unique_count = 0,
                        constr_count = 0,
                        rule_count = 0,
                        trigger_count = 0;
        int            count_footers = 0;

        /* count indexes */
        if (!error && tableinfo.hasindex)
        {
            sprintf(buf, "SELECT c2.relname\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"
               "AND NOT i.indisunique ORDER BY c2.relname",
                    name);
            result1 = PSQLexec(buf);
            if (!result1)
                error = true;
            else
                index_count = PQntuples(result1);
        }

        /* count primary keys */
        if (!error && tableinfo.hasindex)
        {
            sprintf(buf, "SELECT c2.relname\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"
               "AND i.indisprimary AND i.indisunique ORDER BY c2.relname",
                    name);
            result5 = PSQLexec(buf);
            if (!result5)
                error = true;
            else
                primary_count = PQntuples(result5);
        }

        /* count unique constraints */
        if (!error && tableinfo.hasindex)
        {
            sprintf(buf, "SELECT c2.relname\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"
               "AND NOT i.indisprimary AND i.indisunique ORDER BY c2.relname",
                    name);
            result6 = PSQLexec(buf);
            if (!result6)
                error = true;
            else
                unique_count = PQntuples(result6);
        }

        /* count table (and column) constraints */
        if (!error && tableinfo.checks)
        {
            sprintf(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);
            if (!result2)
                error = true;
            else
                constr_count = PQntuples(result2);
        }

        /* count rules */
        if (!error && tableinfo.hasrules)
        {
            sprintf(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);
            if (!result3)
                error = true;
            else
                rule_count = PQntuples(result3);
        }

        /* count triggers */
        if (!error && tableinfo.triggers)
        {
            sprintf(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);
            if (!result4)
                error = true;
            else
                trigger_count = PQntuples(result4);
        }

        footers = xmalloc((index_count + primary_count + unique_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)
                snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result1, i, 0));
            else
                snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result1, i, 0));
            if (i < index_count - 1)
                strcat(buf, ",");

            footers[count_footers++] = xstrdup(buf);
        }

        /* print primary keys */
        for (i = 0; i < primary_count; i++)
        {
            char   *s = _("Primary key");

            if (i == 0)
                snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result5, i, 0));
            else
                snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result5, i, 0));
            if (i < primary_count - 1)
                strcat(buf, ",");

            footers[count_footers++] = xstrdup(buf);
        }

        /* print unique constraints */
        for (i = 0; i < unique_count; i++)
        {
            char   *s = _("Unique keys");

            if (i == 0)
                snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result6, i, 0));
            else
                snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result6, i, 0));
            if (i < unique_count - 1)
                strcat(buf, ",");

            footers[count_footers++] = xstrdup(buf);
        }

        /* print constraints */
        for (i = 0; i < constr_count; i++)
        {
            char   *s = _("Check constraints");

            if (i == 0)
                snprintf(buf, sizeof(buf), _("%s: \"%s\" %s"), s,
                         PQgetvalue(result2, i, 1), PQgetvalue(result2, i, 0));
            else
                snprintf(buf, sizeof(buf), _("%*s  \"%s\" %s"), (int)strlen(s), "",
                         PQgetvalue(result2, i, 1), PQgetvalue(result2, i, 0));
            footers[count_footers++] = xstrdup(buf);
        }

        /* print rules */
        for (i = 0; i < rule_count; i++)
        {
            char   *s = _("Rules");

            if (i == 0)
                snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result3, i, 0));
            else
                snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result3, i, 0));
            if (i < rule_count - 1)
                strcat(buf, ",");

            footers[count_footers++] = xstrdup(buf);
        }

        /* print triggers */
        for (i = 0; i < trigger_count; i++)
        {
            char   *s = _("Triggers");

            if (i == 0)
                snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result4, i, 0));
            else
                snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result4, i, 0));
            if (i < trigger_count - 1)
                strcat(buf, ",");

            footers[count_footers++] = xstrdup(buf);
        }

        /* end of list marker */
        footers[count_footers] = NULL;

        PQclear(result1);
        PQclear(result2);
        PQclear(result3);
        PQclear(result4);
        PQclear(result5);
        PQclear(result6);
    }

    if (!error)
        printTable(title, headers,
                   (const char **) cells, (const char **) footers,
                   "llll", &myopt, pset.queryFout);

    /* clean up */
    free(title);

    for (i = 0; i < PQntuples(res); i++)
    {
        if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
            free(cells[i * cols + 2]);
    }
    free(cells);

    for (ptr = footers; footers && *ptr; ptr++)
        free(*ptr);
    free(footers);

    PQclear(res);

    return !error;
}


/*
 * \du [user]
 *
 * Describes users, possibly based on a simplistic prefix search on the
 * argument.
 */

bool
describeUsers (const char *name)
{
    char        buf[384 + REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    snprintf(buf, sizeof(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)
    {
        strcat(buf, "WHERE u.usename ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "'\n");
    }
    strcat(buf, "ORDER BY 1;");

    res = PSQLexec(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)
{
    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;

    char        buf[3072 + 8 * REGEXP_CUTOFF];
    PGresult   *res;
    printQueryOpt myopt = pset.popt;

    if (showSystem && !(showSeq || showIndexes || showViews || showTables))
        showTables = showViews = showSeq = true;


    buf[0] = '\0';

    snprintf(buf, sizeof(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)
        snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                 ",\n  obj_description(c.oid, 'pg_class') as \"%s\"",
                 _("Description"));
    strcat(buf,
           "\nFROM pg_class c LEFT JOIN pg_user u ON c.relowner = u.usesysid\n"
           "WHERE c.relkind IN (");
    if (showTables)
        strcat(buf, "'r',");
    if (showViews)
        strcat(buf, "'v',");
    if (showIndexes)
        strcat(buf, "'i',");
    if (showSeq)
        strcat(buf, "'S',");
    if (showSystem && showTables)
        strcat(buf, "'s',");
    strcat(buf, "''");                /* dummy */
    strcat(buf, ")\n");

    if (showSystem)
        strcat(buf, "  AND c.relname ~ '^pg_'\n");
    else
        strcat(buf, "  AND c.relname !~ '^pg_'\n");

    if (name)
    {
        strcat(buf, "  AND c.relname ~ '^");
        strncat(buf, name, REGEXP_CUTOFF);
        strcat(buf, "'\n");
    }

    strcat(buf, "ORDER BY 1;");

    res = PSQLexec(buf);
    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"));
    }
    else
    {
        myopt.nullPrint = NULL;
        myopt.title = _("List of relations");

        printQuery(res, &myopt, pset.queryFout);
    }

    PQclear(res);
    return true;
}

Re: Showing index details with \d on psql

From
"Greg Sabino Mullane"
Date:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

..
> Notice the use of the _() macro.  Also, looks like we have a
> PRIMARY KEY section in the code now.

New patch applied. I think I was able to wrap my brain around
the new _() macro and use of snprintf this early in the weekend.
:) I removed the separate counts of primary key and unique keys,
and simply added labels to the index list, since that's what
they are anyway.

One thing for someone to look into (I think it's related to the
backend/utils code, which I am not familiar with) is that any
describing of indexes in psql generates an error about
not finding the function 'pg_get_expr'

Sample output from this patch:

                          Table "b1"
 Column |           Type           |        Modifiers
- --------+--------------------------+--------------------------
 post   | integer                  | *
 thread | smallint                 | not null *
 reply  | smallint                 | not null *
 stat   | character varying(1)     | not null default 'N' * *
 uid    | integer                  | not null
 ctime  | timestamp with time zone | default 'now'
 atime  | timestamp with time zone |
Indexes  abc (post, reply, stat)
         def (stat)
         foo_pkey [PRIMARY_KEY] (thread)


The code also makes allowances for the table pg_class, just in
case anyone does a \d on it (one of its indexes refers to column
"-2")

Greg Sabino Mullane
greg@turnstep.com
PGP Key: 0x14964AC8 200110131131

-----BEGIN PGP SIGNATURE-----
Comment: http://www.turnstep.com/pgp.html

iQA/AwUBO8hhcrybkGcUlkrIEQJtNACggHKbcz6+MXGkR34T/Woe8JyyP8EAoLty
ZxuvaVQQ3H34HYfNWsrDUOwr
=f2Oy
-----END PGP SIGNATURE-----
*** ./src/bin/psql/describe.c.orig    Sat Oct 13 10:13:10 2001
--- ./src/bin/psql/describe.c    Sat Oct 13 11:17:27 2001
***************
*** 707,718 ****
          PGresult        *result1 = NULL,
                          *result2 = NULL,
                          *result3 = NULL,
!                         *result4 = NULL,
!                         *result5 = NULL,
!                         *result6 = NULL;
          int            index_count = 0,
-                         primary_count = 0,
-                         unique_count = 0,
                          constr_count = 0,
                          rule_count = 0,
                          trigger_count = 0;
--- 707,714 ----
          PGresult        *result1 = NULL,
                          *result2 = NULL,
                          *result3 = NULL,
!                         *result4 = NULL;
          int            index_count = 0,
                          constr_count = 0,
                          rule_count = 0,
                          trigger_count = 0;
***************
*** 721,730 ****
          /* count indexes */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname\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"
!                "AND NOT i.indisunique ORDER BY c2.relname",
                      name);
              result1 = PSQLexec(buf);
              if (!result1)
--- 717,726 ----
          /* count indexes */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname, i.indkey, i.indisprimary, i.indisunique\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 c2.relname",
                      name);
              result1 = PSQLexec(buf);
              if (!result1)
***************
*** 733,774 ****
                  index_count = PQntuples(result1);
          }

-         /* count primary keys */
-         if (!error && tableinfo.hasindex)
-         {
-             sprintf(buf, "SELECT c2.relname\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"
-                "AND i.indisprimary AND i.indisunique ORDER BY c2.relname",
-                     name);
-             result5 = PSQLexec(buf);
-             if (!result5)
-                 error = true;
-             else
-                 primary_count = PQntuples(result5);
-         }
-
-         /* count unique constraints */
-         if (!error && tableinfo.hasindex)
-         {
-             sprintf(buf, "SELECT c2.relname\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"
-                "AND NOT i.indisprimary AND i.indisunique ORDER BY c2.relname",
-                     name);
-             result6 = PSQLexec(buf);
-             if (!result6)
-                 error = true;
-             else
-                 unique_count = PQntuples(result6);
-         }
-
          /* count table (and column) constraints */
          if (!error && tableinfo.checks)
          {
              sprintf(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);
              if (!result2)
--- 729,741 ----
                  index_count = PQntuples(result1);
          }

          /* count table (and column) constraints */
          if (!error && tableinfo.checks)
          {
              sprintf(buf, "SELECT rcsrc, rcname\n"
                      "FROM pg_relcheck r, pg_class c\n"
!                     "WHERE c.relname='%s' AND c.oid = r.rcrelid\n"
!                     "ORDER BY rcname",
                      name);
              result2 = PSQLexec(buf);
              if (!result2)
***************
*** 783,789 ****
              sprintf(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);
              if (!result3)
--- 750,757 ----
              sprintf(buf,
                      "SELECT r.rulename\n"
                      "FROM pg_rewrite r, pg_class c\n"
!                     "WHERE c.relname='%s' AND c.oid = r.ev_class\n"
!                     "ORDER BY r.rulename",
                      name);
              result3 = PSQLexec(buf);
              if (!result3)
***************
*** 798,804 ****
              sprintf(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);
              if (!result4)
--- 766,773 ----
              sprintf(buf,
                      "SELECT t.tgname\n"
                      "FROM pg_trigger t, pg_class c\n"
!                     "WHERE c.relname='%s' AND c.oid = t.tgrelid\n"
!                     "ORDER BY t.tgname",
                      name);
              result4 = PSQLexec(buf);
              if (!result4)
***************
*** 807,858 ****
                  trigger_count = PQntuples(result4);
          }

!         footers = xmalloc((index_count + primary_count + unique_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)
!                 snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result1, i, 0));
!             else
!                 snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result1, i, 0));
!             if (i < index_count - 1)
!                 strcat(buf, ",");
!
!             footers[count_footers++] = xstrdup(buf);
!         }
!
!         /* print primary keys */
!         for (i = 0; i < primary_count; i++)
!         {
!             char   *s = _("Primary key");
!
!             if (i == 0)
!                 snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result5, i, 0));
!             else
!                 snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result5, i, 0));
!             if (i < primary_count - 1)
!                 strcat(buf, ",");
!
!             footers[count_footers++] = xstrdup(buf);
!         }
!
!         /* print unique constraints */
!         for (i = 0; i < unique_count; i++)
!         {
!             char   *s = _("Unique keys");
!
!             if (i == 0)
!                 snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result6, i, 0));
!             else
!                 snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result6, i, 0));
!             if (i < unique_count - 1)
!                 strcat(buf, ",");
!
              footers[count_footers++] = xstrdup(buf);
          }

--- 776,816 ----
                  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");

!             snprintf(buf, sizeof(buf), "%*s  %s%s%s",
!                      (int)strlen(s),
!                      i == 0 ? _("Indexes") : "",
!                      PQgetvalue(result1, i, 0),
!                      strcmp(PQgetvalue(result1, i, 2), "t") == 0 ? _(" [PRIMARY KEY]") : "",
!                      strcmp(PQgetvalue(result1, i, 3), "t") == 0 ? _(" [UNIQUE]") : "");
!
!             char indexchar[5]; /* Should be plenty */
!             int indexnumber=0;
!             char * indexlist = PQgetvalue(result1, i, 1);
!             int j,found;
!             for (j=0,found=0;j<=strlen(indexlist); j++) {
!               if (indexlist[j] == 0 || indexlist[j] == 32) {
!                 indexnumber = atoi(indexchar);
!                 if (indexnumber>0) /* pg_class has a -2! */
!                   {
!                   strcat(cells[(indexnumber-1) * cols + 2],
!                          cells[(indexnumber-1) * cols +2][0] ? " *" : "*");
!                   strcat(buf, ++found==1 ? " (" : ", ");
!                   strcat(buf, cells[(indexnumber-1) * cols]);
!                   }
!                 indexchar[0] = '\0';
!                 }
!               else { strcat(indexchar,&indexlist[j]); }
!             }
!             if (found) /* must cover for pg_class again */
!               strcat(buf, ")");
              footers[count_footers++] = xstrdup(buf);
          }

***************
*** 907,914 ****
          PQclear(result2);
          PQclear(result3);
          PQclear(result4);
-         PQclear(result5);
-         PQclear(result6);
      }

      if (!error)
--- 865,870 ----

Re: Showing index details with \d on psql

From
Peter Eisentraut
Date:
Greg Sabino Mullane writes:

>                           Table "b1"
>  Column |           Type           |        Modifiers
> - --------+--------------------------+--------------------------
>  post   | integer                  | *
>  thread | smallint                 | not null *
>  reply  | smallint                 | not null *
>  stat   | character varying(1)     | not null default 'N' * *
>  uid    | integer                  | not null
>  ctime  | timestamp with time zone | default 'now'
>  atime  | timestamp with time zone |
> Indexes  abc (post, reply, stat)
>          def (stat)
>          foo_pkey [PRIMARY_KEY] (thread)

Please use "(primary key)" and add a colon after "Indexes".

I don't like the '*' things.  They look ugly and convey little real
information.

--
Peter Eisentraut   peter_e@gmx.net   http://funkturm.homeip.net/~peter


Re: Showing index details with \d on psql

From
"Greg Sabino Mullane"
Date:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

> Please use "(primary key)" and add a colon after "Indexes".

Done and done. I capitalized and used brackets to make it
stand out more from the index contents, but it is now changed
back.

> I don't like the '*' things.  They look ugly and convey little
> real information.

They convey "this column is indexed" and also indicate in how many
indexes it appears.

Anyway, revised patch attached.

Revised sample output:

                          Table "b1"
 Column |           Type           |        Modifiers
- --------+--------------------------+--------------------------
 post   | integer                  | *
 thread | smallint                 | not null *
 reply  | smallint                 | not null *
 stat   | character varying(1)     | not null default 'N' * *
 uid    | integer                  | not null
 ctime  | timestamp with time zone | default 'now'
 atime  | timestamp with time zone |
Indexes: abc (post, reply, stat)
         def (stat)
         foo_pkey (primary key) (thread)


Greg Sabino Mullane
greg@turnstep.com
PGP Key: 0x14964AC8 200110131806


-----BEGIN PGP SIGNATURE-----
Comment: http://www.turnstep.com/pgp.html

iQA/AwUBO8i8YrybkGcUlkrIEQI1ngCcCtzjcBB8hMrBsAh+wbSsExJCpUYAoP4E
FWCa51FCDi1BP8zTgdujnDlu
=4P3T
-----END PGP SIGNATURE-----
*** ./src/bin/psql/describe.c.orig    Sat Oct 13 10:13:10 2001
--- ./src/bin/psql/describe.c    Sat Oct 13 11:17:27 2001
***************
*** 707,718 ****
          PGresult        *result1 = NULL,
                          *result2 = NULL,
                          *result3 = NULL,
!                         *result4 = NULL,
!                         *result5 = NULL,
!                         *result6 = NULL;
          int            index_count = 0,
-                         primary_count = 0,
-                         unique_count = 0,
                          constr_count = 0,
                          rule_count = 0,
                          trigger_count = 0;
--- 707,714 ----
          PGresult        *result1 = NULL,
                          *result2 = NULL,
                          *result3 = NULL,
!                         *result4 = NULL;
          int            index_count = 0,
                          constr_count = 0,
                          rule_count = 0,
                          trigger_count = 0;
***************
*** 721,730 ****
          /* count indexes */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname\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"
!                "AND NOT i.indisunique ORDER BY c2.relname",
                      name);
              result1 = PSQLexec(buf);
              if (!result1)
--- 717,726 ----
          /* count indexes */
          if (!error && tableinfo.hasindex)
          {
!             sprintf(buf, "SELECT c2.relname, i.indkey, i.indisprimary, i.indisunique\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 c2.relname",
                      name);
              result1 = PSQLexec(buf);
              if (!result1)
***************
*** 733,774 ****
                  index_count = PQntuples(result1);
          }

-         /* count primary keys */
-         if (!error && tableinfo.hasindex)
-         {
-             sprintf(buf, "SELECT c2.relname\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"
-                "AND i.indisprimary AND i.indisunique ORDER BY c2.relname",
-                     name);
-             result5 = PSQLexec(buf);
-             if (!result5)
-                 error = true;
-             else
-                 primary_count = PQntuples(result5);
-         }
-
-         /* count unique constraints */
-         if (!error && tableinfo.hasindex)
-         {
-             sprintf(buf, "SELECT c2.relname\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"
-                "AND NOT i.indisprimary AND i.indisunique ORDER BY c2.relname",
-                     name);
-             result6 = PSQLexec(buf);
-             if (!result6)
-                 error = true;
-             else
-                 unique_count = PQntuples(result6);
-         }
-
          /* count table (and column) constraints */
          if (!error && tableinfo.checks)
          {
              sprintf(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);
              if (!result2)
--- 729,741 ----
                  index_count = PQntuples(result1);
          }

          /* count table (and column) constraints */
          if (!error && tableinfo.checks)
          {
              sprintf(buf, "SELECT rcsrc, rcname\n"
                      "FROM pg_relcheck r, pg_class c\n"
!                     "WHERE c.relname='%s' AND c.oid = r.rcrelid\n"
!                     "ORDER BY rcname",
                      name);
              result2 = PSQLexec(buf);
              if (!result2)
***************
*** 783,789 ****
              sprintf(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);
              if (!result3)
--- 750,757 ----
              sprintf(buf,
                      "SELECT r.rulename\n"
                      "FROM pg_rewrite r, pg_class c\n"
!                     "WHERE c.relname='%s' AND c.oid = r.ev_class\n"
!                     "ORDER BY r.rulename",
                      name);
              result3 = PSQLexec(buf);
              if (!result3)
***************
*** 798,804 ****
              sprintf(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);
              if (!result4)
--- 766,773 ----
              sprintf(buf,
                      "SELECT t.tgname\n"
                      "FROM pg_trigger t, pg_class c\n"
!                     "WHERE c.relname='%s' AND c.oid = t.tgrelid\n"
!                     "ORDER BY t.tgname",
                      name);
              result4 = PSQLexec(buf);
              if (!result4)
***************
*** 807,858 ****
                  trigger_count = PQntuples(result4);
          }

!         footers = xmalloc((index_count + primary_count + unique_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)
!                 snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result1, i, 0));
!             else
!                 snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result1, i, 0));
!             if (i < index_count - 1)
!                 strcat(buf, ",");
!
!             footers[count_footers++] = xstrdup(buf);
!         }
!
!         /* print primary keys */
!         for (i = 0; i < primary_count; i++)
!         {
!             char   *s = _("Primary key");
!
!             if (i == 0)
!                 snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result5, i, 0));
!             else
!                 snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result5, i, 0));
!             if (i < primary_count - 1)
!                 strcat(buf, ",");
!
!             footers[count_footers++] = xstrdup(buf);
!         }
!
!         /* print unique constraints */
!         for (i = 0; i < unique_count; i++)
!         {
!             char   *s = _("Unique keys");
!
!             if (i == 0)
!                 snprintf(buf, sizeof(buf), "%s: %s", s, PQgetvalue(result6, i, 0));
!             else
!                 snprintf(buf, sizeof(buf), "%*s  %s", (int)strlen(s), "", PQgetvalue(result6, i, 0));
!             if (i < unique_count - 1)
!                 strcat(buf, ",");
!
              footers[count_footers++] = xstrdup(buf);
          }

--- 776,816 ----
                  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:");

!             snprintf(buf, sizeof(buf), "%*s %s%s%s",
!                      (int)strlen(s),
!                      i == 0 ? s : "",
!                      PQgetvalue(result1, i, 0),
!                      strcmp(PQgetvalue(result1, i, 2), "t") == 0 ? _(" (primary key)") : "",
!                      strcmp(PQgetvalue(result1, i, 3), "t") == 0 ? _(" (unique)") : "");
!
!             char indexchar[5]; /* Should be plenty */
!             int indexnumber=0;
!             char * indexlist = PQgetvalue(result1, i, 1);
!             int j,found;
!             for (j=0,found=0;j<=strlen(indexlist); j++) {
!               if (indexlist[j] == 0 || indexlist[j] == 32) {
!                 indexnumber = atoi(indexchar);
!                 if (indexnumber>0) /* pg_class has a -2! */
!                   {
!                   strcat(cells[(indexnumber-1) * cols + 2],
!                          cells[(indexnumber-1) * cols +2][0] ? " *" : "*");
!                   strcat(buf, ++found==1 ? " (" : ", ");
!                   strcat(buf, cells[(indexnumber-1) * cols]);
!                   }
!                 indexchar[0] = '\0';
!                 }
!               else { strcat(indexchar,&indexlist[j]); }
!             }
!             if (found) /* must cover for pg_class again */
!               strcat(buf, ")");
              footers[count_footers++] = xstrdup(buf);
          }

***************
*** 907,914 ****
          PQclear(result2);
          PQclear(result3);
          PQclear(result4);
-         PQclear(result5);
-         PQclear(result6);
      }

      if (!error)
--- 865,870 ----

Re: Showing index details with \d on psql

From
Tom Lane
Date:
>> I don't like the '*' things.  They look ugly and convey little
>> real information.

> They convey "this column is indexed" and also indicate in how many
> indexes it appears.

I tend to agree with Peter on that part ... the asterisks add more
clutter than information.  I also think that they could lead to
ambiguity; for example, it's not obvious that the * is not part of
the default clause where there's a default.

I have a large number of problems with this part of your patch:

              char   *s = _("Indexes:");

!             snprintf(buf, sizeof(buf), "%*s %s%s%s",
!                      (int)strlen(s),
!                      i == 0 ? s : "",
!                      PQgetvalue(result1, i, 0),
!                      strcmp(PQgetvalue(result1, i, 2), "t") == 0 ? _(" (primary key)") : "",
!                      strcmp(PQgetvalue(result1, i, 3), "t") == 0 ? _(" (unique)") : "");
!
!             char indexchar[5]; /* Should be plenty */
!             int indexnumber=0;
!             char * indexlist = PQgetvalue(result1, i, 1);
!             int j,found;
!             for (j=0,found=0;j<=strlen(indexlist); j++) {
!               if (indexlist[j] == 0 || indexlist[j] == 32) {
!                 indexnumber = atoi(indexchar);
!                 if (indexnumber>0) /* pg_class has a -2! */
!                   {
!                   strcat(cells[(indexnumber-1) * cols + 2],
!                          cells[(indexnumber-1) * cols +2][0] ? " *" : "*");
!                   strcat(buf, ++found==1 ? " (" : ", ");
!                   strcat(buf, cells[(indexnumber-1) * cols]);
!                   }
!                 indexchar[0] = '\0';
!                 }
!               else { strcat(indexchar,&indexlist[j]); }
!             }
!             if (found) /* must cover for pg_class again */
!               strcat(buf, ")");
              footers[count_footers++] = xstrdup(buf);

Gripe #1: declarations after the start of a block are a C++-ism.  They
are not legal in ANSI C.

Gripe #2: what is indexchar[], why is it being used without
initialization, and what is your justification for thinking 5 is enough
space?

Gripe #3: "32" is not a portable spelling of "' '".

Gripe #4: looks to me like it will fail when indexes are on columns
numbered 10 or above, because the loop will do strcat() multiple times.

Gripe #5: doing the wrong thing on indexes that mention system columns
(negative column numbers) isn't acceptable.

You are really doing things quite the hard way here anyhow, since
pg_get_indexdef would produce the info you want without so much work,
and with less dependency in psql on backend catalog details.  I'd
suggest pulling the pg_get_indexdef result instead of indkey in the
SELECT, and then just print the part after USING.

BTW, "primary key" implies "unique", so I think it's not necessary to
print both annotations for a primary key.

            regards, tom lane

Re: Showing index details with \d on psql

From
Peter Eisentraut
Date:
Greg Sabino Mullane writes:

> > I don't like the '*' things.  They look ugly and convey little
> > real information.
>
> They convey "this column is indexed" and also indicate in how many
> indexes it appears.

I understood that, but a new user would not without a footnote.  And
obviously, '*' is just an arbitrary character -- what would we use next to
say "this column is part of a table constraint"?  I think this part is
very useful as it stands:

> Indexes: abc (post, reply, stat)
>          def (stat)
>          foo_pkey (primary key) (thread)

Hmm, shouldn't that be "foo_pkey(thread) (primary key)".  I understand
what you meant about the brackets, but brackets aren't typically used in
writing and look artificial.  Maybe something like

    foo_pkey (thread) -- primary key

--
Peter Eisentraut   peter_e@gmx.net   http://funkturm.homeip.net/~peter


Re: Showing index details with \d on psql

From
Tom Lane
Date:
Peter Eisentraut <peter_e@gmx.net> writes:
> Hmm, shouldn't that be "foo_pkey(thread) (primary key)".  I understand
> what you meant about the brackets, but brackets aren't typically used in
> writing and look artificial.  Maybe something like

>     foo_pkey (thread) -- primary key

Or

    primary key: foo_pkey (thread)
    unique: foo_serial_key (serialcol)

Note my advice nearby for Greg to make use of pg_get_indexdef, which
returns a CREATE INDEX command string for the index.  I have an ulterior
motive here, which is that pg_get_indexdef will deliver useful
information about functional and partial indexes, and I think \d ought
to show that stuff.

In the case of a partial index, anything tacked onto the end of the line
is likely to be confused with the partial index condition.  Consider
these examples, all obtained from the regression database with
    select pg_get_indexdef(oid) from pg_class where relkind='i';

CREATE INDEX onek2_u2_prtl ON onek2 USING btree (unique2) WHERE (stringu1 < 'B'::name)
CREATE INDEX rect2ind ON fast_emp4000 USING rtree (home_base bigbox_ops)
CREATE UNIQUE INDEX func_index_index ON func_index_heap USING btree (textcat(f1, f2))

I suggest that \d should produce entries like this:

Indexes:  onek2_u2_prtl btree (unique2) WHERE (stringu1 < 'B'::name)

Indexes:  rect2ind rtree (home_base bigbox_ops)

Indexes:  unique: func_index_index btree (textcat(f1, f2))

or possibly this would read better:

Indexes:  func_index_index unique btree (textcat(f1, f2))

(s/unique/primary key/ where appropriate).

            regards, tom lane

Re: Showing index details with \d on psql

From
"Christopher Kings-Lynne"
Date:
> You are really doing things quite the hard way here anyhow, since
> pg_get_indexdef would produce the info you want without so much work,
> and with less dependency in psql on backend catalog details.  I'd
> suggest pulling the pg_get_indexdef result instead of indkey in the
> SELECT, and then just print the part after USING.

Quick question - where are all these handy 'pg_get_indexdef' and friends
functions documented???

Chris


Re: Showing index details with \d on psql

From
"Christopher Kings-Lynne"
Date:
> "Christopher Kings-Lynne" <chriskl@familyhealth.com.au> writes:
> > Quick question - where are all these handy 'pg_get_indexdef' and friends
> > functions documented???
>
> They ain't :-(.  Feel free to contribute some documentation patches...

When did they appear?  Are they 7.2-devel only, or are they hidden in 7.1.3
as well?  I ask because I'm a committer on the phpPgAdmin (WebPg) project
and I've never heard of them!  They seem like they'd be helpful in replacing
some of our 20 line sql statements!

Chris


Re: Showing index details with \d on psql

From
Tom Lane
Date:
"Christopher Kings-Lynne" <chriskl@familyhealth.com.au> writes:
> Quick question - where are all these handy 'pg_get_indexdef' and friends
> functions documented???
>>
>> They ain't :-(.  Feel free to contribute some documentation patches...

> When did they appear?  Are they 7.2-devel only, or are they hidden in 7.1.3
> as well?

pg_get_indexdef goes back to 6.4, according to the cvs logs at
http://developer.postgresql.org/cvsweb.cgi/pgsql/src/backend/utils/adt/ruleutils.c

            regards, tom lane

Re: Showing index details with \d on psql

From
Tom Lane
Date:
"Christopher Kings-Lynne" <chriskl@familyhealth.com.au> writes:
> Quick question - where are all these handy 'pg_get_indexdef' and friends
> functions documented???

They ain't :-(.  Feel free to contribute some documentation patches...

            regards, tom lane

Re: Showing index details with \d on psql

From
"Greg Sabino Mullane"
Date:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

> I understood that, but a new user would not without a footnote.  And
> obviously, '*' is just an arbitrary character...

Hey, the asterick was Bruce's idea! :) Consider it dropped.

>     foo_pkey (thread) -- primary key

I'll put that and the other suggestions by Tom Lane into
my next patch, when I have time for it. Please don't let my
possible acquirement of free time in the future dissuade
others from knocking the patch out.

Greg Sabino Mullane
greg@turnstep.com
PGP Key: 0x14964AC8 200110171426

-----BEGIN PGP SIGNATURE-----
Comment: http://www.turnstep.com/pgp.html

iQA/AwUBO88e+bybkGcUlkrIEQKfLgCfRhobqXuWMwBhqSjl+5p391IYxvEAnjLh
KGaka5SSVF87brjN6joSy7us
=jv4S
-----END PGP SIGNATURE-----


Re: Showing index details with \d on psql

From
Tom Lane
Date:
Peter Eisentraut <peter_e@gmx.net> writes:
> Christopher Kings-Lynne writes:
>> Quick question - where are all these handy 'pg_get_indexdef' and friends
>> functions documented???

> I think they aren't documented because we'd rather not have them exposed
> to users, since they might change whenever it seems convenient.  Whether
> this is a good strategy is another question.

My take is that they aren't documented because Jan didn't bother to
document them :-(.  I think we're much better off encouraging
applications to use these functions rather than burrowing around
in the system catalogs for themselves, which seems the only alternative.

            regards, tom lane

Re: Showing index details with \d on psql

From
Peter Eisentraut
Date:
Christopher Kings-Lynne writes:

> Quick question - where are all these handy 'pg_get_indexdef' and friends
> functions documented???

I think they aren't documented because we'd rather not have them exposed
to users, since they might change whenever it seems convenient.  Whether
this is a good strategy is another question.

--
Peter Eisentraut   peter_e@gmx.net   http://funkturm.homeip.net/~peter


Re: Showing index details with \d on psql

From
Bruce Momjian
Date:
Greg, do you have an updated version of the patch you would like applied
to 7.3?

---------------------------------------------------------------------------

Greg Sabino Mullane wrote:
> my next patch, when I have time for it. Please don't let my
> possible acquirement of free time in the future dissuade
> others from knocking the patch out.
>
> Greg Sabino Mullane
> greg@turnstep.com
> PGP Key: 0x14964AC8 200110171426
-- End of PGP signed section, PGP failed!

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Showing index details with \d on psql

From
Bruce Momjian
Date:
Patch applied.  Thanks.

---------------------------------------------------------------------------


Greg Sabino Mullane wrote:
[ There is text before PGP section. ]
>
-- Start of PGP signed section.
>
> > Greg, do you have an updated version of the patch you would like
> > applied to 7.3
>
> Yes, I think this one should work:
>
> http://www.gtsm.com/postgres/showindex2.patch
>
> Greg Sabino Mullane  greg@turnstep.com
> PGP Key: 0x14964AC8 200202221455
-- End of PGP signed section.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026