Taking into account the wishes of all the reviewers, the current
position of the patch is as follows:

The \dA command displays a list of access methods.

# \dA
        List of access methods
  Name  | Type  |       Handler        
--------+-------+----------------------
 brin   | index | brinhandler
 btree  | index | bthandler
 gin    | index | ginhandler
 gist   | index | gisthandler
 hash   | index | hashhandler
 heap   | table | heap_tableam_handler
 spgist | index | spghandler
(7 rows)

With + it shows description:
# \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
 spgist | index | spghandler           | SP-GiST index access method
(7 rows)

The functionality of the \dAp command has been moved to \dA NAME.
Now the user can query the properties of a particular AM (or several,
using the search pattern) as follows:

# \dA h*
                                                         Index access
method properties
  AM  | Can order | Support unique indexes | Support indexes with
multiple columns | Support exclusion constraints | Can include non-key
columns 
------+-----------+------------------------+---------------------------
------------+-------------------------------+------------------------
-----
 hash | no        | no                     |
no                                    | yes                           |
no
(1 row)

                 Table access method properties
 Name | Type  |       Handler        |       Description        
------+-------+----------------------+--------------------------
 heap | table | heap_tableam_handler | heap table access method
(1 row)

Note that for heap, as well as for future table AM, a separate table is
displayed, since it is not clear which properties can be displayed for
them.

The \dAoc command has been renamed to \dAc.
The command displays information about operator classes. The "Input
type" field was left, because the user may first be interested in what
type of data opclass can work with,
and in the second - how it will keep this type inside. Nikita also
chose to leave the opfamily field as additional information.

# \dAc btree name
             Index access method operator classes
  AM   | Input type | Storage type | Operator class | Default? 
-------+------------+--------------+----------------+----------
 btree | name       | cstring      | name_ops       | yes
(1 row)

# \dAc+ btree record
                            Index access method operator classes
  AM   | Input type | Storage type |  Operator class  | Default? |
Operator family  | Owner 
-------+------------+--------------+------------------+----------+-----
-------------+-------
 btree | record     |              | record_image_ops | no       |
record_image_ops | zloj
 btree | record     |              | record_ops       | yes      |
record_ops       | zloj
(2 rows)

The \dAfo command has been renamed to \dAo.
\dAo displays information about operators as follows:

# \dAo gin jsonb_ops
     List operators of family related to access method
 AM  | Opfamily Schema | Opfamily Name |      Operator      
-----+-----------------+---------------+--------------------
 gin | pg_catalog      | jsonb_ops     | @> (jsonb, jsonb)
 gin | pg_catalog      | jsonb_ops     | ? (jsonb, text)
 gin | pg_catalog      | jsonb_ops     | ?| (jsonb, text[])
 gin | pg_catalog      | jsonb_ops     | ?& (jsonb, text[])
(4 rows)

# \dAo+ gist circle_ops
                         List operators of family related to access
method
  AM  | Opfamily Schema | Opfamily Name |       Operator       |
Strategy | Purpose  | Sort family 
------+-----------------+---------------+----------------------+-------
---+----------+-------------
 gist | pg_catalog      | circle_ops    | << (circle,
circle)  |        1 | search   | 
 ... 
 gist | pg_catalog      | circle_ops    | <-> (circle,
point)  |       15 | ordering | float_ops

The \dAop command has been renamed to \dAp.
It displays list of support procedures associated with access method
operator families.
# \dAp hash array_ops 
                List of operator family procedures
  AM  | Family schema | Family name |   Left   |  Right   | Number 
------+---------------+-------------+----------+----------+--------
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      1
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      2
(2 rows)

# \dAp+ hash array_ops 
                           List of operator family procedures
  AM  | Family schema | Family name |   Left   |  Right   | Number
|      Proc name      
------+---------------+-------------+----------+----------+--------+---
------------------
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      1 |
hash_array
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      2 |
hash_array_extended
(2 rows)

It may be easier for the user to navigate in this list if the defining
feature in addition to the number is also the procedure name.
Even if it does not carry important information, it improves the
readability of the list. Maybe it makes sense to return field "Proc
name" to the main output?


0002-psql_add_index_info-v5.patch

The commands \dip and \dicp have so far been left in the current form,
because although they display properties common to the whole AM,
as Nikita already wrote, this properties can be redefined.

# \dip pg_am_oid_index
                                           Index properties
   Schema   |      Name       | Access method | Clusterable | Index
scan | Bitmap scan | Backward scan 
------------+-----------------+---------------+-------------+--------
----+-------------+---------------
 pg_catalog | pg_am_oid_index | btree         | yes         |
yes        | yes         | yes
(1 row)

# \dicp pg_amop_opr_fam_index
                                                Index
pg_catalog.pg_amop_opr_fam_index
 Column name |    Expr     | Opclass  | ASC | Nulls first | Orderable |
Distance orderable | Returnable | Search array | Search nulls 
-------------+-------------+----------+-----+-------------+-----------
+--------------------+------------+--------------+--------------
 amopopr     | amopopr     | oid_ops  | yes | no          | yes       |
no                 | yes        | yes          | yes
 amoppurpose | amoppurpose | char_ops | yes | no          | yes       |
