From ca94bf793c7037a10f539c476486d5885ea9f9ef Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Thu, 1 Aug 2019 01:15:08 +0300
Subject: [PATCH] Show opclass and opfamily related information in psql

Reported-by:
Bug:
Discussion:
Author:
Reviewed-by:
Tested-by:
Backpatch-through:
---
 doc/src/sgml/catalogs.sgml         |   8 +-
 doc/src/sgml/ref/psql-ref.sgml     |  92 ++++++++++
 src/bin/psql/command.c             |  33 +++-
 src/bin/psql/describe.c            | 333 +++++++++++++++++++++++++++++++++++++
 src/bin/psql/describe.h            |  19 +++
 src/bin/psql/help.c                |   4 +
 src/bin/psql/tab-complete.c        |  16 +-
 src/test/regress/expected/psql.out | 162 ++++++++++++++++++
 src/test/regress/sql/psql.sql      |  18 ++
 9 files changed, 679 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 34bc0d05266..10559557db2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -676,7 +676,7 @@
    search and ordering purposes.)
   </para>
 
-  <table>
+  <table id="catalog-pg-amop-table">
    <title><structname>pg_amop</structname> Columns</title>
 
    <tgroup cols="4">
@@ -819,7 +819,7 @@
    is one row for each support function belonging to an operator family.
   </para>
 
-  <table>
+  <table id="catalog-pg-amproc-table">
    <title><structname>pg_amproc</structname> Columns</title>
 
    <tgroup cols="4">
@@ -4462,7 +4462,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
    Operator classes are described at length in <xref linkend="xindex"/>.
   </para>
 
-  <table>
+  <table id="catalog-pg-opclass-table">
    <title><structname>pg_opclass</structname> Columns</title>
 
    <tgroup cols="4">
@@ -4724,7 +4724,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
    Operator families are described at length in <xref linkend="xindex"/>.
   </para>
 
