Add psql command to list constraints
Hi,
I have been wondering why there is no meta-command for listing
constraints in psql. So, I created a POC patch by using my
experience developing \dX command in PG14.
This feature is helpful for DBAs when they want to check or
modify the definition of constraints.
The current status of the POC patch is as follows:
- Add "\dco" command to list constraints from pg_constraint
- Not implemented yet:
- NOT NULL constraint, and so on (based on pg_attribute)
- Tab completion
- Regression test
- Document
The following is test results (See attached test_list_con.sql)
====================================================================
postgres=# \dco
List of constsraints
Schema | Name | Definition | Table
--------+-------------------------+---------------------------------------------------------+----------
public | t01_chk_price_check | CHECK ((price > (0)::numeric)) | t01_chk
public | t02_uniq_product_no_key | UNIQUE (product_no) | t02_uniq
public | t03_pk1_pkey | PRIMARY KEY (product_no) | t03_pk1
public | t03_pk2_product_no_key | UNIQUE (product_no) | t03_pk2
public | t04_fk_pkey | PRIMARY KEY (order_id) | t04_fk
public | t04_fk_product_no_fkey | FOREIGN KEY (product_no) REFERENCES t03_pk1(product_no) | t04_fk
public | t05_ex_c_excl | EXCLUDE USING gist (c WITH &&) | t05_ex
(7 rows)
====================================================================
I have the following two questions that need to be discussed.
Questions:
(1) What strings should be assigned as meta-command for this feature?
Currently, \dc and \dC are not available, so I tentatively
assigned \dco. However, I do not have a strong opinion, so please
let me know if you have any suggestions.
(2) About domain constraints
There is the \dD command to show a list of domain constraints.
So I think this feature should not include it. Is it Okay?
If I can get "+1" for this new feature development, I would like to
improve the patch by adding NOT NULL constraints, and so on.
Any advice or comments would be appreciated.
Thanks,
Tatsuro Yamada
Attachments:
add_psql_command_to_list_constraint_poc1.patchtext/plain; charset=UTF-8; name=add_psql_command_to_list_constraint_poc1.patchDownload
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 49d4c0e3ce..c450972f27 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -769,7 +769,10 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
- success = listConversions(pattern, show_verbose, show_system);
+ if (strncmp(cmd, "dco", 3) == 0 || strncmp(cmd, "dcoS", 4) == 0) /* Constraint */
+ success = listConstraints(pattern, show_system);
+ else
+ success = listConversions(pattern, show_verbose, show_system); /* Conversion */
break;
case 'C':
success = listCasts(pattern, show_verbose);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea721d963a..bd1ec2df5f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4815,6 +4815,61 @@ listExtendedStats(const char *pattern)
return true;
}
+/*
+ * \dco
+ *
+ * Describes constraints
+ */
+bool
+listConstraints(const char *pattern, bool showSystem)
+{
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+
+ initPQExpBuffer(&buf);
+ printfPQExpBuffer(&buf,
+ "SELECT \n"
+ "n.nspname AS \"%s\", \n"
+ "cst.conname AS \"%s\", \n"
+ "pg_catalog.pg_get_constraintdef(cst.oid) AS \"%s\", \n"
+ "c.relname AS \"%s\" \n"
+ "FROM pg_constraint cst \n"
+ "JOIN pg_namespace n ON n.oid = cst.connamespace \n"
+ "JOIN pg_class c ON c.oid = cst.conrelid \n",
+ gettext_noop("Schema"),
+ gettext_noop("Name"),
+ gettext_noop("Definition"),
+ gettext_noop("Table")
+ );
+
+ if (!showSystem && !pattern)
+ appendPQExpBufferStr(&buf,
+ "WHERE n.nspname <> 'pg_catalog' \n"
+ " AND n.nspname <> 'information_schema' \n");
+
+ processSQLNamePattern(pset.db, &buf, pattern,
+ !showSystem && !pattern, false,
+ "n.nspname", "cst.conname",
+ NULL, "pg_catalog.pg_table_is_visible(cst.conrelid)");
+
+ appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
+
+ res = PSQLexec(buf.data);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of constsraints");
+ myopt.translate_header = true;
+
+ printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+ PQclear(res);
+ return true;
+}
+
/*
* \dC
*
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 71b320f1fc..53e51db036 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -76,6 +76,9 @@ extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
/* \dc */
extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
+/* \dco */
+extern bool listConstraints(const char *pattern, bool showSystem);
+
/* \dC */
extern bool listCasts(const char *pattern, bool verbose);
Hi,
On Mon, Nov 15, 2021 at 10:38:55AM +0900, Tatsuro Yamada wrote:
postgres=# \dco
List of constsraints
Schema | Name | Definition | Table
--------+-------------------------+---------------------------------------------------------+----------
public | t01_chk_price_check | CHECK ((price > (0)::numeric)) | t01_chk
public | t02_uniq_product_no_key | UNIQUE (product_no) | t02_uniq
public | t03_pk1_pkey | PRIMARY KEY (product_no) | t03_pk1
public | t03_pk2_product_no_key | UNIQUE (product_no) | t03_pk2
public | t04_fk_pkey | PRIMARY KEY (order_id) | t04_fk
public | t04_fk_product_no_fkey | FOREIGN KEY (product_no) REFERENCES t03_pk1(product_no) | t04_fk
public | t05_ex_c_excl | EXCLUDE USING gist (c WITH &&) | t05_ex
(7 rows)
====================================================================
Maybe it ought to be possible to choose the type of constraints to show.
Similar to how \dt shows tables and \di shows indexes and \dti shows
tables+inds, you could run \dcoc for check constraints and \dcof for foreign
keys. But I think "\dco" is too long of a prefix...
+ initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT \n" + "n.nspname AS \"%s\", \n" + "cst.conname AS \"%s\", \n" + "pg_catalog.pg_get_constraintdef(cst.oid) AS \"%s\", \n" + "c.relname AS \"%s\" \n" + "FROM pg_constraint cst \n" + "JOIN pg_namespace n ON n.oid = cst.connamespace \n" + "JOIN pg_class c ON c.oid = cst.conrelid \n",
You should write "pg_catalog." prefix for the tables (in addition to the
function).
Rather than join to pg_class, you can write conrelid::pg_catalog.regclass,
since regclass is supported since at least v7.3 (but ::regnamespace was
introduced in v9.5, so the join against pg_namespace is still necessary).
https://www.postgresql.org/docs/9.5/datatype-oid.html
+ myopt.title = _("List of constsraints");
spelling: constraints
I'm not confident that if I would use this, so let's wait to see if someone
else wants to give a +1.
--
Justin
Hi Justin,
Thanks for your comments and review!
Maybe it ought to be possible to choose the type of constraints to show.
Similar to how \dt shows tables and \di shows indexes and \dti shows
tables+inds, you could run \dcoc for check constraints and \dcof for foreign
keys. But I think "\dco" is too long of a prefix...
Yeah, agreed.
I added a filter based on the type of constraints:
- c for check
- f for foreign key
- p for primary key
- t for trigger
- u for unique
- x for exclude c, f, p, u, t, and x.
The following is examples of \dcop, \dcof, and \dcopf.
========================================================================
postgres=# \dcop
List of constraints
Schema | Name | Definition | Table
--------+--------------+--------------------------+---------
public | t03_pk1_pkey | PRIMARY KEY (product_no) | t03_pk1
public | t04_fk_pkey | PRIMARY KEY (order_id) | t04_fk
(2 rows)
postgres=# \dcof
List of constraints
Schema | Name | Definition | Table
--------+------------------------+---------------------------------------------------------+--------
public | t04_fk_product_no_fkey | FOREIGN KEY (product_no) REFERENCES t03_pk1(product_no) | t04_fk
(1 row)
postgres=# \dcopf
List of constraints
Schema | Name | Definition | Table
--------+------------------------+---------------------------------------------------------+---------
public | t03_pk1_pkey | PRIMARY KEY (product_no) | t03_pk1
public | t04_fk_pkey | PRIMARY KEY (order_id) | t04_fk
public | t04_fk_product_no_fkey | FOREIGN KEY (product_no) REFERENCES t03_pk1(product_no) | t04_fk
(3 rows)
========================================================================
I too think \dco is a long name. So, I'd like to get suggestions to be more shortened. :)
+ initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT \n" + "n.nspname AS \"%s\", \n" + "cst.conname AS \"%s\", \n" + "pg_catalog.pg_get_constraintdef(cst.oid) AS \"%s\", \n" + "c.relname AS \"%s\" \n" + "FROM pg_constraint cst \n" + "JOIN pg_namespace n ON n.oid = cst.connamespace \n" + "JOIN pg_class c ON c.oid = cst.conrelid \n",You should write "pg_catalog." prefix for the tables (in addition to the
function).
Oops, You are right. Fixed.
Rather than join to pg_class, you can write conrelid::pg_catalog.regclass,
since regclass is supported since at least v7.3 (but ::regnamespace was
introduced in v9.5, so the join against pg_namespace is still necessary).
https://www.postgresql.org/docs/9.5/datatype-oid.html+ myopt.title = _("List of constsraints");
spelling: constraints
Thanks! Fixed.
I'm not confident that if I would use this, so let's wait to see if someone
else wants to give a +1.
Okay, but you agree that there are DBAs and users who want to see the
list of constraints, I think.
Currently, DBAs need the table name to see the constraint information.
However, with this feature, you can see its definition and table name
from the constraint name.
For example, it will be easier to understand how many foreign key
constraints are in the DB. The \d command also displays the constraints
but does not list them, so this feature is more beneficial for those who
want to check them.
Attached new patch includes:
- Add a filter by contype
- Add pg_catalog prefix
- Fix typo
- Fix help message to add \dco
Not implemented yet:
- NOT NULL constraint, and so on (based on pg_attribute)
- Tab completion
- Regression test
- Document
Any comments welcome! :-D
Thanks,
Tatsuro Yamada
Attachments:
0001-Add-psql-command-to-list-constraints-POC2.patchtext/plain; charset=UTF-8; name=0001-Add-psql-command-to-list-constraints-POC2.patchDownload
From ce46a3fa7252109348876ab9efff8bafcb119730 Mon Sep 17 00:00:00 2001
From: Tatsuro Yamada <yamatattsu@gmail.com>
Date: Mon, 15 Nov 2021 17:58:31 +0900
Subject: [PATCH] Add psql command to list constraints POC2
- Add a filter by contype
- Add pg_catalog prefix
- Fix typo
- Fix help message to add \dco
---
src/bin/psql/command.c | 19 +++++++++-
src/bin/psql/describe.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++
src/bin/psql/describe.h | 3 ++
src/bin/psql/help.c | 1 +
4 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 3de9d09..92c61bc 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -769,7 +769,24 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
- success = listConversions(pattern, show_verbose, show_system);
+ if (strncmp(cmd, "dco", 3) == 0) /* Constraint */
+ switch (cmd[3])
+ {
+ case '\0':
+ case '+':
+ case 'S':
+ case 'c':
+ case 'f':
+ case 'p':
+ case 'u':
+ case 't':
+ case 'x':
+ success = listConstraints(&cmd[2], pattern, show_system);
+ break;
+ default:
+ status = PSQL_CMD_UNKNOWN;
+ break;
+ }
break;
case 'C':
success = listCasts(pattern, show_verbose);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea721d9..8837626 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -18,6 +18,7 @@
#include "catalog/pg_attribute_d.h"
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
+#include "catalog/pg_constraint_d.h"
#include "catalog/pg_default_acl_d.h"
#include "common.h"
#include "common/logging.h"
@@ -4816,6 +4817,103 @@ listExtendedStats(const char *pattern)
}
/*
+ * \dco
+ *
+ * Describes constraints
+ *
+ * As with \d, you can specify the kinds of constraints you want:
+ *
+ * c for check
+ * f for foreign key
+ * p for primary key
+ * t for trigger
+ * u for unique
+ * x for exclude
+ *
+ * and you can mix and match these in any order.
+ */
+bool
+listConstraints(const char *contypes, const char *pattern, bool showSystem)
+{
+ bool showCheck = strchr(contypes, 'c') != NULL;
+ bool showForeign = strchr(contypes, 'f') != NULL;
+ bool showPrimary = strchr(contypes, 'p') != NULL;
+ bool showTrigger = strchr(contypes, 't') != NULL;
+ bool showUnique = strchr(contypes, 'u') != NULL;
+ bool showExclude = strchr(contypes, 'x') != NULL;
+ bool showAllkinds = false;
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+
+ /* If contype was not selected, show them all */
+ if (!(showCheck || showForeign || showPrimary || showTrigger || showUnique || showExclude))
+ showAllkinds = true;
+
+ initPQExpBuffer(&buf);
+ printfPQExpBuffer(&buf,
+ "SELECT \n"
+ " n.nspname AS \"%s\", \n"
+ " cst.conname AS \"%s\", \n"
+ " pg_catalog.pg_get_constraintdef(cst.oid) AS \"%s\", \n"
+ " conrelid::pg_catalog.regclass AS \"%s\" \n"
+ "FROM pg_catalog.pg_constraint cst \n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = cst.connamespace \n",
+ gettext_noop("Schema"),
+ gettext_noop("Name"),
+ gettext_noop("Definition"),
+ gettext_noop("Table")
+ );
+
+ if (!showSystem && !pattern)
+ appendPQExpBufferStr(&buf,
+ "WHERE n.nspname <> 'pg_catalog' \n"
+ " AND n.nspname <> 'information_schema' \n");
+
+ processSQLNamePattern(pset.db, &buf, pattern,
+ !showSystem && !pattern, false,
+ "n.nspname", "cst.conname",
+ NULL, "pg_catalog.pg_table_is_visible(cst.conrelid)");
+
+ if (!showAllkinds)
+ {
+ appendPQExpBufferStr(&buf, " AND cst.contype in (");
+
+ if (showCheck)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_CHECK) ",");
+ if (showForeign)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_FOREIGN) ",");
+ if (showPrimary)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_PRIMARY) ",");
+ if (showTrigger)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_TRIGGER) ",");
+ if (showUnique)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_UNIQUE) ",");
+ if (showExclude)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_EXCLUSION) ",");
+
+ appendPQExpBufferStr(&buf, "''"); /* dummy */
+ appendPQExpBufferStr(&buf, ")\n");
+ }
+
+ appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
+
+ res = PSQLexec(buf.data);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of constraints");
+ myopt.translate_header = true;
+
+ printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+ PQclear(res);
+ return true;
+}
+
+/*
* \dC
*
* Describes casts.
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 71b320f..083ca6c 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -76,6 +76,9 @@ extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
/* \dc */
extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
+/* \dco */
+extern bool listConstraints(const char *contypes, const char *pattern, bool showSystem);
+
/* \dC */
extern bool listCasts(const char *pattern, bool verbose);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index db12a8b..62c11c5 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -231,6 +231,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dAp[+] [AMPTRN [OPFPTRN]] list support functions of operator families\n"));
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n"));
+ fprintf(output, _(" \\dco[S] [PATTERN] list constraint\n"));
fprintf(output, _(" \\dC[+] [PATTERN] list casts\n"));
fprintf(output, _(" \\dd[S] [PATTERN] show object descriptions not displayed elsewhere\n"));
fprintf(output, _(" \\dD[S+] [PATTERN] list domains\n"));
--
1.8.3.1
Thanks for updating the patch :)
Currently, DBAs need the table name to see the constraint information.
Or, they can query pg_constraint or information_schema: check_constraints,
table_constraints.
- success = listConversions(pattern, show_verbose, show_system); + if (strncmp(cmd, "dco", 3) == 0) /* Constraint */ + switch (cmd[3]) + { + case '\0': + case '+':
Does "+" do anything ?
+++ b/src/bin/psql/help.c @@ -231,6 +231,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n"));
+ fprintf(output, _(" \\dco[S] [PATTERN] list constraint\n"));
Should be plural "constraints".
I think "exclude" should be called "exclusion" ("exclude" sounded to me like
you're going to provide a way to "exclude" types of constraints, like "xc"
would show everything except check constraints).
--
Justin
On Mon, Nov 15, 2021 at 5:23 PM Tatsuro Yamada <
tatsuro.yamada.tf@nttcom.co.jp> wrote:
I'm not confident that if I would use this, so let's wait to see if
someone
else wants to give a +1.
Okay, but you agree that there are DBAs and users who want to see the
list of constraints, I think.
My opinion is this doesn't exist because there isn't any demand for it.
For example, it will be easier to understand how many foreign key
constraints are in the DB.
That isn't a very compelling metric. Metrics also don't seem to be the
primary motivation for the psql \d commands. I envision them mostly useful
when writing a query and wanting a query refresher as to what is
valid/available. In that context looking at constraints in the context of
a single table makes sense. Looking at all constraints is considerably
less so. Especially since constraints mostly impact insert queries and
those only affect a single table.
If the only motivation for this is "feature completion" - since we have so
many other \d commands already implemented - I say we should pass.
David J.
On Monday, November 15, 2021, David G. Johnston <david.g.johnston@gmail.com>
wrote:
If the only motivation for this is "feature completion" - since we have so
many other \d commands already implemented - I say we should pass.
If anything, doing this for triggers would be a much better feature.
The other missing listing then would be columns. I would rather add both
columns and constraints, or neither. And add triggers regardless as their
combination of rarity and performance impact makes having them listable on
a \d command would be beneficial.
I also noticed that the “\dd” command would need to be modified (and maybe
deprecated if we actually do simply provide a listing for object type).
David J.
Hi Justin,
Thanks for your comments!
Currently, DBAs need the table name to see the constraint information.
Or, they can query pg_constraint or information_schema: check_constraints,
table_constraints.
Yeah, right.
If they can use the meta-command instead of a long query against pg_constraint
or information_schema and also pg_attribulte, it would be helpful, I believe. :-D
- success = listConversions(pattern, show_verbose, show_system); + if (strncmp(cmd, "dco", 3) == 0) /* Constraint */ + switch (cmd[3]) + { + case '\0': + case '+':Does "+" do anything ?
No, it doesn't. Removed.
+++ b/src/bin/psql/help.c @@ -231,6 +231,7 @@ slashUsage(unsigned short int pager)fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n"));
+ fprintf(output, _(" \\dco[S] [PATTERN] list constraint\n"));Should be plural "constraints".
I think "exclude" should be called "exclusion" ("exclude" sounded to me like
you're going to provide a way to "exclude" types of constraints, like "xc"
would show everything except check constraints).
Thanks! Fixed the both.
Attached file is new patch. It includes:
- Fix help message s/constraint/constraints/
- s/Exclude/Exclusion/
- Remove unused modifier "+"
- Add document for \dco
Thanks,
Tatsuro Yamada
Attachments:
0001-Add-psql-command-to-list-constraints-POC3.patchtext/plain; charset=UTF-8; name=0001-Add-psql-command-to-list-constraints-POC3.patchDownload
From eee92ee549e49d0b5aef438aff10236611db410e Mon Sep 17 00:00:00 2001
From: Tatsuro Yamada <yamatattsu@gmail.com>
Date: Mon, 15 Nov 2021 17:58:31 +0900
Subject: [PATCH] Add psql command to list constraints POC3
- Fix help message s/constraint/constraints/
- s/Exclude/Exclusion/
- Remove unused modifier "+"
- Add document for \dco
---
doc/src/sgml/ref/psql-ref.sgml | 18 ++++++++
src/bin/psql/command.c | 18 +++++++-
src/bin/psql/describe.c | 98 ++++++++++++++++++++++++++++++++++++++++++
src/bin/psql/describe.h | 3 ++
src/bin/psql/help.c | 1 +
5 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 48248f7..c6704d7 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1388,6 +1388,24 @@ testdb=>
<varlistentry>
+ <term><literal>\dco[cfptuxS] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <listitem>
+ <para>
+ Lists constraints.
+ If <replaceable class="parameter">pattern</replaceable>
+ is specified, only entries whose name matches the pattern are listed.
+ The modifiers <literal>c</literal> (check), <literal>f</literal> (foreign key),
+ <literal>p</literal> (primary key), <literal>t</literal> (trigger),
+ <literal>u</literal> (unique), <literal>x</literal> (exclusion) can be
+ appended to the command, filtering the kind of constraints to list.
+ By default, only user-created constraints are shown; supply the
+ <literal>S</literal> modifier to include system objects.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
+ <varlistentry>
<term><literal>\dC[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
<para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 3de9d09..0379610 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -769,7 +769,23 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
- success = listConversions(pattern, show_verbose, show_system);
+ if (strncmp(cmd, "dco", 3) == 0) /* Constraint */
+ switch (cmd[3])
+ {
+ case '\0':
+ case 'S':
+ case 'c':
+ case 'f':
+ case 'p':
+ case 'u':
+ case 't':
+ case 'x':
+ success = listConstraints(&cmd[2], pattern, show_system);
+ break;
+ default:
+ status = PSQL_CMD_UNKNOWN;
+ break;
+ }
break;
case 'C':
success = listCasts(pattern, show_verbose);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea721d9..4724d63 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -18,6 +18,7 @@
#include "catalog/pg_attribute_d.h"
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
+#include "catalog/pg_constraint_d.h"
#include "catalog/pg_default_acl_d.h"
#include "common.h"
#include "common/logging.h"
@@ -4816,6 +4817,103 @@ listExtendedStats(const char *pattern)
}
/*
+ * \dco
+ *
+ * Describes constraints
+ *
+ * As with \d, you can specify the kinds of constraints you want:
+ *
+ * c for check
+ * f for foreign key
+ * p for primary key
+ * t for trigger
+ * u for unique
+ * x for exclusion
+ *
+ * and you can mix and match these in any order.
+ */
+bool
+listConstraints(const char *contypes, const char *pattern, bool showSystem)
+{
+ bool showCheck = strchr(contypes, CONSTRAINT_CHECK) != NULL;
+ bool showForeign = strchr(contypes, CONSTRAINT_FOREIGN) != NULL;
+ bool showPrimary = strchr(contypes, CONSTRAINT_PRIMARY) != NULL;
+ bool showTrigger = strchr(contypes, CONSTRAINT_TRIGGER) != NULL;
+ bool showUnique = strchr(contypes, CONSTRAINT_UNIQUE) != NULL;
+ bool showExclusion = strchr(contypes, CONSTRAINT_EXCLUSION) != NULL;
+ bool showAllkinds = false;
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+
+ /* If contype was not selected, show them all */
+ if (!(showCheck || showForeign || showPrimary || showTrigger || showUnique || showExclusion))
+ showAllkinds = true;
+
+ initPQExpBuffer(&buf);
+ printfPQExpBuffer(&buf,
+ "SELECT \n"
+ " n.nspname AS \"%s\", \n"
+ " cst.conname AS \"%s\", \n"
+ " pg_catalog.pg_get_constraintdef(cst.oid) AS \"%s\", \n"
+ " conrelid::pg_catalog.regclass AS \"%s\" \n"
+ "FROM pg_catalog.pg_constraint cst \n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = cst.connamespace \n",
+ gettext_noop("Schema"),
+ gettext_noop("Name"),
+ gettext_noop("Definition"),
+ gettext_noop("Table")
+ );
+
+ if (!showSystem && !pattern)
+ appendPQExpBufferStr(&buf,
+ "WHERE n.nspname <> 'pg_catalog' \n"
+ " AND n.nspname <> 'information_schema' \n");
+
+ processSQLNamePattern(pset.db, &buf, pattern,
+ !showSystem && !pattern, false,
+ "n.nspname", "cst.conname",
+ NULL, "pg_catalog.pg_table_is_visible(cst.conrelid)");
+
+ if (!showAllkinds)
+ {
+ appendPQExpBufferStr(&buf, " AND cst.contype in (");
+
+ if (showCheck)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_CHECK) ",");
+ if (showForeign)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_FOREIGN) ",");
+ if (showPrimary)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_PRIMARY) ",");
+ if (showTrigger)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_TRIGGER) ",");
+ if (showUnique)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_UNIQUE) ",");
+ if (showExclusion)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_EXCLUSION) ",");
+
+ appendPQExpBufferStr(&buf, "''"); /* dummy */
+ appendPQExpBufferStr(&buf, ")\n");
+ }
+
+ appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
+
+ res = PSQLexec(buf.data);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of constraints");
+ myopt.translate_header = true;
+
+ printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+ PQclear(res);
+ return true;
+}
+
+/*
* \dC
*
* Describes casts.
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 71b320f..083ca6c 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -76,6 +76,9 @@ extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
/* \dc */
extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
+/* \dco */
+extern bool listConstraints(const char *contypes, const char *pattern, bool showSystem);
+
/* \dC */
extern bool listCasts(const char *pattern, bool verbose);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index db12a8b..0804b9c 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -231,6 +231,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dAp[+] [AMPTRN [OPFPTRN]] list support functions of operator families\n"));
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n"));
+ fprintf(output, _(" \\dco[cfptuxS] [PATTERN] list constraints\n"));
fprintf(output, _(" \\dC[+] [PATTERN] list casts\n"));
fprintf(output, _(" \\dd[S] [PATTERN] show object descriptions not displayed elsewhere\n"));
fprintf(output, _(" \\dD[S+] [PATTERN] list domains\n"));
--
1.8.3.1
Hi David,
Thanks for your comments.
Okay, but you agree that there are DBAs and users who want to see the
list of constraints, I think.My opinion is this doesn't exist because there isn't any demand for it.
I don't know if this is a good example, but if you look at StackOverflow,
it seems that people who want to see a list of constraints appear regularly.
(The other day, I also wanted to see the list, and I arrived at this site)
Therefore, the only thing that hasn't been implemented so far is that no one
could communicate the request to -hackers, and I think there is demand.
https://stackoverflow.com/questions/62987794/how-to-list-all-constraints-of-a-table-in-postgresql
Regards,
Tatsuro Yamada
On Monday, November 15, 2021, Tatsuro Yamada <tatsuro.yamada.tf@nttcom.co.jp>
wrote:
I don't know if this is a good example, but if you look at StackOverflow,
it seems that people who want to see a list of constraints appear
regularly.
https://stackoverflow.com/questions/62987794/how-to-list-all
-constraints-of-a-table-in-postgresql
Given the questioner restricted their question to “for a given table” I’d
say it supports leaving the status quo, not changing it.
If, as you suppose, these come up regularly then finding one that asks for
it “in the entire database”, ideally with some stated goal, should be
doable.
David J.
"David G. Johnston" <david.g.johnston@gmail.com> writes:
On Monday, November 15, 2021, Tatsuro Yamada
<tatsuro.yamada.tf@nttcom.co.jp> wrote:I don't know if this is a good example, but if you look at
StackOverflow,
it seems that people who want to see a list of constraints appear
regularly.https://stackoverflow.com/questions/62987794/how-to-list-all-constraints-
of-a-table-in-postgresqlGiven the questioner restricted their question to “for a given table”
I’d say it supports leaving the status quo, not changing it.If, as you suppose, these come up regularly then finding one that asks
for it “in the entire database”, ideally with some stated goal, should
be doable.David J.
This is my review of the patch in
https://commitfest.postgresql.org/37/3468/
The patch adds the command "\dco" to list constraints in psql. This
seems useful to me.
The patch applies cleanly to HEAD, although some hunks have rather large
offsets.
As far as I can tell, the "\dco" command works as documented.
I have however found the following issues with the patch:
* A TAB character has been added to doc/src/sgml/ref/psql-ref.sgml -
this should be replaced with spaces.
* The call to listConstraints in line src/bin/psql/command.c 794 refers
to &cmd[2], this should rather be &cmd[3].
* The patch kills the "\dc" command in src/bin/psql/command.c
This can be fixed by adding the following at line 800:
else
success =
listConversions(pattern, show_verbose, show_system);
Another comment is that the "\dco" command outputs quite a lot of
information, which only fits in a wide terminal window. Would it be an
idea to only display the columns "Schema" and "Name" by default, and
use "+" to specify inclusion of the columns "Definition" and "Table".
Best regards
Dag Lem
Development of this seems to have stalled with the only review of this
patch expressing some skepticism about whether it's needed at all.
Unless anyone steps forward and says it's interesting I'm going to
mark it rejected?
I don't actually think it's a terrible idea myself but I think every
use case I might have had for it was solved better by directly
querying catalog tables.
On Fri, Mar 25, 2022 at 12:28 AM Greg Stark <stark@mit.edu> wrote:
Development of this seems to have stalled with the only review of this
patch expressing some skepticism about whether it's needed at all.
My opinion on this patch is that we typically handle objects that are
essentially table properties by showing the output in \d+ on the
individual table. And that already works just fine:
rhaas=# create table duck (quack int unique, check (quack > 0));
CREATE TABLE
rhaas=# \d+ duck
Table "public.duck"
Column | Type | Collation | Nullable | Default | Storage |
Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
quack | integer | | | | plain |
| |
Indexes:
"duck_quack_key" UNIQUE CONSTRAINT, btree (quack)
Check constraints:
"duck_quack_check" CHECK (quack > 0)
Access method: heap
Now, there is some precedent for the idea of providing a command that
lists everything globally. Specifically, we have a \dp command, also
known as \z, to list privileges across all objects in the database.
However, I have found that command to be relatively useless, because
if you've got any significant number of grants in the database it just
produces too much output. I think this would have the same problem.
The other thing that we have that is somewhat similar to this is \dd,
which lists comments "for object types which do not have their
comments displayed by their own backslash commands." However, it says
that the object types that it covers are "constraint, operator class,
operator family, rule, and trigger," and that list is out of date,
because operator classes and families got their own backslash commands
two years ago. We could update that, but honestly I can't see anyone
being too excited about a command that lists comments on every
constraint, rule, and trigger in the system: it would be a lot more
useful to show those commands in the \d+ output for the table to which
they are bound, and get rid of \dd (and maybe \dp and \z too).
Now that is not to say that what is being proposed here is completely
useless. It clearly isn't. It's totally debatable whether we ought to
start having commands like this, and maybe we should. It would make
for more commands, and that's not entirely great because the command
names are increasingly alphabet soup. Who can remember what \drds or
\dAc does? Only real power users. If we add more, it's going to get
even more difficult, but some people will use it and like it and those
people will be happy. It's kind of hard to say whether we'd come out
ahead or not. What I think is fairly certain is that it would
represent a reversal of our current policy.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Fri, Mar 25, 2022 at 03:11:47PM -0400, Robert Haas wrote:
On Fri, Mar 25, 2022 at 12:28 AM Greg Stark <stark@mit.edu> wrote:
Development of this seems to have stalled with the only review of this
patch expressing some skepticism about whether it's needed at all.Now, there is some precedent for the idea of providing a command that
lists everything globally. Specifically, we have a \dp command, also
known as \z, to list privileges across all objects in the database.
The other thing that we have that is somewhat similar to this is \dd,
\dX is similar, and I remember wondering whether it was really useful/needed.
The catalog tables are exposed and documented for a reason, and power-users
will learn to use them.
+0.5 to mark the patch as RWF or rejected.
--
Justin
On Fri, Mar 25, 2022 at 3:20 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
\dX is similar, and I remember wondering whether it was really useful/needed.
The catalog tables are exposed and documented for a reason, and power-users
will learn to use them.
I don't think that \dX is comparable, because I don't think we should
regard extended statistics as a table object. Indeed, generalizing
extended statistics so that they can be generated on a join seems to
me to be one of the most important things we could do in that area.
--
Robert Haas
EDB: http://www.enterprisedb.com
In the interests of trying to clean up the CF and keep things moving
I'll mark the patch rejected.
That doesn't mean the decision can't change or that nobody's allowed
to keep discussing it. It's just that that seems to be the decision
right now and there are too many patches queued up to keep things in a
pending state waiting for a more decisive conclusion. We can always
change it if the winds shift...
Hi All,
In the interests of trying to clean up the CF and keep things moving
I'll mark the patch rejected.
Thank you for managing the commitfest and the comments from many of
hackers. I apologize for not being able to email you more often due to
my busy day job.
First of all, I understand to a certain extent your opinion that we
can use \d and look at the constraints on a table-by-table basis as a
way to check the constraints.
However, suppose We want to reverse lookup a table from a constraint.
In that case, there are two ways: (1) use "\d" to lookup all tables,
(2) execute a select statement against pg_constraint. I think the
proposed function is more valuable than these.
From another perspective, when looking at the comprehensiveness of
metacommands in psql, it seems that only functions that focus on
constraints do not exist. Therefore, It would be better to add it in
terms of comprehensiveness.
I think there is room for other discussions about this patch. Still,
at least there are people (including myself) who think it is useful.
I don't think there is anything wrong with this patch that would
complicate the code or cause performance degradation, so I would like to
continue developing it for those who want to use it.
However, I understand that it will not be ready in time for PG15, so
I would like to move forward with PG16. Therefore, the status of the
patch would be better by changing "Waiting for Author" rather than
"Rejected".
P.S.
I'll send a new patch addressing Dag's comments in the next email.
Thanks,
Tatsuro Yamada
Hi Dag,
The patch adds the command "\dco" to list constraints in psql. This
seems useful to me.
Thank you!
The patch applies cleanly to HEAD, although some hunks have rather large
offsets.As far as I can tell, the "\dco" command works as documented.
I have however found the following issues with the patch:
* A TAB character has been added to doc/src/sgml/ref/psql-ref.sgml -
this should be replaced with spaces.
Fixed.
* The call to listConstraints in line src/bin/psql/command.c 794 refers
to &cmd[2], this should rather be &cmd[3].* The patch kills the "\dc" command in src/bin/psql/command.c
This can be fixed by adding the following at line 800:
else
success =
listConversions(pattern, show_verbose, show_system);
Oh, you are right! Fixed.
Another comment is that the "\dco" command outputs quite a lot of
information, which only fits in a wide terminal window. Would it be an
idea to only display the columns "Schema" and "Name" by default, and
use "+" to specify inclusion of the columns "Definition" and "Table".
I fixed the output columns as you proposed.
The current status of this patch is:
- Addressed Dag's comments
- Not implemented yet:
- Tab completion
- Regression test
- NOT NULL constraint, and so on (based on pg_attribute)
Please find attached new patch.
Thanks,
Tatsuro Yamada
Attachments:
0001-Add-psql-command-to-list-constraints-POC4.patchtext/plain; charset=UTF-8; name=0001-Add-psql-command-to-list-constraints-POC4.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index caabb06..125ae3d 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1388,6 +1388,26 @@ testdb=>
<varlistentry>
+ <term><literal>\dco[cfptuxS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <listitem>
+ <para>
+ Lists constraints.
+ If <replaceable class="parameter">pattern</replaceable>
+ is specified, only entries whose name matches the pattern are listed.
+ The modifiers <literal>c</literal> (check), <literal>f</literal> (foreign key),
+ <literal>p</literal> (primary key), <literal>t</literal> (trigger),
+ <literal>u</literal> (unique), <literal>x</literal> (exclusion) can be
+ appended to the command, filtering the kind of constraints to list.
+ By default, only user-created constraints are shown; supply the
+ <literal>S</literal> modifier to include system objects.
+ If <literal>+</literal> is appended to the command name, each object
+ is listed with its associated description.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
+ <varlistentry>
<term><literal>\dC[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
<para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 079f4a1..05ae25e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -780,7 +780,26 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
- success = listConversions(pattern, show_verbose, show_system);
+ if (strncmp(cmd, "dco", 3) == 0) /* Constraint */
+ switch (cmd[3])
+ {
+ case '\0':
+ case '+':
+ case 'S':
+ case 'c':
+ case 'f':
+ case 'p':
+ case 't':
+ case 'u':
+ case 'x':
+ success = listConstraints(&cmd[3], pattern, show_verbose, show_system);
+ break;
+ default:
+ status = PSQL_CMD_UNKNOWN;
+ break;
+ }
+ else
+ success = listConversions(pattern, show_verbose, show_system);
break;
case 'C':
success = listCasts(pattern, show_verbose);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4dddf08..7acd25a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
#include "catalog/pg_attribute_d.h"
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
+#include "catalog/pg_constraint_d.h"
#include "catalog/pg_default_acl_d.h"
#include "common.h"
#include "common/logging.h"
@@ -4599,6 +4600,109 @@ listExtendedStats(const char *pattern)
}
/*
+ * \dco
+ *
+ * Describes constraints
+ *
+ * As with \d, you can specify the kinds of constraints you want:
+ *
+ * c for check
+ * f for foreign key
+ * p for primary key
+ * t for trigger
+ * u for unique
+ * x for exclusion
+ *
+ * and you can mix and match these in any order.
+ */
+bool
+listConstraints(const char *contypes, const char *pattern, bool verbose, bool showSystem)
+{
+ bool showCheck = strchr(contypes, CONSTRAINT_CHECK) != NULL;
+ bool showForeign = strchr(contypes, CONSTRAINT_FOREIGN) != NULL;
+ bool showPrimary = strchr(contypes, CONSTRAINT_PRIMARY) != NULL;
+ bool showTrigger = strchr(contypes, CONSTRAINT_TRIGGER) != NULL;
+ bool showUnique = strchr(contypes, CONSTRAINT_UNIQUE) != NULL;
+ bool showExclusion = strchr(contypes, CONSTRAINT_EXCLUSION) != NULL;
+ bool showAllkinds = false;
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+
+ /* If contype was not selected, show them all */
+ if (!(showCheck || showForeign || showPrimary || showTrigger || showUnique || showExclusion))
+ showAllkinds = true;
+
+ initPQExpBuffer(&buf);
+ printfPQExpBuffer(&buf,
+ "SELECT n.nspname AS \"%s\", \n"
+ " cst.conname AS \"%s\" ",
+ gettext_noop("Schema"),
+ gettext_noop("Name")
+ );
+
+ if (verbose)
+ appendPQExpBuffer(&buf,
+ ",\n pg_catalog.pg_get_constraintdef(cst.oid) AS \"%s\", \n"
+ " conrelid::pg_catalog.regclass AS \"%s\" ",
+ gettext_noop("Definition"),
+ gettext_noop("Table")
+ );
+
+ appendPQExpBufferStr(&buf,
+ "\nFROM pg_catalog.pg_constraint cst \n"
+ " JOIN pg_catalog.pg_namespace n ON n.oid = cst.connamespace \n"
+ );
+
+ if (!showSystem && !pattern)
+ appendPQExpBufferStr(&buf,
+ "WHERE n.nspname <> 'pg_catalog' \n"
+ " AND n.nspname <> 'information_schema' \n");
+
+ processSQLNamePattern(pset.db, &buf, pattern,
+ !showSystem && !pattern, false,
+ "n.nspname", "cst.conname",
+ NULL, "pg_catalog.pg_table_is_visible(cst.conrelid)");
+
+ if (!showAllkinds)
+ {
+ appendPQExpBufferStr(&buf, " AND cst.contype in (");
+
+ if (showCheck)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_CHECK) ",");
+ if (showForeign)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_FOREIGN) ",");
+ if (showPrimary)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_PRIMARY) ",");
+ if (showTrigger)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_TRIGGER) ",");
+ if (showUnique)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_UNIQUE) ",");
+ if (showExclusion)
+ appendPQExpBufferStr(&buf, CppAsString2(CONSTRAINT_EXCLUSION) ",");
+
+ appendPQExpBufferStr(&buf, "''"); /* dummy */
+ appendPQExpBufferStr(&buf, ")\n");
+ }
+
+ appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
+
+ res = PSQLexec(buf.data);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of constraints");
+ myopt.translate_header = true;
+
+ printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+ PQclear(res);
+ return true;
+}
+
+/*
* \dC
*
* Describes casts.
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index fd60796..0929ff5 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -76,6 +76,9 @@ extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
/* \dc */
extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
+/* \dco */
+extern bool listConstraints(const char *contypes, const char *pattern, bool verbose, bool showSystem);
+
/* \dC */
extern bool listCasts(const char *pattern, bool verbose);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 56afa68..6cfee66 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -231,6 +231,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dAp[+] [AMPTRN [OPFPTRN]] list support functions of operator families\n"));
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n"));
+ fprintf(output, _(" \\dco[cfptuxS+] [PATTERN] list constraints\n"));
fprintf(output, _(" \\dC[+] [PATTERN] list casts\n"));
fprintf(output, _(" \\dd[S] [PATTERN] show object descriptions not displayed elsewhere\n"));
fprintf(output, _(" \\dD[S+] [PATTERN] list domains\n"));