no                 | yes        | yes          | yes
 amopfamily  | amopfamily  | oid_ops  | yes | no          | yes       |
no                 | yes        | yes          | yes
Table: pg_amop
Access method: btree

Also please look through the documentation for these features. I am
sure that the information specified there can be submitted in a more
accurate and convenient form.

P.S. Since the formatting of the letter can brake the form of the
tables, I attach a text file with the same content so that you do not
have to do too much copy/paste to see original view =)

Sincerely
Sergey Cherkashin.

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0fd792ff1a..6105680be6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -675,7 +675,7 @@
    search and ordering purposes.)
   </para>
 
-  <table>
+  <table id="catalog-pg-amop-table">
    <title><structname>pg_amop</structname> Columns</title>
 
    <tgroup cols="4">
@@ -818,7 +818,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">
@@ -4438,7 +4438,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">
@@ -4700,7 +4700,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 1b5d82ed8e..da85f63400 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1219,11 +1219,82 @@ testdb=&gt;
 
         <listitem>
         <para>
-        Lists access methods. If <replaceable
-        class="parameter">pattern</replaceable> is specified, only access
-        methods whose names match the pattern are shown. If
-        <literal>+</literal> is appended to the command name, each access
-        method is listed with its associated handler function and description.
+        Lists access methods with their associated handler function. If
+        <literal>+</literal> is appended to the command name, additional
+        description is provided.
+        If <replaceable class="parameter">pattern</replaceable> is specified,
+        the command displays the properties of the access methods whose names
+        match the search pattern.
+        </para>
+        </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 families 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>\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>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index ab259c473a..7b57c7cb49 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -719,7 +719,25 @@ 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)
+						pattern2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
+
+					if (strncmp(cmd, "dAo", 3) == 0)
+						success = listFamilyClassOperators(pattern, pattern2, show_verbose);
+					else if (strncmp(cmd, "dAp", 3) == 0)
+						success = listOperatorFamilyProcedures(pattern, pattern2, show_verbose);
+					else if (strncmp(cmd, "dAc", 3) == 0)
+						success = describeAccessMethodOperatorClasses(pattern, pattern2, show_verbose);
+					else if (pattern)
+						success = describeAccessMethodProperties(pattern);
+					else
+						success = listAccessMethods(show_verbose);
+
+					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 779e48437c..e5535e619c 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"
@@ -146,7 +147,7 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
  * Takes an optional regexp to select particular access methods
  */
 bool
-describeAccessMethods(const char *pattern, bool verbose)
+listAccessMethods(bool verbose)
 {
 	PQExpBufferData buf;
 	PGresult   *res;
@@ -168,29 +169,23 @@ describeAccessMethods(const char *pattern, bool verbose)
 	printfPQExpBuffer(&buf,
 					  "SELECT amname AS \"%s\",\n"
 					  "  CASE amtype"
-					  " WHEN 'i' THEN '%s'"
-					  " END AS \"%s\"",
+					  "    WHEN 'i' THEN '%s'"
+					  "    WHEN 't' THEN '%s'"
+					  "  END AS \"%s\",\n"
+					  "  amhandler AS \"%s\"",
 					  gettext_noop("Name"),
-					  gettext_noop("Index"),
-					  gettext_noop("Type"));
+					  gettext_noop("index"),
+					  gettext_noop("table"),
+					  gettext_noop("Type"),
+					  gettext_noop("Handler"));
 
 	if (verbose)
-	{
 		appendPQExpBuffer(&buf,
-						  ",\n  amhandler AS \"%s\",\n"
-						  "  pg_catalog.obj_description(oid, 'pg_am') AS \"%s\"",
-						  gettext_noop("Handler"),
+						  ",\n  pg_catalog.obj_description(oid, 'pg_am') AS \"%s\"",
 						  gettext_noop("Description"));
-	}
-
 	appendPQExpBufferStr(&buf,
-						 "\nFROM pg_catalog.pg_am\n");
-
-	processSQLNamePattern(pset.db, &buf, pattern, false, false,
-						  NULL, "amname", NULL,
-						  NULL);
-
-	appendPQExpBufferStr(&buf, "ORDER BY 1;");
+						 "\nFROM pg_catalog.pg_am\n"
+						 "ORDER BY 1;");
 
 	res = PSQLexec(buf.data);
 	termPQExpBuffer(&buf);
@@ -5625,3 +5620,382 @@ printACLColumn(PQExpBuffer buf, const char *colname)
 						  "pg_catalog.array_to_string(%s, '\\n') AS \"%s\"",
 						  colname, gettext_noop("Access privileges"));
 }