-  <table>
+  <table id="catalog-pg-opfamily-table">
    <title><structname>pg_opfamily</structname> Columns</title>
 
    <tgroup cols="4">
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 20ba1051606..7743b7e88a0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1231,6 +1231,98 @@ testdb=&gt;
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term>
+          <literal>\dAc[+]
+            [<link linkend="app-psql-patterns"><replaceable class="parameter">access-method-pattern</replaceable></link>
+              [<link linkend="app-psql-patterns"><replaceable class="parameter">input-type-pattern</replaceable></link>]]
+          </literal>
+        </term>
+        <listitem>
+        <para>
+        Shows info index access method operator classes listed in
+        <xref linkend="catalog-pg-opclass-table"/>.
+        If <replaceable class="parameter">access-method-patttern</replaceable>
+        is specified, only operator classes associated with access method whose
+        name matches pattern are shown.
+        If <replaceable class="parameter">input-type-pattern</replaceable>
+        is specified, only procedures associated with type whose input type
+        matches the pattern are shown.
+        If <literal>+</literal> is appended to the command name, operator family
+        and owner are listed.
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <literal>\dAf[+]
+            [<link linkend="app-psql-patterns"><replaceable class="parameter">access-method-pattern</replaceable></link>
+              [<link linkend="app-psql-patterns"><replaceable class="parameter">input-type-pattern</replaceable></link>]]
+          </literal>
+        </term>
+        <listitem>
+        <para>
+        Shows info index access method operator families listed in
+        <xref linkend="catalog-pg-opfamily-table"/>.
+        If <replaceable class="parameter">access-method-patttern</replaceable>
+        is specified, only operator families associated with access method whose
+        name matches pattern are shown.
+        If <replaceable class="parameter">input-type-pattern</replaceable>
+        is specified, only procedures associated with type whose input type
+        matches the pattern are shown.
+        If <literal>+</literal> is appended to the command name, owner is listed.
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <literal>\dAo[+]
+            [<link linkend="app-psql-patterns"><replaceable class="parameter">access-method-pattern</replaceable></link>
+              [<link linkend="app-psql-patterns"><replaceable class="parameter">operator-family-pattern</replaceable></link>]]
+          </literal>
+        </term>
+
+        <listitem>
+        <para>
+        Lists operators (<xref linkend="catalog-pg-amop-table"/>) associated
+        with access method operator families. If
+        <replaceable class="parameter">access-method-patttern</replaceable> is
+        specified, only operators associated with access method whose name
+        matches pattern are shown. If
+        <replaceable class="parameter">operator-family-pattern</replaceable> is
+        specified, only operators associated with families whose name matches
+        the pattern are shown.
+        If <literal>+</literal> is appended to the command name, displays
+        additional info.
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <literal>\dAp[+]
+            [<link linkend="app-psql-patterns"><replaceable class="parameter">access-method-pattern</replaceable></link>
+              [<link linkend="app-psql-patterns"><replaceable class="parameter">operator-family-pattern</replaceable></link>]]
+          </literal>
+        </term>
+        <listitem>
+        <para>
+        Lists procedures (<xref linkend="catalog-pg-amproc-table"/>) associated
+        with access method operator families.
+        If <replaceable class="parameter">access-method-patttern</replaceable>
+        is specified, only procedures associated with access method whose name
+        matches pattern are shown.
+        If <replaceable class="parameter">operator-family-pattern</replaceable>
+        is specified, only procedures associated with families whose name
+        matches the pattern are shown.
+        If <literal>+</literal> is appended to the command name, procedures
+        listed with its names.
+        </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><literal>\db[+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index e111cee5568..6c70fa03bfa 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -721,7 +721,38 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 					success = listTables("tvmsE", NULL, show_verbose, show_system);
 				break;
 			case 'A':
-				success = describeAccessMethods(pattern, show_verbose);
+				{
+					char	   *pattern2 = NULL;
+
+					if (pattern && cmd[2] != '\0' && cmd[2] != '+')
+						pattern2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
+					switch (cmd[2])
+					{
+						case '\0':
+						case '+':
+							success = describeAccessMethods(pattern, show_verbose);
+							break;
+						case 'c':
+							success = describeAccessMethodOperatorClasses(pattern, pattern2, show_verbose);
+							break;
+						case 'f':
+							success = describeAccessMethodOperatorFamilies(pattern, pattern2, show_verbose);
+							break;
+						case 'o':
+							success = listFamilyClassOperators(pattern, pattern2, show_verbose);
+							break;
+						case 'p':
+							success = listOperatorFamilyProcedures(pattern, pattern2);
+							break;
+						default:
+							status = PSQL_CMD_UNKNOWN;
+							break;
+					}
+
+					if (pattern2)
+						free(pattern2);
+				}
 				break;
 			case 'a':
 				success = describeAggregates(pattern, show_verbose, show_system);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb96fa6..0f280682ccf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -14,6 +14,7 @@
 
 #include <ctype.h>
 
+#include "catalog/pg_am.h"
 #include "catalog/pg_attribute_d.h"
 #include "catalog/pg_cast_d.h"
 #include "catalog/pg_class_d.h"
@@ -6015,3 +6016,335 @@ printACLColumn(PQExpBuffer buf, const char *colname)
 						  "pg_catalog.array_to_string(%s, '\\n') AS \"%s\"",
 						  colname, gettext_noop("Access privileges"));
 }
