From 64809c9c42205b3bb9b1fc866402be03ee21ddea Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sun, 25 Jan 2026 00:33:27 +0530 Subject: [PATCH 1/4] fix: Relation information missing in wildcard describes --- pgspecial/dbcommands.py | 18 +++++++++++++++++- tests/test_specials.py | 16 +++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 24cc9f0..31c13f6 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -25,6 +25,19 @@ ], ) +RELKIND_TO_NAME = { + "r": "Table", + "p": "Partitioned table", + "v": "View", + "m": "Materialized view", + "i": "Index", + "I": "Partitioned index", + "S": "Sequence", + "f": "Foreign table", + "c": "Composite type", + "t": "TOAST table", +} + log = logging.getLogger(__name__) @@ -1805,7 +1818,10 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if verbose and tableinfo.reloptions: status.append(f"Options: {tableinfo.reloptions}\n") - return (None, cells, headers, "".join(status)) + relkind_name = RELKIND_TO_NAME.get(tableinfo.relkind, "Relation") + title = f'{relkind_name} "{schema_name}.{relation_name}"' + + return (title, cells, headers, "".join(status)) def sql_name_pattern(pattern): diff --git a/tests/test_specials.py b/tests/test_specials.py index c93583d..77ecd74 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -159,7 +159,7 @@ def test_slash_d_verbose(executor): @dbtest def test_slash_d_table_1(executor): results = executor(r"\d tbl1") - title = None + title = 'Table "public.tbl1"' rows = [ ["id1", "integer", " not null"], ["txt1", "text", " not null"], @@ -173,7 +173,7 @@ def test_slash_d_table_1(executor): @dbtest def test_slash_d_table_2(executor): results = executor(r"\d tbl2") - title = None + title = 'Table "public.tbl2"' rows = [ ["id2", "integer", " not null default nextval('tbl2_id2_seq'::regclass)"], ["txt2", "text", ""], @@ -200,10 +200,10 @@ def test_slash_d_test_generated_default(executor): @dbtest def test_slash_d_table_verbose_1(executor): - title = None headers = ["Column", "Type", "Modifiers", "Storage", "Stats target", "Description"] results = executor(r"\d+ tbl1") + title = 'Table "public.tbl1"' rows = [ ["id1", "integer", " not null", "plain", None, None], ["txt1", "text", " not null", "extended", None, None], @@ -213,6 +213,7 @@ def test_slash_d_table_verbose_1(executor): assert results == expected results = executor(r'\d+ "Inh1"') + title = 'Table "public.Inh1"' rows = [ ["id1", "integer", " not null", "plain", None, None], ["txt1", "text", " not null", "extended", None, None], @@ -225,10 +226,10 @@ def test_slash_d_table_verbose_1(executor): @dbtest def test_slash_d_table_verbose_2(executor): - title = None headers = ["Column", "Type", "Modifiers", "Storage", "Stats target", "Description"] results = executor(r"\d+ tbl2") + title = 'Table "public.tbl2"' rows = [ [ "id2", @@ -245,6 +246,7 @@ def test_slash_d_table_verbose_2(executor): assert results == expected results = executor(r"\d+ inh2") + title = 'Table "public.inh2"' rows = [ ["id1", "integer", " not null", "plain", None, None], ["txt1", "text", " not null", "extended", None, None], @@ -266,7 +268,7 @@ def test_slash_d_table_verbose_2(executor): @dbtest def test_slash_d_view_verbose(executor): - title = None + title = 'View "public.vw1"' headers = ["Column", "Type", "Modifiers", "Storage", "Description"] results = executor(r"\d+ vw1") @@ -283,7 +285,7 @@ def test_slash_d_view_verbose(executor): @dbtest def test_slash_d_table_with_exclusion(executor): results = executor(r"\d tbl3") - title = None + title = 'Table "public.tbl3"' rows = [["c3", "circle", ""]] headers = ["Column", "Type", "Modifiers"] status = 'Indexes:\n "tbl3_c3_excl" EXCLUDE USING gist (c3 WITH &&)\n' @@ -294,7 +296,7 @@ def test_slash_d_table_with_exclusion(executor): @dbtest def test_slash_d_table_2_in_schema(executor): results = executor(r"\d schema2.tbl2") - title = None + title = 'Table "schema2.tbl2"' rows = [ [ "id2", From 4ec0b15addfe17e63d8f1439cfb44a2ad7815378 Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sun, 1 Mar 2026 18:28:48 +0530 Subject: [PATCH 2/4] chore: add change to changelog --- changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.rst b/changelog.rst index 69a0072..bbdcfb0 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,4 +1,11 @@ +Unreleased +========== + +Bug fixes: +---------- +* Include relation type/name titles in `\d` and `\d+` describe output so wildcard describe results retain per-relation context. + 2.2.1 (2025-04-27) ================== From 0be9890165c025bcda423b26300444af167e3eee Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sun, 1 Mar 2026 19:01:06 +0530 Subject: [PATCH 3/4] refactor: make relkind to name mapping protected variable --- pgspecial/dbcommands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 31c13f6..7f02e6c 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -25,7 +25,7 @@ ], ) -RELKIND_TO_NAME = { +_RELKIND_TO_NAME = { "r": "Table", "p": "Partitioned table", "v": "View", @@ -1818,7 +1818,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if verbose and tableinfo.reloptions: status.append(f"Options: {tableinfo.reloptions}\n") - relkind_name = RELKIND_TO_NAME.get(tableinfo.relkind, "Relation") + relkind_name = _RELKIND_TO_NAME.get(tableinfo.relkind, "Relation") title = f'{relkind_name} "{schema_name}.{relation_name}"' return (title, cells, headers, "".join(status)) From 1d45e3f09c6fc474f3a73c073dcb4bc713faa36d Mon Sep 17 00:00:00 2001 From: Devadathan M B Date: Sun, 1 Mar 2026 19:30:06 +0530 Subject: [PATCH 4/4] feat: add tests --- tests/test_specials.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_specials.py b/tests/test_specials.py index 77ecd74..6ce5a03 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -184,6 +184,22 @@ def test_slash_d_table_2(executor): assert results == expected +@dbtest +def test_slash_d_wildcard(executor): + results = executor(r"\d tbl*") + # tbl* matches tbl1, tbl2, tbl2_id2_seq (sequence), tbl3, tbl3_c3_excl (index) + # executor flattens each block as [title, rows, headers, status] + # so titles appear at indices 0, 4, 8, ... + titles = results[0::4] + assert titles == [ + 'Table "public.tbl1"', + 'Table "public.tbl2"', + 'Sequence "public.tbl2_id2_seq"', + 'Table "public.tbl3"', + 'Index "public.tbl3_c3_excl"', + ] + + @dbtest def test_slash_d_test_generated_default(executor): results = executor(r"\d schema3.test_generated_default")