+
+/*
+ * \dA NAME
+ * Describes access method properties.
+ *
+ * Takes an optional regexp to select particular access methods
+ */
+bool
+describeAccessMethodProperties(const char *pattern)
+{
+	PQExpBufferData buf;
+	PGresult	   *res;
+	bool			found_result = false;
+	printQueryOpt myopt = pset.popt;
+	static const bool translate_columns_i[] = {true, true, true, true, true, true};
+	static const bool translate_columns_t[] = {true, true, true, true};
+
+	if (pset.sversion < 90600)
+	{
+		char		sverbuf[32];
+
+		psql_error("The server (version %s) does not support access methods.\n",
+				   formatPGVersionNumber(pset.sversion, false,
+										 sverbuf, sizeof(sverbuf)));
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  pset.sversion >= 90600 ?
+					  "SELECT a.amname AS \"%1$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_indexam_has_property(a.oid, 'can_order')\n"
+					  "    THEN '%2$s' ELSE '%3$s' END AS \"%4$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_indexam_has_property(a.oid, 'can_unique')\n"
+					  "    THEN '%2$s' ELSE '%3$s' END AS \"%5$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_indexam_has_property(a.oid, 'can_multi_col')\n"
+					  "    THEN '%2$s' ELSE '%3$s' END AS \"%6$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_indexam_has_property(a.oid, 'can_exclude')\n"
+					  "    THEN '%2$s' ELSE '%3$s' END AS \"%7$s\",\n"
+					  :
+					  "SELECT a.amname AS \"%1$s\",\n"
+					  "  CASE WHEN a.amcanorder THEN '%2$s' ELSE '%3$s' END AS \"%4$s\",\n"
+					  "  CASE WHEN a.amcanunique THEN '%2$s' ELSE '%3$s' END AS \"%5$s\",\n"
+					  "  CASE WHEN a.amcanmulticol THEN '%2$s' ELSE '%3$s' END AS \"%6$s\",\n"
+					  "  CASE WHEN a.amgettuple <> 0 THEN '%2$s' ELSE '%3$s' END AS \"%7$s\",\n",
+					  gettext_noop("AM"),
+					  gettext_noop("yes"),
+					  gettext_noop("no"),
+					  gettext_noop("Can order"),
+					  gettext_noop("Support unique indexes"),
+					  gettext_noop("Support indexes with multiple columns"),
+					  gettext_noop("Support exclusion constraints"));
+
+	appendPQExpBuffer(&buf,
+					  pset.sversion >= 110000
+						? "  CASE WHEN pg_catalog.pg_indexam_has_property(a.oid, 'can_include')\n"
+						  "    THEN '%1$s' ELSE '%2$s' END AS \"%3$s\""
+						: "  CASE WHEN false THEN '%1$s' ELSE '%2$s' END AS \"%3$s\"",
+					  gettext_noop("yes"), gettext_noop("no"),
+					  gettext_noop("Can include non-key columns"));
+
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_am a\n"
+						 "  WHERE a.amtype = 'i'\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  NULL, "amname", NULL, NULL);
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1;");
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("Index access method properties");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns_i;
+	myopt.n_translate_columns = lengthof(translate_columns_i);
+
+	if (PQntuples(res) > 0)
+	{
+		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+		found_result = true;
+	}
+
+	PQclear(res);
+
+	/* Table AM */
+	initPQExpBuffer(&buf);
+	printfPQExpBuffer(&buf,
+					  "SELECT a.amname AS \"%s\",\n"
+					  "  'table' AS \"%s\",\n"
+					  "  a.amhandler AS \"%s\",\n"
+					  "  pg_catalog.obj_description(a.oid, 'pg_am') AS \"%s\"\n"
+					  "FROM pg_catalog.pg_am a\n"
+					  "  WHERE a.amtype = 't'\n",
+					  gettext_noop("Name"),
+					  gettext_noop("Type"),
+					  gettext_noop("Handler"),
+					  gettext_noop("Description"));
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  NULL, "a.amname", NULL, NULL);
+	appendPQExpBufferStr(&buf, "ORDER BY 1;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("Table access method properties");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns_t;
+	myopt.n_translate_columns = lengthof(translate_columns_t);
+
+	if (PQntuples(res) > 0)
+	{
+		printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+		found_result = true;
+	}
+
+	PQclear(res);
+
+	if (!found_result)
+		psql_error("Did not find any AM named \"%s\".\n", pattern);
+
+	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"
+					  "  nsf.nspname AS \"%s\",\n"
+					  "  of.opfname 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 Schema"),
+					  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 family"));
+	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, 3, o.amopstrategy, 4;");
+
+	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, 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};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT DISTINCT\n"
+					  "  am.amname AS \"%s\",\n"
+					  "  ns.nspname AS \"%s\",\n"
+					  "  of.opfname 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",
+					  gettext_noop("AM"),
+					  gettext_noop("Family schema"),
+					  gettext_noop("Family name"),
+					  gettext_noop("Left"),
+					  gettext_noop("Right"),
+					  gettext_noop("Number"));
+	if (verbose)
+		appendPQExpBuffer(&buf,
+					  ", ap.amproc::pg_catalog.regproc::pg_catalog.text AS \"%s\"\n",
+					  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");
+
+	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, 6;");
+
+	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;
+}
+
+/*
+ * \dAc
+ * List index access method operator classes.
+ * Takes an optional regexp to select particular access method and operator class.
+ */
+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;
+}
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 4ff1f91f38..6cce80f260 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -13,7 +13,7 @@
 extern bool describeAggregates(const char *pattern, bool verbose, bool showSystem);
 
 /* \dA */