+
+/*
+ * \dAc
+ * List index access method operator classes.
+ * Takes an optional regexp to select particular access method and type.
+ */
+bool
+describeAccessMethodOperatorClasses(const char *access_method_pattern,
+									const char *type_pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	bool		have_where = false;
+	static const bool translate_columns[] = {false, false, false, false, false,
+	false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT DISTINCT"
+					  "  am.amname AS \"%s\",\n"
+					  "  c.opcintype::pg_catalog.regtype AS \"%s\",\n"
+					  "  (CASE WHEN c.opckeytype <> 0 AND c.opckeytype <> c.opcintype\n"
+					  "    THEN c.opckeytype\n"
+					  "    ELSE NULL -- c.opcintype\n"
+					  "  END)::pg_catalog.regtype AS \"%s\",\n"
+					  "  CASE\n"
+					  "    WHEN pg_catalog.pg_opclass_is_visible(c.oid)\n"
+					  "    THEN format('%%I', c.opcname)\n"
+					  "    ELSE format('%%I.%%I', n.nspname, c.opcname)\n"
+					  "  END AS \"%s\",\n"
+					  "  (CASE WHEN c.opcdefault\n"
+					  "    THEN '%s'\n"
+					  "    ELSE '%s'\n"
+					  "  END) AS \"%s\"",
+					  gettext_noop("AM"),
+					  gettext_noop("Input type"),
+					  gettext_noop("Storage type"),
+					  gettext_noop("Operator class"),
+					  gettext_noop("yes"),
+					  gettext_noop("no"),
+					  gettext_noop("Default?"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ",\n  CASE\n"
+						  "    WHEN pg_catalog.pg_opfamily_is_visible(of.oid)\n"
+						  "    THEN format('%%I', of.opfname)\n"
+						  "    ELSE format('%%I.%%I', ofn.nspname, of.opfname)\n"
+						  "  END AS \"%s\",\n"
+						  " pg_catalog.pg_get_userbyid(c.opcowner) AS \"%s\"\n",
+						  gettext_noop("Operator family"),
+						  gettext_noop("Owner"));
+	appendPQExpBuffer(&buf,
+					  "\nFROM pg_catalog.pg_opclass c\n"
+					  "  LEFT JOIN pg_catalog.pg_am am on am.oid = c.opcmethod\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.opcnamespace\n"
+					  "  LEFT JOIN pg_catalog.pg_type t1 ON t1.oid = c.opcintype\n"
+		);
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  "  LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = c.opcfamily\n"
+						  "  LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
+
+	if (access_method_pattern)
+		have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
+										   false, false, NULL, "am.amname", NULL, NULL);
+	if (type_pattern)
+		processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
+							  NULL, "t1.typname", NULL, NULL);
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("Index access method operator classes");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \dAf
+ * List index access method operator families.
+ * Takes an optional regexp to select particular access method and type.
+ */
+bool
+describeAccessMethodOperatorFamilies(const char *access_method_pattern,
+									 const char *type_pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	bool		have_where = false;
+	static const bool translate_columns[] = {false, false, false, false, false,
+	false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT DISTINCT"
+					  "  am.amname AS \"%s\",\n"
+					  "  CASE\n"
+					  "    WHEN pg_catalog.pg_opfamily_is_visible(f.oid)\n"
+					  "    THEN format('%%I', f.opfname)\n"
+					  "    ELSE format('%%I.%%I', n.nspname, f.opfname)\n"
+					  "  END AS \"%s\",\n"
+					  "  (SELECT\n"
+					  "     string_agg(format_type(oc.opcintype, -1), ', ')\n"
+					  "   FROM pg_opclass oc\n"
+					  "   WHERE oc.opcfamily = f.oid) \"%s\"",
+					  gettext_noop("AM"),
+					  gettext_noop("Operator family"),
+					  gettext_noop("Applicable types"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ",\n  pg_catalog.pg_get_userbyid(f.opfowner) AS \"%s\"\n",
+						  gettext_noop("Owner"));
+	appendPQExpBuffer(&buf,
+					  "\nFROM pg_catalog.pg_opfamily f\n"
+					  "  LEFT JOIN pg_catalog.pg_am am on am.oid = f.opfmethod\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n"
+		);
+
+	if (access_method_pattern)
+		have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
+										   false, false, NULL, "am.amname", NULL, NULL);
+	if (type_pattern)
+	{
+		appendPQExpBuffer(&buf,
+						  "\n  %s EXISTS (\n"
+						  "    SELECT 1\n"
+						  "    FROM pg_type t\n"
+						  "    JOIN pg_opclass oc ON oc.opcintype = t.oid\n"
+						  "    WHERE oc.opcfamily = f.oid",
+						  have_where ? "AND" : "WHERE");
+		processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
+							  NULL, "t.typname", NULL, NULL);
+		appendPQExpBuffer(&buf, ")");
+	}
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("Index access method operator families");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \dAo
+ * Lists operators associated with access method operator families.
+ *
+ * Takes an optional regexp to select particular access methods
+ * and operator families
+ */
+bool
+listFamilyClassOperators(const char *access_method_pattern,
+						 const char *family_pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	bool		have_where = false;
+
+	static const bool translate_columns[] = {false, false, false, false, false,
+	false, false, true, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT\n"
+					  "  am.amname AS \"%s\",\n"
+					  "  CASE\n"
+					  "    WHEN pg_catalog.pg_opfamily_is_visible(of.oid)\n"
+					  "    THEN format('%%I', of.opfname)\n"
+					  "    ELSE format('%%I.%%I', nsf.nspname, of.opfname)\n"
+					  "  END AS \"%s\",\n"
+					  "  format ('%%s (%%s, %%s)',\n"
+					  "    CASE\n"
+					  "      WHEN pg_catalog.pg_operator_is_visible(op.oid) \n"
+					  "      THEN op.oprname::pg_catalog.text \n"
+					  "      ELSE o.amopopr::pg_catalog.regoper::pg_catalog.text \n"
+					  "    END,\n"
+					  "    pg_catalog.format_type(o.amoplefttype, NULL),\n"
+					  "    pg_catalog.format_type(o.amoprighttype, NULL)\n"
+					  "  ) AS \"%s\"\n",
+					  gettext_noop("AM"),
+					  gettext_noop("Opfamily Name"),
+					  gettext_noop("Operator"));
+
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ", o.amopstrategy AS \"%s\",\n"
+						  "  CASE o.amoppurpose\n"
+						  "    WHEN 'o' THEN '%s'\n"
+						  "    WHEN 's' THEN '%s'\n"
+						  "  END AS \"%s\",\n"
+						  "  ofs.opfname AS \"%s\"\n",
+						  gettext_noop("Strategy"),
+						  gettext_noop("ordering"),
+						  gettext_noop("search"),
+						  gettext_noop("Purpose"),
+						  gettext_noop("Sort opfamily"));
+	appendPQExpBuffer(&buf,
+					  "FROM pg_catalog.pg_amop o\n"
+					  "  LEFT JOIN pg_catalog.pg_operator op ON op.oid = o.amopopr\n"
+					  "  LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = o.amopfamily\n"
+					  "  LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod AND am.oid = o.amopmethod\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace nsf ON of.opfnamespace = nsf.oid\n");
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  "  LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
+
+	if (access_method_pattern)
+		have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
+										   false, false, NULL, "am.amname",
+										   NULL, NULL);
+
+	if (family_pattern)
+		processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
+							  "nsf.nspname", "of.opfname", NULL, NULL);
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1, 2, o.amopstrategy, 3;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List operators of family related to access method");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \dAp
+ * Lists procedures associated with access method operator families.
+ *
+ * Takes an optional regexp to select particular access methods
+ * and operator families
+ */
+bool
+listOperatorFamilyProcedures(const char *access_method_pattern,
+							 const char *family_pattern)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	bool		have_where = false;
+	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT DISTINCT\n"
+					  "  am.amname AS \"%s\",\n"
+					  "  CASE\n"
+					  "    WHEN pg_catalog.pg_opfamily_is_visible(of.oid)\n"
+					  "    THEN format('%%I', of.opfname)\n"
+					  "    ELSE format('%%I.%%I', ns.nspname, of.opfname)\n"
+					  "  END AS \"%s\",\n"
+					  "  pg_catalog.format_type(ap.amproclefttype, NULL) AS \"%s\",\n"
+					  "  pg_catalog.format_type(ap.amprocrighttype, NULL) AS \"%s\",\n"
+					  "  ap.amprocnum AS \"%s\"\n,"
+					  "  p.proname AS \"%s\"\n",
+					  gettext_noop("AM"),
+					  gettext_noop("Operator family"),
+					  gettext_noop("Left arg type"),
+					  gettext_noop("Right arg type"),
+					  gettext_noop("Number"),
+					  gettext_noop("Proc name"));
+
+	appendPQExpBuffer(&buf,
+					  "FROM pg_catalog.pg_amproc ap\n"
+					  "  LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = ap.amprocfamily\n"
+					  "  LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace ns ON of.opfnamespace = ns.oid\n"
+					  "  LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
+
+	if (access_method_pattern)
+		have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
+										   false, false, NULL, "am.amname",
+										   NULL, NULL);
+	if (family_pattern)
+		processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
+							  "ns.nspname", "of.opfname", NULL, NULL);
+
+	appendPQExpBufferStr(&buf,
+						 "ORDER BY 1, 2, 3, 4, 5;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of operator family procedures");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 20dbfd20f0f..8edcece5094 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -114,4 +114,23 @@ bool		describePublications(const char *pattern);
 /* \dRs */
 bool		describeSubscriptions(const char *pattern, bool verbose);
 