-extern bool describeAccessMethods(const char *pattern, bool verbose);
+extern bool listAccessMethods(bool verbose);
 
 /* \db */
 extern bool describeTablespaces(const char *pattern, bool verbose);
@@ -111,4 +111,21 @@ bool		describePublications(const char *pattern);
 /* \dRs */
 bool		describeSubscriptions(const char *pattern, bool verbose);
 
+/* \dA foo*/
+extern bool describeAccessMethodProperties(const char *pattern);
+
+/* \dAp */
+extern bool listOperatorFamilyProcedures(const char *access_method_pattern,
+										 const char *family_pattern,
+										 bool verbose);
+
+/* \dAo */
+extern bool listFamilyClassOperators(const char *accessMethod_pattern,
+									 const char *family_pattern, bool verbose);
+
+/* \dAc */
+extern bool describeAccessMethodOperatorClasses(const char *access_method_pattern,
+												const char *opclass_pattern,
+												bool verbose);
+
 #endif							/* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 6fc4ebab1e..a8d2b53b62 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -224,7 +224,11 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\d[S+]                 list tables, views, and sequences\n"));
 	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, _("  \\dA[+]                 list access methods\n"));
+	fprintf(output, _("  \\dA     NAME           describe properties of access method\n"));
+	fprintf(output, _("  \\dAc[+] [AMPTRN [TYPEPTRN]] list operator classes 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 10ae21cc61..3a280ccd94 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -472,6 +472,23 @@ static const SchemaQuery Query_for_list_of_relations = {
 	.result = "pg_catalog.quote_ident(c.relname)",
 };
 
+static const SchemaQuery Query_for_list_of_operator_families = {
+	/* min_server_version */
+	0,
+	/* catname */
+	"pg_catalog.pg_opfamily c",
+	/* selcondition */
+	NULL,
+	/* viscondition */
+	"true",
+	/* namespace */
+	"c.opfnamespace",
+	/* result */
+	"pg_catalog.quote_ident(c.opfname)",
+	/* qualresult */
+	NULL
+};
+
 /* Relations supporting INSERT, UPDATE or DELETE */
 static const SchemaQuery Query_for_list_of_updatables = {
 	.catname = "pg_catalog.pg_class c",
@@ -1378,7 +1395,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",
@@ -3476,6 +3494,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 aa101de906..78961ef2ee 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4578,3 +4578,56 @@ last error message: division by zero
 \echo 'last error code:' :LAST_ERROR_SQLSTATE
 last error code: 22012
 \unset FETCH_COUNT
+-- check printing info about access methods
+\dA gin
+                                                         Index access method properties
+ AM  | Can order | Support unique indexes | Support indexes with multiple columns | Support exclusion constraints | Can include non-key columns 
+-----+-----------+------------------------+---------------------------------------+-------------------------------+-----------------------------
+ gin | no        | no                     | yes                                   | no                            | no
+(1 row)
+
+\dAo brin uuid_minmax_ops
+     List operators of family related to access method
+  AM  | Opfamily Schema |  Opfamily Name  |    Operator     
+------+-----------------+-----------------+-----------------
+ brin | pg_catalog      | uuid_minmax_ops | < (uuid, uuid)
+ brin | pg_catalog      | uuid_minmax_ops | <= (uuid, uuid)
+ brin | pg_catalog      | uuid_minmax_ops | = (uuid, uuid)
+ brin | pg_catalog      | uuid_minmax_ops | >= (uuid, uuid)
+ brin | pg_catalog      | uuid_minmax_ops | > (uuid, uuid)
+(5 rows)
+
+\dAo * pg_catalog.jsonb_path_ops
+     List operators of family related to access method
+ AM  | Opfamily Schema | Opfamily Name  |     Operator      
+-----+-----------------+----------------+-------------------
+ gin | pg_catalog      | jsonb_path_ops | @> (jsonb, jsonb)
+(1 row)
+
+\dAp brin uuid_minmax_ops
+               List of operator family procedures
+  AM  | Family schema |   Family name   | Left | Right | Number 
+------+---------------+-----------------+------+-------+--------
+ brin | pg_catalog    | uuid_minmax_ops | uuid | uuid  |      1
+ brin | pg_catalog    | uuid_minmax_ops | uuid | uuid  |      2
+ brin | pg_catalog    | uuid_minmax_ops | uuid | uuid  |      3
+ brin | pg_catalog    | uuid_minmax_ops | uuid | uuid  |      4
+(4 rows)
+
+\dAp * pg_catalog.uuid_ops
+             List of operator family procedures
+  AM   | Family schema | Family name | Left | Right | Number 
+-------+---------------+-------------+------+-------+--------
+ btree | pg_catalog    | uuid_ops    | uuid | uuid  |      1
+ btree | pg_catalog    | uuid_ops    | uuid | uuid  |      2
+ hash  | pg_catalog    | uuid_ops    | uuid | uuid  |      1
+ hash  | pg_catalog    | uuid_ops    | uuid | uuid  |      2
+(4 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)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index fb7d17fc76..74cc7142da 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1031,3 +1031,11 @@ select 1/(15-unique2) from tenk1 order by unique2 limit 19;
 \echo 'last error code:' :LAST_ERROR_SQLSTATE
 
 \unset FETCH_COUNT
+
+-- check printing info about access methods
+\dA gin
+\dAo brin uuid_minmax_ops
+\dAo * pg_catalog.jsonb_path_ops
+\dAp brin uuid_minmax_ops
+\dAp * pg_catalog.uuid_ops
+\dAc brin pg*.oid*
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index da85f63400..16d45d5c49 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1446,6 +1446,34 @@ testdb=&gt;
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\dip [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+
+        <listitem>
+        <para>
+        Shows index properties listed in
+        <xref linkend="functions-info-index-props"/>.
+        If <replaceable class="parameter">pattern</replaceable> is
+        specified, only access methods whose names match the pattern are shown.
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <literal>\dicp [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link>]
+          </literal>
+        </term>
+
+        <listitem>
+        <para>
+        Shows index column properties listed in
+        <xref linkend="functions-info-index-column-props"/>.
+        If <replaceable class="parameter">pattern</replaceable> is
+        specified, only access methods whose names match the pattern are shown.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\des[+] [ <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 7b57c7cb49..0ab4a4c2ab 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -807,6 +807,16 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 			case 'v':
 			case 'm':
 			case 'i':
+				if (strncmp(cmd, "dip", 3) == 0)
+				{
+					success = describeIndexProperties(pattern, show_system);
+					break;
+				}
+				else if (strncmp(cmd, "dicp", 4) == 0)
+				{
+					success = describeIndexColumnProperties(pattern, show_system);
+					break;
+				}
 			case 's':
 			case 'E':
 				success = listTables(&cmd[1], pattern, show_verbose, show_system);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e5535e619c..2e61c0f9ff 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_cast_d.h"
 #include "catalog/pg_class_d.h"
 #include "catalog/pg_default_acl_d.h"
+#include "catalog/pg_index.h"
 #include "fe_utils/string_utils.h"
 
 #include "common.h"
@@ -45,6 +46,9 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
 					const char *pnspname, const char *prsname);
 static void printACLColumn(PQExpBuffer buf, const char *colname);
 static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool describeOneIndexColumnProperties(const char *oid, const char *nspname,
+								 const char *idxname, const char *amname,
+								 const char *tabname);
 
 
 /*----------------
@@ -5999,3 +6003,287 @@ describeAccessMethodOperatorClasses(const char *access_method_pattern,
 	PQclear(res);
 	return true;
 }
+
+/*
+ * \dip
+ * Describes index properties.
+ *
+ * Takes an optional regexp to select particular index.
+ */
+bool
+describeIndexProperties(const char *pattern, bool showSystem)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	static const bool translate_columns[] = {false, false, false, false, false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT"
+					  "  n.nspname AS \"%s\",\n"
+					  "  c.relname AS \"%s\",\n"
+					  "  am.amname AS \"%s\",\n",
+					  gettext_noop("Schema"),
+					  gettext_noop("Name"),
+					  gettext_noop("Access method"));
+	appendPQExpBuffer(&buf,
+					pset.sversion >= 90600 ?
+					  "  CASE WHEN pg_catalog.pg_index_has_property(c.oid, 'clusterable')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%3$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_has_property(c.oid, 'index_scan')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%4$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_has_property(c.oid, 'bitmap_scan')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%5$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_has_property(c.oid, 'backward_scan')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%6$s\"\n"
+					  :
+					  "  CASE WHEN am.amclusterable THEN '%1$s' ELSE '%2$s' END AS \"%3$s\",\n"
+					  "  CASE WHEN am.amgettuple <> 0 THEN '%1$s' ELSE '%2$s' END AS \"%4$s\",\n"
+					  "  CASE WHEN am.amgetbitmap <> 0 THEN '%1$s' ELSE '%2$s' END AS \"%5$s\",\n"
+					  "  CASE WHEN am.amcanbackward THEN '%1$s' ELSE '%2$s' END AS \"%6$s\"\n",
+					  gettext_noop("yes"),
+					  gettext_noop("no"),
+					  gettext_noop("Clusterable"),
+					  gettext_noop("Index scan"),
+					  gettext_noop("Bitmap scan"),
+					  gettext_noop("Backward scan"));
+	appendPQExpBufferStr(&buf,
+					  "FROM pg_catalog.pg_class c\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
+					  "  LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\n"
+					  "WHERE c.relkind='i'\n"
+					  "  AND n.nspname !~ 'pg_toast'\n");
+
+	if (!showSystem && !pattern)
+		appendPQExpBufferStr(&buf,
+							 "  AND n.nspname <> 'pg_catalog'\n"
+							 "  AND n.nspname <> 'information_schema'\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  "n.nspname", "c.relname", NULL, NULL);
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1;");
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("Index properties");
+	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;
+}
+
+/*
+ * \dicp
+ * Describes index index column properties.
+ *
+ * Takes an optional regexp to select particular index.
+ */
+bool
+describeIndexColumnProperties(const char *index_pattern, bool showSystem)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	int			i;
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT DISTINCT c.oid,\n"
+					  "  n.nspname,\n"
+					  "  c.relname,\n"
+					  "  am.amname,\n"
+					  "  c2.relname\n"
+					  "FROM pg_catalog.pg_class c\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = relnamespace\n"
+					  "  LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\n"
+					  "  LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid\n"
+					  "  LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid\n");
+
+	appendPQExpBufferStr(&buf, "WHERE c.relkind='i'\n");
+
+	if (!showSystem && !index_pattern)
+		appendPQExpBufferStr(&buf, "AND n.nspname <> 'pg_catalog'\n"
+							 "AND n.nspname <> 'information_schema'\n");
+
+	processSQLNamePattern(pset.db, &buf, index_pattern, true, false,
+						  "n.nspname", "c.relname", NULL,
+						  "pg_catalog.pg_table_is_visible(c.oid)");
+
+	appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	if (PQntuples(res) == 0)
+	{
+		if (!pset.quiet)
+		{
+			if (index_pattern)
+				psql_error("Did not find any index named \"%s\"\n",
+						   index_pattern);
+			else
+				psql_error("Did not find any relations.\n");
+		}
+		PQclear(res);
+		return false;
+	}
+
+	for (i = 0; i < PQntuples(res); i++)
+	{
+		const char *oid = PQgetvalue(res, i, 0);
+		const char *nspname = PQgetvalue(res, i, 1);
+		const char *idxname = PQgetvalue(res, i, 2);
+		const char *amname = PQgetvalue(res, i, 3);
+		const char *tabname = PQgetvalue(res, i, 4);
+
+		if (!describeOneIndexColumnProperties(oid, nspname, idxname, amname,
+											  tabname))
+		{
+			PQclear(res);
+			return false;
+		}
+		if (cancel_pressed)
+		{
+			PQclear(res);
+			return false;
+		}
+	}
+
+	PQclear(res);
+	return true;
+}
+
+static bool
+describeOneIndexColumnProperties(const char *oid,
+								 const char *nspname,
+								 const char *idxname,
+								 const char *amname,
+								 const char *tabname)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	char	   *footers[3] = {NULL, NULL};
+	static const bool translate_columns[] = {false, false, false, false, false,
+											 false, false, false, false, false};
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+				  "SELECT\n"
+				  "  a.attname AS \"%s\",\n"
+				  "  pg_catalog.pg_get_indexdef(i.indexrelid, a.attnum, true) AS \"%s\",\n"
+				  "  CASE WHEN pg_catalog.pg_opclass_is_visible(o.oid) THEN '' ELSE n.nspname || '.' END || o.opcname AS \"%s\",\n",
+				  gettext_noop("Column name"),
+				  gettext_noop("Expr"),
+				  gettext_noop("Opclass"));
+
+	if (pset.sversion >= 90600)
+		appendPQExpBuffer(&buf,
+					  "  CASE\n"
+					  "    WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'orderable') = true \n"
+					  "    THEN CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'asc')\n"
+					  "         THEN '%1$s' ELSE '%2$s' END \n"
+					  "    ELSE NULL"
+					  "  END AS \"%3$s\","
+					  "  CASE\n"
+					  "    WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'orderable') = true \n"
+					  "    THEN CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'nulls_first')\n"
+					  "         THEN '%1$s' ELSE '%2$s' END \n"
+					  "    ELSE NULL"
+					  "  END AS \"%4$s\","
+					  "  CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'orderable')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%5$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'distance_orderable')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%6$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'returnable')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%7$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'search_array')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%8$s\",\n"
+					  "  CASE WHEN pg_catalog.pg_index_column_has_property(c.oid, a.attnum, 'search_nulls')\n"
+					  "    THEN '%1$s' ELSE '%2$s' END AS \"%9$s\"\n",
+					  gettext_noop("yes"),
+					  gettext_noop("no"),
+					  gettext_noop("ASC"),
+					  gettext_noop("Nulls first"),
+					  gettext_noop("Orderable"),
+					  gettext_noop("Distance orderable"),
+					  gettext_noop("Returnable"),
+					  gettext_noop("Search array"),
+					  gettext_noop("Search nulls"));
+	else
+		appendPQExpBuffer(&buf,
+					  "  CASE WHEN am.amcanorder THEN CASE\n"
+					  "    WHEN (i.indoption[a.attnum - 1] & %1$d) =  0\n" /* INDOPTION_DESC */
+					  "    THEN '%2$s' ELSE '%3$s' END\n"
+					  "  ELSE NULL END AS \"%4$s\",\n"
+					  "  CASE WHEN am.amcanorder THEN CASE\n"
+					  "    WHEN (i.indoption[a.attnum - 1] & %5$d) <> 0\n" /* INDOPTION_NULLS_FIRST */
+					  "    THEN '%2$s' ELSE '%3$s' END\n"
+					  "  ELSE NULL END AS \"%6$s\",\n"
+					  "  CASE WHEN am.amcanorder THEN '%2$s' ELSE '%3$s' END AS \"%7$s\",\n"
+					  "  CASE WHEN am.amcanorderbyop THEN '%2$s' ELSE '%3$s' END AS \"%8$s\",\n"
+					  "  CASE WHEN am.amsearcharray THEN '%2$s' ELSE '%3$s' END AS \"%9$s\",\n"
+					  "  CASE WHEN am.amsearchnulls THEN '%2$s' ELSE '%3$s' END AS \"%10$s\"\n",
+					  INDOPTION_DESC,
+					  gettext_noop("yes"),
+					  gettext_noop("no"),
+					  gettext_noop("ASC"),
+					  INDOPTION_NULLS_FIRST,
+					  gettext_noop("Nulls first"),
+					  gettext_noop("Orderable"),
+					  gettext_noop("Distance orderable"),
+					  gettext_noop("Search array"),
+					  gettext_noop("Search nulls"));
+
+	appendPQExpBuffer(&buf,
+					  "FROM pg_catalog.pg_class c\n"
+					  "  LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid\n"
+					  "  LEFT JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid\n"
+					  "  LEFT JOIN pg_catalog.pg_opclass o ON o.oid = (i.indclass::pg_catalog.oid[])[a.attnum - 1]\n"
+					  "  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.opcnamespace\n");
+	if (pset.sversion < 90600)
+		appendPQExpBuffer(&buf,
+					  "  LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam\n");
+	appendPQExpBuffer(&buf,
+					  "WHERE c.oid = %s\n"
+					  "ORDER BY a.attnum",
+					  oid);
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+	if (PQntuples(res) == 0)
+	{
+		PQclear(res);
+		return true;
+	}
+
+	myopt.nullPrint = NULL;
+	myopt.title = psprintf(_("Index %s.%s"), nspname, idxname);
+	footers[0] = psprintf(_("Table: %s"), tabname);
+	footers[1] = psprintf(_("Access method: %s"), amname);
+	myopt.footers = footers;
+	myopt.topt.default_footer = false;
+	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 6cce80f260..86ce18071b 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -128,4 +128,11 @@ extern bool describeAccessMethodOperatorClasses(const char *access_method_patter
 												const char *opclass_pattern,
 												bool verbose);
 
+/* \dip */
+extern bool describeIndexProperties(const char *pattern, bool showSystem);
+
+/* \dicp */
+extern bool describeIndexColumnProperties(const char *indexPattern,
+										  bool showSystem);
+
 #endif							/* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index a8d2b53b62..1c9a242e2e 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -247,6 +247,8 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dFt[+] [PATTERN]      list text search templates\n"));
 	fprintf(output, _("  \\dg[S+] [PATTERN]      list roles\n"));
 	fprintf(output, _("  \\di[S+] [PATTERN]      list indexes\n"));
+	fprintf(output, _("  \\dip[S] [PATTERN]      list indexes with properties\n"));
+	fprintf(output, _("  \\dicp[S][PATTERN]      show index column properties\n"));
 	fprintf(output, _("  \\dl                    list large objects, same as \\lo_list\n"));
 	fprintf(output, _("  \\dL[S+] [PATTERN]      list procedural languages\n"));
 	fprintf(output, _("  \\dm[S+] [PATTERN]      list materialized views\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3a280ccd94..2c6e47d0fd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1398,8 +1398,8 @@ psql_completion(const char *text, int start, int end)
 		"\\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",
+		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dicp", "\\dip",
+		"\\dl", "\\dL", "\\dm", "\\dn", "\\do", "\\dO", "\\dp",
 		"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
 		"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
 		"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 78961ef2ee..32731e9242 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4631,3 +4631,19 @@ last error code: 22012
  brin | oid        |              | oid_minmax_ops | yes
 (1 row)
 
+-- check printing info about indexes
+\dip pg_am_name_index
+                                            Index properties
+   Schema   |       Name       | Access method | Clusterable | Index scan | Bitmap scan | Backward scan 
+------------+------------------+---------------+-------------+------------+-------------+---------------
+ pg_catalog | pg_am_name_index | btree         | yes         | yes        | yes         | yes
+(1 row)
+
+\dicp pg_am_name_index
+                                                Index pg_catalog.pg_am_name_index
+ Column name |  Expr  | Opclass  | ASC | Nulls first | Orderable | Distance orderable | Returnable | Search array | Search nulls 
+-------------+--------+----------+-----+-------------+-----------+--------------------+------------+--------------+--------------
+ amname      | amname | name_ops | yes | no          | yes       | no                 | yes        | yes          | yes
+Table: pg_am
+Access method: btree
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 74cc7142da..0b7daa5d0e 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1039,3 +1039,6 @@ select 1/(15-unique2) from tenk1 order by unique2 limit 19;
 \dAp brin uuid_minmax_ops
 \dAp * pg_catalog.uuid_ops
 \dAc brin pg*.oid*
+-- check printing info about indexes
+\dip pg_am_name_index
+\dicp pg_am_name_index
Taking into account the wishes of all the reviewers, the current position of 
the patch is as follows:

The \dA command displays a list of access methods.

# \dA
        List of access methods
  Name  | Type  |       Handler        
--------+-------+----------------------
 brin   | index | brinhandler
 btree  | index | bthandler
 gin    | index | ginhandler
 gist   | index | gisthandler
 hash   | index | hashhandler
 heap   | table | heap_tableam_handler
 spgist | index | spghandler
(7 rows)

With + it shows description:
# \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
 spgist | index | spghandler           | SP-GiST index access method
(7 rows)

The functionality of the \dAp command has been moved to \dA NAME.
Now the user can query the properties of a particular AM (or several, using the 
search pattern) as follows:

# \dA h*
                                                         Index access method 
properties
  AM  | Can order | Support unique indexes | Support indexes with multiple 
columns | Support exclusion constraints | Can include non-key columns 
------+-----------+------------------------+---------------------------------------+-------------------------------+-----------------------------
 hash | no        | no                     | no                                 
   | yes                           | no
(1 row)

                 Table access method properties
 Name | Type  |       Handler        |       Description        
------+-------+----------------------+--------------------------
 heap | table | heap_tableam_handler | heap table access method
(1 row)

Note that for heap, as well as for future table AM, a separate table is 
displayed, since it is not clear which properties can be displayed for them.

The \dAoc command has been renamed to \dAc.
The command displays information about operator classes. The "Input type" field 
was left, because the user may first be interested in what type of data opclass 
can work with,
and in the second - how it will keep this type inside. Nikita also chose to 
leave the opfamily field as additional information.

# \dAc btree name
             Index access method operator classes
  AM   | Input type | Storage type | Operator class | Default? 
-------+------------+--------------+----------------+----------
 btree | name       | cstring      | name_ops       | yes
(1 row)

# \dAc+ btree record
                            Index access method operator classes
  AM   | Input type | Storage type |  Operator class  | Default? | Operator 
family  | Owner 
-------+------------+--------------+------------------+----------+------------------+-------
 btree | record     |              | record_image_ops | no       | 
record_image_ops | zloj
 btree | record     |              | record_ops       | yes      | record_ops   
    | zloj
(2 rows)

The \dAfo command has been renamed to \dAo.
\dAo displays information about operators as follows:

# \dAo gin jsonb_ops
     List operators of family related to access method
 AM  | Opfamily Schema | Opfamily Name |      Operator      
-----+-----------------+---------------+--------------------
 gin | pg_catalog      | jsonb_ops     | @> (jsonb, jsonb)
 gin | pg_catalog      | jsonb_ops     | ? (jsonb, text)
 gin | pg_catalog      | jsonb_ops     | ?| (jsonb, text[])
 gin | pg_catalog      | jsonb_ops     | ?& (jsonb, text[])
(4 rows)

# \dAo+ gist circle_ops
                         List operators of family related to access method
  AM  | Opfamily Schema | Opfamily Name |       Operator       | Strategy | 
Purpose  | Sort family 
------+-----------------+---------------+----------------------+----------+----------+-------------
 gist | pg_catalog      | circle_ops    | << (circle, circle)  |        1 | 
search   | 
 ... 
 gist | pg_catalog      | circle_ops    | <-> (circle, point)  |       15 | 
ordering | float_ops

The \dAop command has been renamed to \dAp.
It displays list of support procedures associated with access method operator 
families.
# \dAp hash array_ops 
                List of operator family procedures
  AM  | Family schema | Family name |   Left   |  Right   | Number 
------+---------------+-------------+----------+----------+--------
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      1
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      2
(2 rows)

# \dAp+ hash array_ops 
                           List of operator family procedures
  AM  | Family schema | Family name |   Left   |  Right   | Number |      Proc 
name      
------+---------------+-------------+----------+----------+--------+---------------------
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      1 | hash_array
 hash | pg_catalog    | array_ops   | anyarray | anyarray |      2 | 
hash_array_extended
(2 rows)

It may be easier for the user to navigate in this list if the defining feature 
in addition to the number is also the procedure name.
Even if it does not carry important information, it improves the readability of 
the list. Maybe it makes sense to return field "Proc name" to the main output?


0002-psql_add_index_info-v5.patch

The commands \dip and \dicp have so far been left in the current form, because 
although they display properties common to the whole AM,
as Nikita already wrote, this properties can be redefined.

# \dip pg_am_oid_index
                                           Index properties
   Schema   |      Name       | Access method | Clusterable | Index scan | 
Bitmap scan | Backward scan 
------------+-----------------+---------------+-------------+------------+-------------+---------------
 pg_catalog | pg_am_oid_index | btree         | yes         | yes        | yes  
       | yes
(1 row)

# \dicp pg_amop_opr_fam_index
                                                Index 
pg_catalog.pg_amop_opr_fam_index
 Column name |    Expr     | Opclass  | ASC | Nulls first | Orderable | 
Distance orderable | Returnable | Search array | Search nulls 
-------------+-------------+----------+-----+-------------+-----------+--------------------+------------+--------------+--------------
 amopopr     | amopopr     | oid_ops  | yes | no          | yes       | no      
           | yes        | yes          | yes
 amoppurpose | amoppurpose | char_ops | yes | no          | yes       | no      
           | yes        | yes          | yes
 amopfamily  | amopfamily  | oid_ops  | yes | no          | yes       | no      
           | yes        | yes          | yes
Table: pg_amop
Access method: btree

Also please look through the documentation for these features. I am sure that 
the information specified there can be submitted in a more accurate and 
convenient form.

Sincerely
Sergey Cherkashin.

Reply via email to