+/* \dAc */
+extern bool describeAccessMethodOperatorClasses(const char *access_method_pattern,
+												const char *opclass_pattern,
+												bool verbose);
+
+/* \dAf */
+extern bool describeAccessMethodOperatorFamilies(const char *access_method_pattern,
+												 const char *opclass_pattern,
+												 bool verbose);
+
+/* \dAp */
+extern bool listOperatorFamilyProcedures(const char *access_method_pattern,
+										 const char *family_pattern);
+
+/* \dAo */
+extern bool listFamilyClassOperators(const char *accessMethod_pattern,
+									 const char *family_pattern, bool verbose);
+
+
 #endif							/* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 1f1f7784261..95e8aea6918 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -227,6 +227,10 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\d[S+]  NAME           describe table, view, sequence, or index\n"));
 	fprintf(output, _("  \\da[S]  [PATTERN]      list aggregates\n"));
 	fprintf(output, _("  \\dA[+]  [PATTERN]      list access methods\n"));
+	fprintf(output, _("  \\dAc[+] [AMPTRN [TYPEPTRN]] list operator classes of index access methods\n"));
+	fprintf(output, _("  \\dAf[+] [AMPTRN [TYPEPTRN]] list operator families of index access methods\n"));
+	fprintf(output, _("  \\dAo[+] [AMPTRN [OPFPTRN]] list operators of family related to access method\n"));
+	fprintf(output, _("  \\dAp[+] [AMPTRN [OPFPTRN]] list procedures of operator family related to access method\n"));
 	fprintf(output, _("  \\db[+]  [PATTERN]      list tablespaces\n"));
 	fprintf(output, _("  \\dc[S+] [PATTERN]      list conversions\n"));
 	fprintf(output, _("  \\dC[+]  [PATTERN]      list casts\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b6b08d0ccb6..1bc66b7c30f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -510,6 +510,13 @@ static const SchemaQuery Query_for_list_of_partitioned_relations = {
 	.result = "pg_catalog.quote_ident(c.relname)",
 };
 
+static const SchemaQuery Query_for_list_of_operator_families = {
+	.catname = "pg_catalog.pg_opfamily c",
+	.viscondition = "pg_catalog.pg_opfamily_is_visible(c.oid)",
+	.namespace = "c.opfnamespace",
+	.result = "pg_catalog.quote_ident(c.opfname)",
+};
+
 /* Relations supporting INSERT, UPDATE or DELETE */
 static const SchemaQuery Query_for_list_of_updatables = {
 	.catname = "pg_catalog.pg_class c",
@@ -1462,7 +1469,8 @@ psql_completion(const char *text, int start, int end)
 		"\\a",
 		"\\connect", "\\conninfo", "\\C", "\\cd", "\\copy",
 		"\\copyright", "\\crosstabview",
-		"\\d", "\\da", "\\dA", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
+		"\\d", "\\da", "\\dA", "\\dAp", "\\dAo", "\\dAp", "\\dAc",
+		"\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt",
@@ -3698,6 +3706,12 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (TailMatchesCS("\\da*"))
 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+	else if (TailMatchesCS("\\dAp*", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_operator_families, NULL);
+	else if (TailMatchesCS("\\dAo*", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_operator_families, NULL);
+	else if (TailMatchesCS("\\dAc*", MatchAny))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
 	else if (TailMatchesCS("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (TailMatchesCS("\\db*"))
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 242f817163e..906341184bb 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4809,3 +4809,165 @@ Owning table: "pg_catalog.pg_statistic"
 Indexes:
     "pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
 
+-- check printing info about access methods
+\dA
+List of access methods
+  Name  | Type  
+--------+-------
+ brin   | Index
+ btree  | Index
+ gin    | Index
+ gist   | Index
+ hash   | Index
+ heap   | Table
+ heap2  | Table
+ spgist | Index
+(8 rows)
+
+\dA *
+List of access methods
+  Name  | Type  
+--------+-------
+ brin   | Index
+ btree  | Index
+ gin    | Index
+ gist   | Index
+ hash   | Index
+ heap   | Table
+ heap2  | Table
+ spgist | Index
+(8 rows)
+
+\dA h*
+List of access methods
+ Name  | Type  
+-------+-------
+ hash  | Index
+ heap  | Table
+ heap2 | Table
+(3 rows)
+
+\dA foo
+List of access methods
+ Name | Type 
+------+------
+(0 rows)
+
+\dA foo bar
+List of access methods
+ Name | Type 
+------+------
+(0 rows)
+
+\dA: extra argument "bar" ignored
+\dA+
+                             List of access methods
+  Name  | Type  |       Handler        |              Description               
+--------+-------+----------------------+----------------------------------------
+ brin   | Index | brinhandler          | block range index (BRIN) access method
+ btree  | Index | bthandler            | b-tree index access method
+ gin    | Index | ginhandler           | GIN index access method
+ gist   | Index | gisthandler          | GiST index access method
+ hash   | Index | hashhandler          | hash index access method
+ heap   | Table | heap_tableam_handler | heap table access method
+ heap2  | Table | heap_tableam_handler | 
+ spgist | Index | spghandler           | SP-GiST index access method
+(8 rows)
+
+\dA+ *
+                             List of access methods
+  Name  | Type  |       Handler        |              Description               
+--------+-------+----------------------+----------------------------------------
+ brin   | Index | brinhandler          | block range index (BRIN) access method
+ btree  | Index | bthandler            | b-tree index access method
+ gin    | Index | ginhandler           | GIN index access method
+ gist   | Index | gisthandler          | GiST index access method
+ hash   | Index | hashhandler          | hash index access method
+ heap   | Table | heap_tableam_handler | heap table access method
+ heap2  | Table | heap_tableam_handler | 
+ spgist | Index | spghandler           | SP-GiST index access method
+(8 rows)
+
+\dA+ h*
+                     List of access methods
+ Name  | Type  |       Handler        |       Description        
+-------+-------+----------------------+--------------------------
+ hash  | Index | hashhandler          | hash index access method
+ heap  | Table | heap_tableam_handler | heap table access method
+ heap2 | Table | heap_tableam_handler | 
+(3 rows)
+
+\dA+ foo
+       List of access methods
+ Name | Type | Handler | Description 
+------+------+---------+-------------
+(0 rows)
+
+\dAc brin pg*.oid*
+             Index access method operator classes
+  AM  | Input type | Storage type | Operator class | Default? 
+------+------------+--------------+----------------+----------
+ brin | oid        |              | oid_minmax_ops | yes
+(1 row)
+
+\dAf spgist
+    Index access method operator families
+   AM   | Operator family | Applicable types 
+--------+-----------------+------------------
+ spgist | box_ops         | box
+ spgist | kd_point_ops    | point
+ spgist | network_ops     | inet
+ spgist | poly_ops        | polygon
+ spgist | quad_point_ops  | point
+ spgist | range_ops       | anyrange
+ spgist | text_ops        | text
+(7 rows)
+
+\dAf btree int4
+        Index access method operator families
+  AM   | Operator family |     Applicable types      
+-------+-----------------+---------------------------
+ btree | integer_ops     | smallint, integer, bigint
+(1 row)
+
+\dAo brin uuid_minmax_ops
+List operators of family related to access method
+  AM  |  Opfamily Name  |    Operator     
+------+-----------------+-----------------
+ brin | uuid_minmax_ops | < (uuid, uuid)
+ brin | uuid_minmax_ops | <= (uuid, uuid)
+ brin | uuid_minmax_ops | = (uuid, uuid)
+ brin | uuid_minmax_ops | >= (uuid, uuid)
+ brin | uuid_minmax_ops | > (uuid, uuid)
+(5 rows)
+
+\dAo * pg_catalog.jsonb_path_ops
+List operators of family related to access method
+ AM  | Opfamily Name  |       Operator       
+-----+----------------+----------------------
+ gin | jsonb_path_ops | @> (jsonb, jsonb)
+ gin | jsonb_path_ops | @? (jsonb, jsonpath)
+ gin | jsonb_path_ops | @@ (jsonb, jsonpath)
+(3 rows)
+
+\dAp brin uuid_minmax_ops
+                            List of operator family procedures
+  AM  | Operator family | Left arg type | Right arg type | Number |       Proc name        
+------+-----------------+---------------+----------------+--------+------------------------
+ brin | uuid_minmax_ops | uuid          | uuid           |      1 | brin_minmax_opcinfo
+ brin | uuid_minmax_ops | uuid          | uuid           |      2 | brin_minmax_add_value
+ brin | uuid_minmax_ops | uuid          | uuid           |      3 | brin_minmax_consistent
+ brin | uuid_minmax_ops | uuid          | uuid           |      4 | brin_minmax_union
+(4 rows)
+
+\dAp * pg_catalog.uuid_ops
+                           List of operator family procedures
+  AM   | Operator family | Left arg type | Right arg type | Number |     Proc name      
+-------+-----------------+---------------+----------------+--------+--------------------
+ btree | uuid_ops        | uuid          | uuid           |      1 | uuid_cmp
+ btree | uuid_ops        | uuid          | uuid           |      2 | uuid_sortsupport
+ btree | uuid_ops        | uuid          | uuid           |      4 | btequalimage
+ hash  | uuid_ops        | uuid          | uuid           |      1 | uuid_hash
+ hash  | uuid_ops        | uuid          | uuid           |      2 | uuid_hash_extended
+(5 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 26a0bcf7181..3c876d26992 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1182,3 +1182,21 @@ drop role regress_partitioning_role;
 
 -- \d on toast table (use pg_statistic's toast table, which has a known name)
 \d pg_toast.pg_toast_2619
+
+-- check printing info about access methods
+\dA
+\dA *
+\dA h*
+\dA foo
+\dA foo bar
+\dA+
+\dA+ *
+\dA+ h*
+\dA+ foo
+\dAc brin pg*.oid*
+\dAf spgist
+\dAf btree int4
+\dAo brin uuid_minmax_ops
+\dAo * pg_catalog.jsonb_path_ops
+\dAp brin uuid_minmax_ops
+\dAp * pg_catalog.uuid_ops
-- 
2.14.3

