From 1263fe2f5698a5ba467ba1f4a1e6bce63269e120 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Sat, 4 Nov 2023 07:17:27 +0000 Subject: [PATCH 01/30] move_sql_to_external_file --- .gitignore | 3 +++ dbcommands.sql | 28 +++++++++++++++++++++++++++ pgspecial/dbcommands.py | 42 ++++++----------------------------------- requirements-dev.txt | 3 ++- tests/dbutils.py | 2 +- tests/test_specials.py | 33 ++++++++++++++++---------------- 6 files changed, 56 insertions(+), 55 deletions(-) create mode 100644 dbcommands.sql diff --git a/.gitignore b/.gitignore index bbf58a0..92278fa 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,7 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties +# Used by direnv plugin +.direnv +.envrc # Created by .ignore support plugin (hsz.mobi) diff --git a/dbcommands.sql b/dbcommands.sql new file mode 100644 index 0000000..38117fd --- /dev/null +++ b/dbcommands.sql @@ -0,0 +1,28 @@ +-- name: list_databases +SELECT d.datname as "Name", +pg_catalog.pg_get_userbyid(d.datdba) as "Owner", +pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding", +d.datcollate as "Collate", +d.datctype as "Ctype", +pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges" +FROM pg_catalog.pg_database d +WHERE d.datname ~ %s +ORDER BY 1 + +-- name: list_databases_verbose +SELECT d.datname as "Name", +pg_catalog.pg_get_userbyid(d.datdba) as "Owner", +pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding", +d.datcollate as "Collate", +d.datctype as "Ctype", +pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges", +CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') + THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname)) +ELSE 'No Access' +END as "Size", +t.spcname as "Tablespace", +pg_catalog.shobj_description(d.oid, 'pg_database') as "Description" +FROM pg_catalog.pg_database d +JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid +WHERE d.datname ~ %s +ORDER BY 1 diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 3ba67a0..729bf73 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -5,6 +5,9 @@ from collections import namedtuple from psycopg.sql import SQL +import aiosql + +queries = aiosql.from_path("dbcommands.sql", "psycopg2") from .main import special_command, RAW_QUERY @@ -30,45 +33,12 @@ @special_command("\\l", "\\l[+] [pattern]", "List databases.", aliases=("\\list",)) def list_databases(cur, pattern, verbose): - params = {} - query = SQL( - """SELECT d.datname as "Name", - pg_catalog.pg_get_userbyid(d.datdba) as "Owner", - pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding", - d.datcollate as "Collate", - d.datctype as "Ctype", - pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges" - {verbose_fields} - FROM pg_catalog.pg_database d - {verbose_tables} - {pattern_where} - ORDER BY 1""" - ) + pattern = pattern or ".*" if verbose: - params["verbose_fields"] = SQL( - ''', - CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') - THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname)) - ELSE 'No Access' - END as "Size", - t.spcname as "Tablespace", - pg_catalog.shobj_description(d.oid, 'pg_database') as "Description"''' - ) - params["verbose_tables"] = SQL( - """JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid""" - ) + cur.execute(queries.list_databases_verbose.sql, (pattern,)) else: - params["verbose_fields"] = SQL("") - params["verbose_tables"] = SQL("") + cur.execute(queries.list_databases.sql, (pattern,)) - if pattern: - _, schema = sql_name_pattern(pattern) - params["pattern_where"] = SQL("""WHERE d.datname ~ {}""").format(schema) - else: - params["pattern_where"] = SQL("") - formatted_query = query.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] diff --git a/requirements-dev.txt b/requirements-dev.txt index bd13eb4..e2497ec 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,4 +8,5 @@ black>=20.8b1 configobj>=5.0.6 twine==1.11.0 wheel==0.38.1 -build==1.0.3 \ No newline at end of file +build==1.0.3 +aiosql>=9.0 diff --git a/tests/dbutils.py b/tests/dbutils.py index 36951ac..1ddb0fa 100644 --- a/tests/dbutils.py +++ b/tests/dbutils.py @@ -11,7 +11,7 @@ POSTGRES_PORT = getenv("PGPORT", 5432) POSTGRES_PASSWORD = getenv("PGPASSWORD", "postgres") -TEST_DB_NAME = "_test_db" +TEST_DB_NAME = "_local_test_db" FOREIGN_TEST_DB_NAME = "_foreign_test_db" diff --git a/tests/test_specials.py b/tests/test_specials.py index 1c1aefe..8cd90c9 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -16,12 +16,15 @@ # instead as that matches the C library function LC_COLLATE = locale.setlocale(locale.LC_COLLATE, None) LC_CTYPE = locale.setlocale(locale.LC_CTYPE, None) +# TODO remove this line +LC_COLLATE = "en_US.UTF-8" +LC_CTYPE = "en_US.UTF-8" @dbtest def test_slash_l(executor): results = executor(r"\l") - row = ("_test_db", "postgres", "UTF8", LC_COLLATE, LC_CTYPE, None) + row = ("_local_test_db", "postgres", "UTF8", LC_COLLATE, LC_CTYPE, None) headers = ["Name", "Owner", "Encoding", "Collate", "Ctype", "Access privileges"] assert row in results[1] assert headers == results[2] @@ -29,8 +32,8 @@ def test_slash_l(executor): @dbtest def test_slash_l_pattern(executor): - results = executor(r"\l _test*") - row = [("_test_db", "postgres", "UTF8", LC_COLLATE, LC_CTYPE, None)] + results = executor(r"\l local_test*") + row = [("_local_test_db", "postgres", "UTF8", LC_COLLATE, LC_CTYPE, None)] headers = ["Name", "Owner", "Encoding", "Collate", "Ctype", "Access privileges"] assert row == results[1] assert headers == results[2] @@ -322,24 +325,20 @@ def test_slash_dn(executor): """List all schemas.""" results = executor(r"\dn") title = None - if SERVER_VERSION >= 150001: - rows = [ - ("public", "pg_database_owner"), - ("schema1", POSTGRES_USER), - ("schema2", POSTGRES_USER), - ("schema3", POSTGRES_USER), - ] - else: - rows = [ - ("public", POSTGRES_USER), - ("schema1", POSTGRES_USER), - ("schema2", POSTGRES_USER), - ("schema3", POSTGRES_USER), - ] + # TODO remove this line + SERVER_VERSION = 1 + owner = "pg_database_owner" if SERVER_VERSION >= 150001 else POSTGRES_USER + rows = [ + ("public", owner), + ("schema1", POSTGRES_USER), + ("schema2", POSTGRES_USER), + ("schema3", POSTGRES_USER), + ] headers = ["Name", "Owner"] status = "SELECT %s" % len(rows) expected = [title, rows, headers, status] + assert results == expected From 2007a204ceeb6e4b5d2a1cc31c1bbcbc257461f9 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Sat, 4 Nov 2023 23:26:21 +0000 Subject: [PATCH 02/30] refactor list privileges --- dbcommands.sql | 677 ++++++++++++++++++++++++++++++++++++++-- pgspecial/dbcommands.py | 175 ++--------- tests/test_specials.py | 44 +-- 3 files changed, 703 insertions(+), 193 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 38117fd..1feac8a 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1,28 +1,665 @@ +-- name: version +SELECT version() -- name: list_databases -SELECT d.datname as "Name", -pg_catalog.pg_get_userbyid(d.datdba) as "Owner", -pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding", -d.datcollate as "Collate", -d.datctype as "Ctype", -pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges" +SELECT d.datname AS "Name", + pg_catalog.pg_get_userbyid(d.datdba) AS "Owner", + pg_catalog.pg_encoding_to_char(d.encoding) AS "Encoding", + d.datcollate AS "Collate", + d.datctype AS "Ctype", + pg_catalog.array_to_string(d.datacl, e'\n') AS "Access privileges" FROM pg_catalog.pg_database d WHERE d.datname ~ %s ORDER BY 1 - -- name: list_databases_verbose -SELECT d.datname as "Name", -pg_catalog.pg_get_userbyid(d.datdba) as "Owner", -pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding", -d.datcollate as "Collate", -d.datctype as "Ctype", -pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges", -CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') - THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname)) -ELSE 'No Access' -END as "Size", -t.spcname as "Tablespace", -pg_catalog.shobj_description(d.oid, 'pg_database') as "Description" +SELECT d.datname AS "Name", + pg_catalog.pg_get_userbyid(d.datdba) AS "Owner", + pg_catalog.pg_encoding_to_char(d.encoding) AS "Encoding", + d.datcollate AS "Collate", + d.datctype AS "Ctype", + pg_catalog.array_to_string(d.datacl, e'\n') AS "Access privileges", + CASE + WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') + THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname)) + ELSE 'No Access' + END AS "Size", + t.spcname AS "Tablespace", + pg_catalog.shobj_description(d.oid, 'pg_database') AS "Description" FROM pg_catalog.pg_database d -JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid +JOIN pg_catalog.pg_tablespace t ON d.dattablespace = t.oid WHERE d.datname ~ %s ORDER BY 1 +-- name: list_roles_9_verbose +SELECT r.rolname, + r.rolsuper, + r.rolinherit, + r.rolcreaterole, + r.rolcreatedb, + r.rolcanlogin, + r.rolconnlimit, + r.rolvaliduntil, +ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) as memberof, +pg_catalog.shobj_description(r.oid, 'pg_authid') AS description, +r.rolreplication +FROM pg_catalog.pg_roles r +WHERE r.rolname ~ %s +ORDER BY 1 +-- name: list_roles_9 +SELECT r.rolname, + r.rolsuper, + r.rolinherit, + r.rolcreaterole, + r.rolcreatedb, + r.rolcanlogin, + r.rolconnlimit, + r.rolvaliduntil, +ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) as memberof, +r.rolreplication +FROM pg_catalog.pg_roles r +WHERE r.rolname ~ %s +ORDER BY 1 +-- name: list_roles +SELECT u.usename AS rolname, + u.usesuper AS rolsuper, + TRUE AS rolinherit, + FALSE AS rolcreaterole, + u.usecreatedb AS rolcreatedb, + TRUE AS rolcanlogin, + -1 AS rolconnlimit, + u.valuntil AS rolvaliduntil, + array(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = any(g.grolist)) AS memberof +FROM pg_catalog.pg_user u +-- name: list_schemas +-- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") +SELECT + n.nspname AS "name", + pg_catalog.pg_get_userbyid(n.nspowner) AS "owner", + pg_catalog.array_to_string(n.nspacl, E'\\n') AS "access_privileges", + pg_catalog.obj_description(n.oid, 'pg_namespace') AS "description" +FROM + pg_catalog.pg_namespace n +WHERE + n.nspname ~ :pattern +ORDER BY 1 + +-- name: list_privileges +-- docs: ("\\dp", "\\dp [pattern]", "List roles.", aliases=("\\z",)) +SELECT n.nspname AS "Schema", + c.relname AS "Name", + CASE c.relkind + WHEN 'r' THEN 'table' + WHEN 'v' THEN 'view' + WHEN 'p' THEN 'partitioned table' + WHEN 'm' THEN 'materialized view' + WHEN 'i' THEN 'index' + WHEN 'I' THEN 'partitioned index' + WHEN 'S' THEN 'sequence' + WHEN 's' THEN 'special' + WHEN 'f' THEN 'foreign table' + WHEN 't' THEN 'toast table' + WHEN 'c' THEN 'composite type' + END AS "Type", + pg_catalog.array_to_string(c.relacl, e'\n') AS "Access privileges", + pg_catalog.array_to_string(ARRAY + (SELECT attname || e':\n ' || pg_catalog.array_to_string(attacl, e'\n ') + FROM pg_catalog.pg_attribute a + WHERE attrelid = c.oid + AND NOT attisdropped + AND attacl IS NOT NULL), e'\n') AS "Column privileges", + pg_catalog.array_to_string(ARRAY + (SELECT polname || + CASE + WHEN NOT polpermissive THEN e' (RESTRICTIVE)' + ELSE '' + END || + CASE + WHEN polcmd != '*' THEN e' (' || polcmd::pg_catalog.text || e'):' + ELSE e':' + END || + CASE + WHEN polqual IS NOT NULL THEN e'\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid) + ELSE e'' + END || + CASE + WHEN polwithcheck IS NOT NULL THEN e'\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid) + ELSE e'' + END || + CASE + WHEN polroles <> '{0}' THEN e'\n to: ' || pg_catalog.array_to_string(ARRAY + (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = ANY (polroles) ORDER BY 1), e', ') + ELSE e'' + END + FROM pg_catalog.pg_policy pol + WHERE polrelid = c.oid), e'\n') AS "Policies" +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'p') + AND n.nspname !~ '^pg_' + AND CASE + WHEN NOT %s::bool THEN pg_catalog.pg_table_is_visible(c.oid) + ELSE (c.relname operator(pg_catalog.~) %s COLLATE pg_catalog.default + AND n.nspname operator(pg_catalog. ~) %s COLLATE pg_catalog.default) + END +ORDER BY 1, 2 + +-- name: list_default_privileges +-- docs: ("\\ddp", "\\ddp [pattern]", "Lists default access privilege settings.") +SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS "Owner", + n.nspname AS "Schema", + CASE d.defaclobjtype + WHEN 'r' THEN 'table' + WHEN 'S' THEN 'sequence' + WHEN 'f' THEN 'function' + WHEN 'T' THEN 'type' + WHEN 'n' THEN 'schema' + END as "Type", + pg_catalog.array_to_string(d.defaclacl, e'\n') AS "Access privileges" +FROM pg_catalog.pg_default_acl d +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace +WHERE (n.nspname OPERATOR(pg_catalog. ~) %s COLLATE pg_catalog.default + OR pg_catalog.pg_get_userbyid(d.defaclrole) OPERATOR(pg_catalog. ~) %s COLLATE pg_catalog.default) +ORDER BY 1, 2, 3 + +-- name: list_tablespaces +-- docs: ("\\db", "\\db[+] [pattern]", "List tablespaces.") +SELECT + n.spcname AS "name", + pg_catalog.pg_get_userbyid(n.spcowner) AS "owner", + CASE + WHEN (EXISTS ( SELECT * FROM pg_proc WHERE proname = 'pg_tablespace_location')) + THEN pg_catalog.pg_tablespace_location(n.oid) + ELSE 'Not supported' + END AS "location" +FROM + pg_catalog.pg_tablespace n +WHERE + n.spcname ~ :pattern +ORDER BY 1 + +-- name: list_extensions +SELECT + e.extname AS "name", + e.extversion AS "version", + n.nspname AS "schema", + c.description AS "description" +FROM + pg_catalog.pg_extension e + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace + LEFT JOIN pg_catalog.pg_description c ON c.objoid = e.oid + AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass +WHERE + e.extname ~ :pattern +ORDER BY 1, 2 + +-- name: list_extensions_verbose +SELECT + e.extname AS "name", + pg_catalog.pg_describe_object(classid, objid, 0) AS "object_description" +FROM + pg_catalog.pg_depend d + LEFT OUTER JOIN pg_catalog.pg_extension e ON e.oid = refobjid +WHERE + refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass + AND deptype = 'e' + AND e.extname ~ :pattern +ORDER BY 1 + +-- name: list_objects +-- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes +SELECT + n.nspname AS "schema", + c.relname AS "name", + :relkind AS "type", + pg_catalog.pg_get_userbyid(c.relowner) AS "owner", + pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "size", + pg_catalog.obj_description(c.oid, 'pg_class') as "description" +FROM + pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE + c.relkind IN (:relkinds) +AND n.nspname ~ :schema_pattern + AND n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND n.nspname !~ '^pg_toast' + AND c.relname ~ :pattern +ORDER BY 1, 2 + +-- name: list_functions +-- docs: ("\\df", "\\df[+] [pattern]", "List functions.") +SELECT + n.nspname AS "schema", + p.proname AS "name", + pg_catalog.pg_get_function_result(p.oid) AS "result_data_type", + pg_catalog.pg_get_function_arguments(p.oid) AS "argument_data_types", + CASE + WHEN p.prokind = 'a' THEN 'agg' + WHEN p.prokind = 'w' THEN 'window' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "type", + :provolatile AS "volatility", + pg_catalog.pg_get_userbyid(p.proowner) AS "owner", + l.lanname AS "language", + p.prosrc AS "source_code", + pg_catalog.obj_description(p.oid, 'pg_proc') AS "description" +FROM + pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang +WHERE + n.nspname ~ :schema_pattern + AND p.proname ~ :pattern +ORDER BY 1, 2, 4 + +-- name: list_datatypes +-- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") +SELECT + n.nspname AS "schema", + pg_catalog.format_type(t.oid, NULL) AS "name", + t.typname AS "internal_name", + CASE + WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) + WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) + ELSE CAST(t.typlen AS pg_catalog.text) + END AS "size", + pg_catalog.array_to_string(ARRAY ( + SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE + e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS "elements", + pg_catalog.array_to_string(t.typacl, E'\n') AS "access_privileges", + pg_catalog.obj_description(t.oid, 'pg_type') AS "description" +FROM + pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace +WHERE (t.typrelid = 0 + OR ( + SELECT + c.relkind = 'c' + FROM + pg_catalog.pg_class c + WHERE + c.oid = t.typrelid)) + AND n.nspname ~ :schema_pattern + AND (t.typname ~ :pattern OR pg_catalog.format_type(t.oid, NULL) ~ :pattern) +ORDER BY 1, 2 + +-- name: list_domains +-- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") +SELECT + n.nspname AS "schema", + t.typname AS "name", + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "type", + pg_catalog.ltrim((COALESCE(( + SELECT + (' collate ' || c.collname) + FROM pg_catalog.pg_collation AS c, pg_catalog.pg_type AS bt + WHERE + c.oid = t.typcollation + AND bt.oid = t.typbasetype + AND t.typcollation <> bt.typcollation), '') || + CASE WHEN t.typnotnull THEN ' not null' ELSE '' END) || + CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS "modifier", + pg_catalog.array_to_string(ARRAY ( + SELECT + pg_catalog.pg_get_constraintdef(r.oid, TRUE) + FROM pg_catalog.pg_constraint AS r + WHERE t.oid = r.contypid), ' ') AS "check", + pg_catalog.array_to_string(t.typacl, E'\n') AS "access_privileges", + d.description AS "description" +FROM + pg_catalog.pg_type AS t + LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace + LEFT JOIN pg_catalog.pg_description d ON d.classoid = t.tableoid + AND d.objoid = t.oid + AND d.objsubid = 0 +WHERE + t.typtype = 'd' + AND n.nspname ~ :schema_pattern + AND t.typname ~ :pattern +ORDER BY 1, 2 +-- name: describe_table_details +-- docs: ( "\\d", "\\d[+] [pattern]", "List or describe tables, views and sequences.") +SELECT + c.oid, + n.nspname, + c.relname +FROM + pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE + n.nspname ~ :schema_pattern +-- AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname OPERATOR (pg_catalog. ~) :pattern +ORDER BY 2, 3 + +-- name: describe_one_table_details +SELECT + c.relchecks, + c.relhasindex, + c.relhasrules, + c.relhastriggers, + pg_catalog.array_to_string(c.reloptions || ARRAY ( + SELECT 'toast.' || x FROM pg_catalog.unnest(tc.reloptions) x), ', ') as reloptions, + c.reltablespace, + :reloftype as reloftype, + :relkind as relkind, + :relpersistence as relpersistence, + c.relispartition +FROM + pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE + c.oid = :oid + +-- name: get_column_info +SELECT + a.attname AS "name", + a.attnotnull AS "not_null", + a.attidentity AS "identity", + a.attgenerated AS "generated", + pg_catalog.format_type(a.atttypid, a.atttypmod) AS "data_type", + pg_catalog.col_description(a.attrelid, a.attnum) AS "description", + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS "index_definition", + pg_catalog.pg_get_viewdef(:oid::pg_catalog.oid, TRUE) AS "view_definition", + ( + SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) FOR 128) + FROM + pg_catalog.pg_attrdef d + WHERE + d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS "default_value", + ( + SELECT + c.collname + FROM + pg_catalog.pg_collation c, + pg_catalog.pg_type t + WHERE + c.oid = a.attcollation + AND t.oid = a.atttypid + AND a.attcollation <> t.typcollation) AS "collation", + CASE + WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i WHERE i.indexrelid = :oid) THEN 'yes' + ELSE 'no' + END AS "is_key", + CASE + WHEN a.attstattarget = - 1 THEN NULL + ELSE a.attstattarget + END AS "stat_target", + CASE + WHEN attfdwoptions IS NULL THEN '' + ELSE '(' || array_to_string(ARRAY + (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) FROM pg_options_to_table(attfdwoptions)), ', ') || ')' + END AS "fdw_options" +FROM + pg_catalog.pg_attribute a +WHERE + a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum + +-- name: get_view_info +SELECT pg_catalog.pg_get_viewdef(:oid::pg_catalog.oid, true) as viewdef + +-- name: get_index_info +SELECT + i.indisunique, + i.indisprimary, + i.indisclustered, + i.indisvalid, + (NOT i.indimmediate) + AND EXISTS ( SELECT 1 FROM pg_catalog.pg_constraint + WHERE + conrelid = i.indrelid + AND conindid = i.indexrelid + AND contype IN ('p', 'u', 'x') + AND condeferrable) AS condeferrable, + (NOT i.indimmediate) + AND EXISTS ( SELECT 1 FROM pg_catalog.pg_constraint + WHERE + conrelid = i.indrelid + AND conindid = i.indexrelid + AND contype IN ('p', 'u', 'x') + AND condeferred) AS condeferred, + a.amname, + c2.relname, + pg_catalog.pg_get_expr(i.indpred, i.indrelid, TRUE) +FROM + pg_catalog.pg_index i, + pg_catalog.pg_class c, + pg_catalog.pg_class c2, + pg_catalog.pg_am a +WHERE + i.indexrelid = c.oid + AND c.oid = :oid + AND c.relam = a.oid + AND i.indrelid = c2.oid + +-- name: get_sequence_info +SELECT + pg_catalog.quote_ident(nspname) || '.' || + pg_catalog.quote_ident(relname) || '.' || + pg_catalog.quote_ident(attname) AS "column" +FROM + pg_catalog.pg_class c + INNER JOIN pg_catalog.pg_depend d ON c.oid = d.refobjid + INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND a.attnum = d.refobjsubid) +WHERE + d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass + AND d.refclassid = 'pg_catalog.pg_class'::pg_catalog.regclass + AND d.objid = :oid + AND d.deptype = 'a' + +-- name: get_table_index_info +SELECT + c2.relname, + i.indisprimary, + i.indisunique, + i.indisclustered, + i.indisvalid, + pg_catalog.pg_get_indexdef(i.indexrelid, 0, TRUE), + pg_catalog.pg_get_constraintdef(con.oid, TRUE), + :contype as contype, + condeferrable, + condeferred, + c2.reltablespace +FROM + pg_catalog.pg_class c, + pg_catalog.pg_class c2, + pg_catalog.pg_index i + LEFT JOIN pg_catalog.pg_constraint con ON conrelid = i.indrelid + AND conindid = i.indexrelid + AND contype IN ('p', 'u', 'x') +WHERE + c.oid = :oid + AND c.oid = i.indrelid + AND i.indexrelid = c2.oid +ORDER BY + i.indisprimary DESC, + i.indisunique DESC, + c2.relname + +-- name: get_table_constraints +SELECT + conrelid::pg_catalog.regclass, + con.conname, + :contype as contype, + pg_catalog.pg_get_constraintdef(con.oid, TRUE) AS condef +FROM + pg_catalog.pg_constraint con +WHERE + con.confrelid = :oid +ORDER BY 1 + +-- name: get_table_rules +SELECT + r.rulename, + :ev_enabled as ev_enabled, + trim(TRAILING ';' FROM pg_catalog.pg_get_ruledef(r.oid, TRUE)) +FROM + pg_catalog.pg_rewrite r +WHERE + r.ev_class = :oid +ORDER BY 1 + +-- name: get_is_partition +SELECT + quote_ident(np.nspname) || '.' || + quote_ident(cp.relname) || ' ' || + pg_get_expr(cc.relpartbound, cc.oid, TRUE) AS partition_of, + pg_get_partition_constraintdef (cc.oid) AS partition_constraint +FROM + pg_inherits i + INNER JOIN pg_class cp ON cp.oid = i.inhparent + INNER JOIN pg_namespace np ON np.oid = cp.relnamespace + INNER JOIN pg_class cc ON cc.oid = i.inhrelid + INNER JOIN pg_namespace nc ON nc.oid = cc.relnamespace +WHERE + cc.oid = :oid + +-- name: get_table_triggers +SELECT + t.tgname, + pg_catalog.pg_get_triggerdef(t.oid, TRUE), + t.tgenabled +FROM + pg_catalog.pg_trigger t +WHERE + t.tgrelid = :oid + AND NOT t.tgisinternal +ORDER BY 1 + +-- name: get_foreign_table_info +SELECT + s.srvname, + array_to_string(ARRAY ( + SELECT + quote_ident(option_name) || ' ' || + quote_literal(option_value) + FROM pg_options_to_table(ftoptions)), ', ') AS ftoptions +FROM + pg_catalog.pg_foreign_table f, + pg_catalog.pg_foreign_server s +WHERE + f.ftrelid = :oid + AND s.oid = f.ftserver + +-- name: get_inherited_tables +SELECT + c.oid::pg_catalog.regclass +FROM + pg_catalog.pg_class c, + pg_catalog.pg_inherits i +WHERE + c.oid = i.inhparent + AND i.inhrelid = :oid +ORDER BY inhseqno + +-- name: get_child_tables +SELECT + c.oid::pg_catalog.regclass +FROM + pg_catalog.pg_class c, + pg_catalog.pg_inherits i +WHERE + c.oid = i.inhrelid + AND i.inhparent = :oid +ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text + +-- name: show_function_definition +-- docs: ("\\sf", "\\sf[+] FUNCNAME", "Show a function's definition.") +SELECT + pg_catalog.pg_get_functiondef( + SELECT + coalesce( + get_coordinates::pg_catalog.regprocedure::pg_catalog.oid, + get_coordinates::pg_catalog.regproc::pg_catalog.oid) + ) AS source + + +-- name: list_foreign_tables +-- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) +SELECT + n.nspname AS "schema", + c.relname AS "name", + :relkind AS "type", + pg_catalog.pg_get_userbyid(c.relowner) AS "owner", + pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "size", + pg_catalog.obj_description(c.oid, 'pg_class') AS "description" +FROM + pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE + c.relkind IN ('f', '') + AND n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND n.nspname !~ '^pg_toast' + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname OPERATOR (pg_catalog. ~) :pattern +ORDER BY 1, 2 + +-- name: relkind +CASE c.relkind +WHEN 'r' THEN 'table' +WHEN 'v' THEN 'view' +WHEN 'p' THEN 'partitioned table' +WHEN 'm' THEN 'materialized view' +WHEN 'i' THEN 'index' +WHEN 'I' THEN 'partitioned index' +WHEN 'S' THEN 'sequence' +WHEN 's' THEN 'special' +WHEN 'f' THEN 'foreign table' +WHEN 't' THEN 'toast table' +WHEN 'c' THEN 'composite type' +END + +-- name: relpersistence +CASE c.relpersistence +WHEN 'p' THEN 'permanent' +WHEN 'u' THEN 'unlogged' +WHEN 't' THEN 'temporary' +END + +-- name: reloftype +CASE +WHEN c.reloftype = 0 +THEN '' +ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text +END + +-- name: defaclobjtype +CASE d.defaclobjtype +WHEN 'r' THEN 'table' +WHEN 'S' THEN 'sequence' +WHEN 'f' THEN 'function' +WHEN 'T' THEN 'type' +WHEN 'n' THEN 'schema' +END + +-- name: provolatile +CASE p.provolatile +WHEN 'i' THEN 'immutable' +WHEN 's' THEN 'stable' +WHEN 'v' THEN 'volatile' +WHEN 'c' THEN 'volatile' +END + +-- name: contype +CASE con.contype +WHEN 'c' THEN 'check constraint' +WHEN 'f' THEN 'foreign key constraint' +WHEN 'p' THEN 'primary key constraint' +WHEN 'u' THEN 'unique constraint' +WHEN 't' THEN 'constraint trigger' +WHEN 'x' THEN 'exclusion constraint' +END + +-- name: ev_enabled +CASE ev_enabled +WHEN 'A' THEN 'always' +WHEN 'O' THEN 'origin' +WHEN 'R' THEN 'replica' +WHEN 'D' THEN 'disabled' +END diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 729bf73..6dda4fd 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -9,7 +9,7 @@ queries = aiosql.from_path("dbcommands.sql", "psycopg2") -from .main import special_command, RAW_QUERY +from .main import special_command TableInfo = namedtuple( "TableInfo", @@ -51,59 +51,15 @@ def list_roles(cur, pattern, verbose): """ Returns (title, rows, headers, status) """ - - params = {} - + pattern = pattern or ".*" if cur.connection.info.server_version > 90000: - sql = SQL( - """ - SELECT r.rolname, - r.rolsuper, - r.rolinherit, - r.rolcreaterole, - r.rolcreatedb, - r.rolcanlogin, - r.rolconnlimit, - r.rolvaliduntil, - ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) as memberof, - {verbose} - r.rolreplication - FROM pg_catalog.pg_roles r - {pattern} - ORDER BY 1 - """ - ) if verbose: - params["verbose"] = SQL( - """pg_catalog.shobj_description(r.oid, 'pg_authid') AS description, """ - ) + cur.execute(queries.list_roles_9_verbose.sql, (pattern,)) else: - params["verbose"] = SQL("") - else: - sql = SQL( - """ - SELECT u.usename AS rolname, - u.usesuper AS rolsuper, - true AS rolinherit, - false AS rolcreaterole, - u.usecreatedb AS rolcreatedb, - true AS rolcanlogin, - -1 AS rolconnlimit, - u.valuntil as rolvaliduntil, - ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof - FROM pg_catalog.pg_user u - """ - ) - - if pattern: - _, schema = sql_name_pattern(pattern) - params["pattern"] = SQL("WHERE r.rolname ~ {}").format(schema) + cur.execute(queries.list_roles_9.sql, (pattern,)) else: - params["pattern"] = SQL("") + cur.execute(queries.list_roles.sql) - formatted_query = sql.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -112,81 +68,16 @@ def list_roles(cur, pattern, verbose): @special_command("\\dp", "\\dp [pattern]", "List privileges.", aliases=("\\z",)) def list_privileges(cur, pattern, verbose): """Returns (title, rows, headers, status)""" - sql = SQL( - """ - SELECT n.nspname as "Schema", - c.relname as "Name", - CASE c.relkind WHEN 'r' THEN 'table' - WHEN 'v' THEN 'view' - WHEN 'm' THEN 'materialized view' - WHEN 'S' THEN 'sequence' - WHEN 'f' THEN 'foreign table' - WHEN 'p' THEN 'partitioned table' END as "Type", - pg_catalog.array_to_string(c.relacl, E'\n') AS "Access privileges", - pg_catalog.array_to_string(ARRAY( - SELECT attname || E':\n ' || pg_catalog.array_to_string(attacl, E'\n ') - FROM pg_catalog.pg_attribute a - WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL - ), E'\n') AS "Column privileges", - pg_catalog.array_to_string(ARRAY( - SELECT polname - || CASE WHEN NOT polpermissive THEN - E' (RESTRICTIVE)' - ELSE '' END - || CASE WHEN polcmd != '*' THEN - E' (' || polcmd::pg_catalog.text || E'):' - ELSE E':' - END - || CASE WHEN polqual IS NOT NULL THEN - E'\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid) - ELSE E'' - END - || CASE WHEN polwithcheck IS NOT NULL THEN - E'\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid) - ELSE E'' - END || CASE WHEN polroles <> '{0}' THEN - E'\n to: ' || pg_catalog.array_to_string( - ARRAY( - SELECT rolname - FROM pg_catalog.pg_roles - WHERE oid = ANY (polroles) - ORDER BY 1 - ), E', ') - ELSE E'' - END - FROM pg_catalog.pg_policy pol - WHERE polrelid = c.oid), E'\n') - AS "Policies" - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - """ + param = bool(pattern) + schema, table = sql_name_pattern(pattern) + print((schema, table, pattern, param)) + print("+") + schema = schema or ".*" + table = table or ".*" + cur.execute( + queries.list_privileges.sql, + (param, table, schema), ) - - if pattern: - schema, table = sql_name_pattern(pattern) - if table: - pattern = SQL( - " AND c.relname OPERATOR(pg_catalog.~) {} COLLATE pg_catalog.default " - ).format(table) - if schema: - pattern += SQL( - " AND n.nspname OPERATOR(pg_catalog.~) {} COLLATE pg_catalog.default " - ).format(schema) - else: - pattern = SQL(" AND pg_catalog.pg_table_is_visible(c.oid) ") - - where_clause = SQL( - """ - WHERE c.relkind IN ('r','v','m','S','f','p') - {pattern} - AND n.nspname !~ '^pg_' - """ - ).format(pattern=pattern) - - sql += where_clause + SQL(" ORDER BY 1, 2 ") - - log.debug(sql.as_string(cur)) - cur.execute(sql) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -195,36 +86,9 @@ def list_privileges(cur, pattern, verbose): @special_command("\\ddp", "\\ddp [pattern]", "Lists default access privilege settings.") def list_default_privileges(cur, pattern, verbose): """Returns (title, rows, headers, status)""" - sql = SQL( - """ - SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS "Owner", - n.nspname AS "Schema", - CASE d.defaclobjtype WHEN 'r' THEN 'table' - WHEN 'S' THEN 'sequence' - WHEN 'f' THEN 'function' - WHEN 'T' THEN 'type' - WHEN 'n' THEN 'schema' END AS "Type", - pg_catalog.array_to_string(d.defaclacl, E'\n') AS "Access privileges" - FROM pg_catalog.pg_default_acl d - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace - {where_clause} - ORDER BY 1, 2, 3 - """ - ) - - params = {} - if pattern: - params["where_clause"] = SQL( - """ - WHERE (n.nspname OPERATOR(pg_catalog.~) {pattern} COLLATE pg_catalog.default - OR pg_catalog.pg_get_userbyid(d.defaclrole) OPERATOR(pg_catalog.~) {pattern} COLLATE pg_catalog.default) - """ - ).format(pattern=f"^({pattern})$") - else: - params["where_clause"] = SQL("") - log.debug(sql.format(**params).as_string(cur)) - cur.execute(sql.format(**params)) + pattern = f"^({pattern})$" if pattern else ".*" + cur.execute(queries.list_default_privileges.sql, (pattern, pattern)) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -821,8 +685,11 @@ def _fetch_oid_details(cur, oid): cur, headers, status = _fetch_oid_details(cur, oid) yield title, cur, headers, status else: - yield None, None, None, 'Did not find any results for pattern "{}".'.format( - pattern + yield ( + None, + None, + None, + 'Did not find any results for pattern "{}".'.format(pattern), ) return diff --git a/tests/test_specials.py b/tests/test_specials.py index 8cd90c9..9f93951 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import pytest -from dbutils import dbtest, POSTGRES_USER, SERVER_VERSION, foreign_db_environ, fdw_test +from dbutils import dbtest, POSTGRES_USER, foreign_db_environ, fdw_test import itertools import locale @@ -346,8 +346,8 @@ def test_slash_dn(executor): def test_slash_dp(executor): """List all schemas.""" results = executor(r"\dp") - title = None - rows = [ + expected_title = None + expected_rows = [ ("public", "Inh1", "table", None, "", ""), ("public", "inh2", "table", None, "", ""), ("public", "mvw1", "materialized view", None, "", ""), @@ -358,7 +358,7 @@ def test_slash_dp(executor): ("public", "vw1", "view", None, "", ""), ] - headers = [ + expected_headers = [ "Schema", "Name", "Type", @@ -366,9 +366,11 @@ def test_slash_dp(executor): "Column privileges", "Policies", ] - status = "SELECT %s" % len(rows) - expected = [title, rows, headers, status] - assert results == expected + expected_status = "SELECT %s" % len(expected_rows) + assert results[0] == expected_title + assert results[1] == expected_rows + assert results[2] == expected_headers + assert results[3] == expected_status @dbtest @@ -394,12 +396,12 @@ def test_slash_dp_pattern_table(executor): def test_slash_dp_pattern_schema(executor): """List all schemas.""" results = executor(r"\dp schema2.*") - title = None - rows = [ + expected_title = None + expected_rows = [ ("schema2", "tbl2", "table", None, "", ""), ("schema2", "tbl2_id2_seq", "sequence", None, "", ""), ] - headers = [ + expected_headers = [ "Schema", "Name", "Type", @@ -407,18 +409,20 @@ def test_slash_dp_pattern_schema(executor): "Column privileges", "Policies", ] - status = "SELECT %s" % len(rows) - expected = [title, rows, headers, status] - assert results == expected + expected_status = "SELECT %s" % len(expected_rows) + assert results[0] == expected_title + assert results[1] == expected_rows + assert results[2] == expected_headers + assert results[3] == expected_status @dbtest def test_slash_dp_pattern_alias(executor): """List all schemas.""" results = executor(r"\z i*2") - title = None - rows = [("public", "inh2", "table", None, "", "")] - headers = [ + expected_title = None + expected_rows = [("public", "inh2", "table", None, "", "")] + expected_headers = [ "Schema", "Name", "Type", @@ -426,9 +430,11 @@ def test_slash_dp_pattern_alias(executor): "Column privileges", "Policies", ] - status = "SELECT %s" % len(rows) - expected = [title, rows, headers, status] - assert results == expected + expected_status = "SELECT %s" % len(expected_rows) + assert results[0] == expected_title + assert results[1] == expected_rows + assert results[2] == expected_headers + assert results[3] == expected_status @dbtest From 5f990a7327169577d3e24ebbb46a969f31b0d488 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Sun, 5 Nov 2023 06:44:46 +0000 Subject: [PATCH 03/30] refactor list tablespaces and schemas --- dbcommands.sql | 55 +++++++++++++++++----------- pgspecial/dbcommands.py | 80 +++++++---------------------------------- 2 files changed, 47 insertions(+), 88 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 1feac8a..404dd75 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -68,19 +68,6 @@ SELECT u.usename AS rolname, u.valuntil AS rolvaliduntil, array(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = any(g.grolist)) AS memberof FROM pg_catalog.pg_user u --- name: list_schemas --- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") -SELECT - n.nspname AS "name", - pg_catalog.pg_get_userbyid(n.nspowner) AS "owner", - pg_catalog.array_to_string(n.nspacl, E'\\n') AS "access_privileges", - pg_catalog.obj_description(n.oid, 'pg_namespace') AS "description" -FROM - pg_catalog.pg_namespace n -WHERE - n.nspname ~ :pattern -ORDER BY 1 - -- name: list_privileges -- docs: ("\\dp", "\\dp [pattern]", "List roles.", aliases=("\\z",)) SELECT n.nspname AS "Schema", @@ -162,25 +149,53 @@ ORDER BY 1, 2, 3 -- name: list_tablespaces -- docs: ("\\db", "\\db[+] [pattern]", "List tablespaces.") SELECT - n.spcname AS "name", - pg_catalog.pg_get_userbyid(n.spcowner) AS "owner", + n.spcname AS "Name", + pg_catalog.pg_get_userbyid(n.spcowner) AS "Owner", CASE WHEN (EXISTS ( SELECT * FROM pg_proc WHERE proname = 'pg_tablespace_location')) THEN pg_catalog.pg_tablespace_location(n.oid) ELSE 'Not supported' - END AS "location" + END AS "Location" FROM pg_catalog.pg_tablespace n WHERE n.spcname ~ :pattern ORDER BY 1 +-- name: list_schemas_verbose +-- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") +SELECT + n.nspname AS "Name", + pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner", + pg_catalog.array_to_string(n.nspacl, E'\\n') AS "Access privileges", + pg_catalog.obj_description(n.oid, 'pg_namespace') AS "Description" +FROM + pg_catalog.pg_namespace n +WHERE +CASE :pattern + WHEN '.*' THEN n.nspname !~ '^pg_' AND n.nspname <> 'information_schema' + ELSE n.nspname ~ :pattern +END +ORDER BY 1 +-- name: list_schemas +-- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") +SELECT + n.nspname AS "Name", + pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner" +FROM + pg_catalog.pg_namespace n +WHERE +CASE :pattern + WHEN '.*' THEN n.nspname !~ '^pg_' AND n.nspname <> 'information_schema' + ELSE n.nspname ~ :pattern +END +ORDER BY 1 -- name: list_extensions SELECT - e.extname AS "name", - e.extversion AS "version", - n.nspname AS "schema", - c.description AS "description" + e.extname AS "Name", + e.extversion AS "Version", + n.nspname AS "Schema", + c.description AS "Description" FROM pg_catalog.pg_extension e LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 6dda4fd..cc09ee4 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -6,11 +6,10 @@ from psycopg.sql import SQL import aiosql +from .main import special_command queries = aiosql.from_path("dbcommands.sql", "psycopg2") -from .main import special_command - TableInfo = namedtuple( "TableInfo", [ @@ -39,18 +38,14 @@ def list_databases(cur, pattern, verbose): else: cur.execute(queries.list_databases.sql, (pattern,)) - if cur.description: - headers = [x.name for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] - else: - return [(None, None, None, cur.statusmessage)] + headers = [x.name for x in cur.description] if cur.description else None + return [(None, cur, headers, cur.statusmessage)] @special_command("\\du", "\\du[+] [pattern]", "List roles.") def list_roles(cur, pattern, verbose): - """ - Returns (title, rows, headers, status) - """ + """Returns (title, rows, headers, status)""" + pattern = pattern or ".*" if cur.connection.info.server_version > 90000: if verbose: @@ -96,38 +91,10 @@ def list_default_privileges(cur, pattern, verbose): @special_command("\\db", "\\db[+] [pattern]", "List tablespaces.") def list_tablespaces(cur, pattern, **_): - """ - Returns (title, rows, headers, status) - """ - - params = {} - cur.execute( - "SELECT EXISTS(SELECT * FROM pg_proc WHERE proname = 'pg_tablespace_location')" - ) - (is_location,) = cur.fetchone() - - sql = SQL( - """SELECT n.spcname AS "Name", pg_catalog.pg_get_userbyid(n.spcowner) AS "Owner", - {location} AS "Location" FROM pg_catalog.pg_tablespace n - {pattern} - ORDER BY 1 - """ - ) - - if is_location: - params["location"] = SQL(" pg_catalog.pg_tablespace_location(n.oid)") - else: - params["location"] = SQL(" 'Not supported'") - - if pattern: - _, tbsp = sql_name_pattern(pattern) - params["pattern"] = SQL(" WHERE n.spcname ~ {}").format(tbsp) - else: - params["pattern"] = SQL("") + """Returns (title, rows, headers, status)""" - formatted_query = sql.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) + pattern = pattern or ".*" + cur.execute(queries.list_tablespaces.sql, {"pattern": pattern}) headers = [x.name for x in cur.description] if cur.description else None return [(None, cur, headers, cur.statusmessage)] @@ -135,36 +102,13 @@ def list_tablespaces(cur, pattern, **_): @special_command("\\dn", "\\dn[+] [pattern]", "List schemas.") def list_schemas(cur, pattern, verbose): - """ - Returns (title, rows, headers, status) - """ - - params = {} - sql = SQL( - """SELECT n.nspname AS "Name", pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner" - {verbose} - FROM pg_catalog.pg_namespace n WHERE n.nspname - {pattern} - ORDER BY 1 - """ - ) - + """Returns (title, rows, headers, status)""" + pattern = pattern or ".*" if verbose: - params["verbose"] = SQL( - ''', pg_catalog.array_to_string(n.nspacl, E'\\n') AS "Access privileges", pg_catalog.obj_description(n.oid, 'pg_namespace') AS "Description"''' - ) + cur.execute(queries.list_schemas_verbose.sql, {"pattern": pattern}) else: - params["verbose"] = SQL("") + cur.execute(queries.list_schemas.sql, {"pattern": pattern}) - if pattern: - _, schema = sql_name_pattern(pattern) - params["pattern"] = SQL("~ {}").format(schema) - else: - params["pattern"] = SQL("!~ '^pg_' AND n.nspname <> 'information_schema'") - - formatted_query = sql.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] From e3c842f25388302adc738dabce048895864f024a Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Sun, 5 Nov 2023 07:03:42 +0000 Subject: [PATCH 04/30] refactor list extensions --- pgspecial/dbcommands.py | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index cc09ee4..a8c0382 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -162,6 +162,9 @@ def _describe_extension(cur, oid): return if verbose: + # TODO - use the join query instead of looping. + # May need refactoring some more code. + # cur.execute(queries.list_extensions_verbose.sql, {"pattern": pattern}) extensions = _find_extensions(cur, pattern) if extensions: @@ -173,33 +176,13 @@ def _describe_extension(cur, oid): yield None, None, None, f"""Did not find any extension named "{pattern}".""" return - sql = SQL( - """ - SELECT e.extname AS "Name", - e.extversion AS "Version", - n.nspname AS "Schema", - c.description AS "Description" - FROM pg_catalog.pg_extension e - LEFT JOIN pg_catalog.pg_namespace n - ON n.oid = e.extnamespace - LEFT JOIN pg_catalog.pg_description c - ON c.objoid = e.oid - AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass - {where_clause} - ORDER BY 1, 2 - """ - ) - - params = {} if pattern: - _, schema = sql_name_pattern(pattern) - params["where_clause"] = SQL("WHERE e.extname ~ {}").format(schema) + _, pattern = sql_name_pattern(pattern) else: - params["where_clause"] = SQL("") + pattern = ".*" + + cur.execute(queries.list_extensions.sql, {"pattern": pattern}) - formatted_query = sql.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) if cur.description: headers = [x.name for x in cur.description] yield None, cur, headers, cur.statusmessage From b506469f473fce7d5349e04e327ea2eb80d7efbf Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Sun, 5 Nov 2023 08:52:17 +0000 Subject: [PATCH 05/30] list objects --- dbcommands.sql | 64 ++++++++++++++++++++++++++++++++++------- pgspecial/dbcommands.py | 64 +++++++++-------------------------------- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 404dd75..f00e1b7 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -218,25 +218,69 @@ WHERE AND e.extname ~ :pattern ORDER BY 1 --- name: list_objects +-- name: list_objects_verbose -- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes SELECT - n.nspname AS "schema", - c.relname AS "name", - :relkind AS "type", - pg_catalog.pg_get_userbyid(c.relowner) AS "owner", - pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "size", - pg_catalog.obj_description(c.oid, 'pg_class') as "description" + n.nspname AS "Schema", + c.relname AS "Name", + CASE c.relkind + WHEN 'r' THEN 'table' + WHEN 'v' THEN 'view' + WHEN 'p' THEN 'partitioned table' + WHEN 'm' THEN 'materialized view' + WHEN 'i' THEN 'index' + WHEN 'S' THEN 'sequence' + WHEN 's' THEN 'special' + WHEN 'f' THEN 'foreign table' END + as "Type", + pg_catalog.pg_get_userbyid(c.relowner) AS "Owner", + pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "Size", + pg_catalog.obj_description(c.oid, 'pg_class') as "Description" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE - c.relkind IN (:relkinds) -AND n.nspname ~ :schema_pattern + c.relkind = ANY(:relkinds) + AND c.relname ~ :table_pattern + AND CASE WHEN :schema_pattern != '.*' + THEN n.nspname ~ :schema_pattern + ELSE + pg_catalog.pg_table_is_visible(c.oid) AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' AND n.nspname !~ '^pg_toast' - AND c.relname ~ :pattern + END +ORDER BY 1, 2 +-- name: list_objects +-- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes +SELECT + n.nspname AS "Schema", + c.relname AS "Name", + CASE c.relkind + WHEN 'r' THEN 'table' + WHEN 'v' THEN 'view' + WHEN 'p' THEN 'partitioned table' + WHEN 'm' THEN 'materialized view' + WHEN 'i' THEN 'index' + WHEN 'S' THEN 'sequence' + WHEN 's' THEN 'special' + WHEN 'f' THEN 'foreign table' END + as "Type", + pg_catalog.pg_get_userbyid(c.relowner) AS "Owner" +FROM + pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE + c.relkind = ANY(:relkinds) + AND c.relname ~ :table_pattern + AND CASE WHEN :schema_pattern != '.*' + THEN n.nspname ~ :schema_pattern + ELSE + pg_catalog.pg_table_is_visible(c.oid) + AND n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND n.nspname !~ '^pg_toast' + END ORDER BY 1, 2 -- name: list_functions diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index a8c0382..d0e8e0a 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -198,59 +198,23 @@ def list_objects(cur, pattern, verbose, relkinds): relkinds is a list of strings to filter pg_class.relkind """ - schema_pattern, table_pattern = sql_name_pattern(pattern) - - params = {"relkind": relkinds} - if verbose: - params["verbose_columns"] = SQL( - """ - ,pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "Size", - pg_catalog.obj_description(c.oid, 'pg_class') as "Description" """ - ) - else: - params["verbose_columns"] = SQL("") - - sql = SQL( - """SELECT n.nspname as "Schema", - c.relname as "Name", - CASE c.relkind - WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' - WHEN 'p' THEN 'partitioned table' - WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' - WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' - WHEN 'f' THEN 'foreign table' END - as "Type", - pg_catalog.pg_get_userbyid(c.relowner) as "Owner" - {verbose_columns} - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n - ON n.oid = c.relnamespace - WHERE c.relkind = ANY({relkind}) - {schema_pattern} - {table_pattern} - ORDER BY 1, 2 - """ - ) - - if schema_pattern: - params["schema_pattern"] = SQL(" AND n.nspname ~ {}").format(schema_pattern) + if pattern: + schema_pattern, table_pattern = sql_name_pattern(pattern) else: - params["schema_pattern"] = SQL( - """ - AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND n.nspname !~ '^pg_toast' - AND pg_catalog.pg_table_is_visible(c.oid) """ - ) - - if table_pattern: - params["table_pattern"] = SQL(" AND c.relname ~ {}").format(table_pattern) + schema_pattern, table_pattern = ".*", ".*" + params = { + "schema_pattern": schema_pattern, + "table_pattern": table_pattern, + "relkinds": relkinds, + } + if verbose: + cur.execute(queries.list_objects_verbose.sql, params) else: - params["table_pattern"] = SQL("") + cur.execute(queries.list_objects.sql, params) - formatted_query = sql.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) + if cur.description: + headers = [x.name for x in cur.description] + yield None, cur, headers, cur.statusmessage if cur.description: headers = [x.name for x in cur.description] From 0df1b0ad39859345fe0b4549006bf5a0e51f9d98 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Mon, 6 Nov 2023 19:19:20 +0000 Subject: [PATCH 06/30] describe table details --- dbcommands.sql | 20 +++++++++----------- pgspecial/dbcommands.py | 32 +++++--------------------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index f00e1b7..655f0d1 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -377,18 +377,16 @@ WHERE ORDER BY 1, 2 -- name: describe_table_details -- docs: ( "\\d", "\\d[+] [pattern]", "List or describe tables, views and sequences.") -SELECT - c.oid, - n.nspname, - c.relname -FROM - pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +SELECT c.oid, n.nspname, c.relname +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE - n.nspname ~ :schema_pattern --- AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname OPERATOR (pg_catalog. ~) :pattern -ORDER BY 2, 3 + CASE WHEN :nspname != '.*' + THEN n.nspname ~ :nspname + ELSE pg_catalog.pg_table_is_visible(c.oid) + END + AND c.relname OPERATOR(pg_catalog.~) :relname +ORDER BY 2,3 -- name: describe_one_table_details SELECT diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index d0e8e0a..b49c311 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -212,10 +212,6 @@ def list_objects(cur, pattern, verbose, relkinds): else: cur.execute(queries.list_objects.sql, params) - if cur.description: - headers = [x.name for x in cur.description] - yield None, cur, headers, cur.statusmessage - if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -623,32 +619,14 @@ def describe_table_details(cur, pattern, verbose): # This is a \d command. A royal pain in the ass. schema, relname = sql_name_pattern(pattern) - where = [] - params = {} - - if schema: - where.append("n.nspname ~ %(nspname)s") - params["nspname"] = schema - else: - where.append("pg_catalog.pg_table_is_visible(c.oid)") + schema = ".*" if not schema else schema + relname = ".*" if not relname else relname - if relname: - where.append("c.relname OPERATOR(pg_catalog.~) %(relname)s") - params["relname"] = relname - - sql = ( - """SELECT c.oid, n.nspname, c.relname - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - """ - + ("WHERE " + " AND ".join(where) if where else "") - + """ - ORDER BY 2,3""" - ) # Execute the sql, get the results and call describe_one_table_details on each table. - log.debug("%s, %s", sql, params) - cur.execute(sql, params) + cur.execute( + queries.describe_table_details.sql, {"nspname": schema, "relname": relname} + ) if not (cur.rowcount > 0): return [(None, None, None, f"Did not find any relation named {pattern}.")] From ef7d56238acd370ccafd01c6278e0234b9d7932c Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Mon, 6 Nov 2023 19:53:22 +0000 Subject: [PATCH 07/30] show function definnition --- dbcommands.sql | 14 +++++++------- pgspecial/dbcommands.py | 17 ++++------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 655f0d1..df9e9f2 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -628,13 +628,13 @@ ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text -- name: show_function_definition -- docs: ("\\sf", "\\sf[+] FUNCNAME", "Show a function's definition.") SELECT - pg_catalog.pg_get_functiondef( - SELECT - coalesce( - get_coordinates::pg_catalog.regprocedure::pg_catalog.oid, - get_coordinates::pg_catalog.regproc::pg_catalog.oid) - ) AS source - +pg_catalog.pg_get_functiondef( + CASE + WHEN :pattern LIKE :bracket_wildcard + THEN :pattern::pg_catalog.regprocedure::pg_catalog.oid + ELSE :pattern::pg_catalog.regproc::pg_catalog.oid + END +) AS source -- name: list_foreign_tables -- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index b49c311..2e5c78e 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -1607,19 +1607,10 @@ def rowcount(self): @special_command("\\sf", "\\sf[+] FUNCNAME", "Show a function's definition.") def show_function_definition(cur, pattern, verbose): - params = {"pattern": pattern} - if "(" in pattern: - sql = "SELECT %(pattern)s::pg_catalog.regprocedure::pg_catalog.oid" - else: - sql = "SELECT %(pattern)s::pg_catalog.regproc::pg_catalog.oid" - log.debug("%s, %s", sql, params) - cur.execute(sql, params) - (foid,) = cur.fetchone() - - params = {"foid": foid} - sql = "SELECT pg_catalog.pg_get_functiondef(%(foid)s) as source" - log.debug("%s, %s", sql, params) - cur.execute(sql, params) + cur.execute( + queries.show_function_definition.sql, + {"pattern": pattern, "bracket_wildcard": "%(%"}, + ) if cur.description: headers = [x.name for x in cur.description] if verbose: From 653da8cb2e8d254c56252dcbdc4849dae8fd06f6 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Mon, 6 Nov 2023 20:06:24 +0000 Subject: [PATCH 08/30] list foreign tables --- dbcommands.sql | 30 +++++++++++++++++++++++------ pgspecial/dbcommands.py | 42 ++++------------------------------------- 2 files changed, 28 insertions(+), 44 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index df9e9f2..1ed814b 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -636,15 +636,33 @@ pg_catalog.pg_get_functiondef( END ) AS source +-- name: list_foreign_tables_verbose +-- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) +SELECT + n.nspname AS "Schema", + c.relname AS "Name", + CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'table' WHEN 'I' THEN 'index' END as "Type", + pg_catalog.pg_get_userbyid(c.relowner) AS "Owner", + pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size", + pg_catalog.obj_description(c.oid, 'pg_class') AS "Description" +FROM + pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace +WHERE + c.relkind IN ('f', '') + AND n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND n.nspname !~ '^pg_toast' + AND pg_catalog.pg_table_is_visible(c.oid) + AND c.relname OPERATOR (pg_catalog. ~) :pattern +ORDER BY 1, 2 -- name: list_foreign_tables -- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) SELECT - n.nspname AS "schema", - c.relname AS "name", - :relkind AS "type", - pg_catalog.pg_get_userbyid(c.relowner) AS "owner", - pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "size", - pg_catalog.obj_description(c.oid, 'pg_class') AS "description" + n.nspname AS "Schema", + c.relname AS "Name", + CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'table' WHEN 'I' THEN 'index' END as "Type", + pg_catalog.pg_get_userbyid(c.relowner) AS "Owner" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 2e5c78e..7d83b7c 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -1640,46 +1640,12 @@ def shell_command(cur, pattern, verbose): @special_command("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) def list_foreign_tables(cur, pattern, verbose): - params = {} - query = SQL( - """ - SELECT n.nspname as "Schema", - c.relname as "Name", - CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'table' WHEN 'I' THEN 'index' END as "Type", - pg_catalog.pg_get_userbyid(c.relowner) as "Owner" - {verbose_cols} - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind IN ('f','') - AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND n.nspname !~ '^pg_toast' - AND pg_catalog.pg_table_is_visible(c.oid) - {filter} - ORDER BY 1,2; - """ - ) - + _, tbl_name = sql_name_pattern(pattern) + pattern = f"^({tbl_name})$" if tbl_name else ".*" if verbose: - params["verbose_cols"] = SQL( - """ - , pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "Size", - pg_catalog.obj_description(c.oid, 'pg_class') as "Description" """ - ) - else: - params["verbose_cols"] = SQL("") - - if pattern: - _, tbl_name = sql_name_pattern(pattern) - params["filter"] = SQL(" AND c.relname OPERATOR(pg_catalog.~) {} ").format( - f"^({tbl_name})$" - ) + cur.execute(queries.list_foreign_tables_verbose.sql, {"pattern": pattern}) else: - params["filter"] = SQL("") - - formatted_query = query.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) + cur.execute(queries.list_foreign_tables.sql, {"pattern": pattern}) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] From cd6f9d0126b7c5cbc55aa1b00b80ac2342d9b32a Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 19:05:52 +0000 Subject: [PATCH 09/30] list functions --- .pre-commit-config.yaml | 2 +- dbcommands.sql | 441 ++++++++++++++++++++++++++++++++++++---- pgspecial/dbcommands.py | 263 ++++-------------------- 3 files changed, 435 insertions(+), 271 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b970ac5..04af16e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,5 +3,5 @@ repos: rev: stable hooks: - id: black - language_version: python3.7 + language_version: python3.11 diff --git a/dbcommands.sql b/dbcommands.sql index 1ed814b..2e02a6b 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -285,47 +285,317 @@ ORDER BY 1, 2 -- name: list_functions -- docs: ("\\df", "\\df[+] [pattern]", "List functions.") +SELECT n.nspname AS "schema", + p.proname AS "name", + pg_catalog.pg_get_function_result(p.oid) AS "result_data_type", + pg_catalog.pg_get_function_arguments(p.oid) AS "argument_data_types", + CASE + WHEN p.prokind = 'a' THEN 'agg' + WHEN p.prokind = 'w' THEN 'window' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "type", + :provolatile AS "volatility", + pg_catalog.pg_get_userbyid(p.proowner) AS "owner", + l.lanname AS "language", + p.prosrc AS "source_code", + pg_catalog.obj_description(p.oid, 'pg_proc') AS "description" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang +WHERE n.nspname ~ :schema_pattern + AND p.proname ~ :pattern +ORDER BY 1, + 2, + 4 -- name: list_functions__verbose_11 + +SELECT n.nspname AS "Schema", + p.proname AS "Name", + pg_catalog.pg_get_function_result(p.oid) AS "Result data type", + pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", + CASE + WHEN p.prokind = 'a' THEN 'agg' + WHEN p.prokind = 'w' THEN 'window' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "Type", + CASE + WHEN p.provolatile = 'i' THEN 'immutable' + WHEN p.provolatile = 's' THEN 'stable' + WHEN p.provolatile = 'v' THEN 'volatile' + END AS "Volatility", + pg_catalog.pg_get_userbyid(p.proowner) AS "Owner", + l.lanname AS "Language", + p.prosrc AS "Source code", + pg_catalog.obj_description(p.oid, 'pg_proc') AS "Description" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang +WHERE p.proname ~ :function_pattern + AND CASE + WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_function_is_visible(p.oid) + END + AND CASE + WHEN :schema_pattern = '.*' + AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + ELSE TRUE + END +ORDER BY 1, + 2, + 4 + +-- name: list_functions_verbose_9 + +SELECT n.nspname AS "Schema", + p.proname AS "Name", + pg_catalog.pg_get_function_result(p.oid) AS "Result data type", + pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", + CASE + WHEN p.proisagg THEN 'agg' + WHEN p.proiswindow THEN 'window' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "Type", + CASE + WHEN p.provolatile = 'i' THEN 'immutable' + WHEN p.provolatile = 's' THEN 'stable' + WHEN p.provolatile = 'v' THEN 'volatile' + END AS "Volatility", + pg_catalog.pg_get_userbyid(p.proowner) AS "Owner", + l.lanname AS "Language", + p.prosrc AS "Source code", + pg_catalog.obj_description(p.oid, 'pg_proc') AS "Description" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang +WHERE p.proname ~ :function_pattern + AND CASE + WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_function_is_visible(p.oid) + END + AND CASE + WHEN :schema_pattern = '.*' + AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + ELSE TRUE + END +ORDER BY 1, + 2, + 4 + +-- name: list_functions__verbose_9 + +SELECT n.nspname AS "Schema", + p.proname AS "Name", + pg_catalog.format_type(p.prorettype, NULL) AS "Result data type", + pg_catalog.oidvectortypes(p.proargtypes) AS "Argument data types", + CASE + WHEN p.proisagg THEN 'agg' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "Type" CASE + WHEN p.provolatile = 'i' THEN 'immutable' + WHEN p.provolatile = 's' THEN 'stable' + WHEN p.provolatile = 'v' THEN 'volatile' + END AS "Volatility", + pg_catalog.pg_get_userbyid(p.proowner) AS "Owner", + l.lanname AS "Language", + p.prosrc AS "Source code", + pg_catalog.obj_description(p.oid, 'pg_proc') AS "Description" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang +WHERE p.proname ~ :function_pattern + AND CASE + WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_function_is_visible(p.oid) + END + AND CASE + WHEN :schema_pattern = '.*' + AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + ELSE TRUE + END +ORDER BY 1, + 2, + 4 + +-- name: list_functions_11 +SELECT n.nspname AS "Schema", + p.proname AS "Name", + pg_catalog.pg_get_function_result(p.oid) AS "Result data type", + pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", + CASE + WHEN p.prokind = 'a' THEN 'agg' + WHEN p.prokind = 'w' THEN 'window' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "Type" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +WHERE p.proname ~ :function_pattern + AND CASE + WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_function_is_visible(p.oid) + END + AND CASE + WHEN :schema_pattern = '.*' + AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + ELSE TRUE + END +ORDER BY 1, + 2, + 4 + +-- name: list_functions_9 +SELECT n.nspname AS "Schema", + p.proname AS "Name", + pg_catalog.pg_get_function_result(p.oid) AS "Result data type", + pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", + CASE + WHEN p.proisagg THEN 'agg' + WHEN p.proiswindow THEN 'window' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "Type" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +WHERE p.proname ~ :function_pattern + AND CASE + WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_function_is_visible(p.oid) + END + AND CASE + WHEN :schema_pattern = '.*' + AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + ELSE TRUE + END +ORDER BY 1, + 2, + 4 + +-- name: list_functions_9 +SELECT n.nspname AS "Schema", + p.proname AS "Name", + pg_catalog.format_type(p.prorettype, NULL) AS "Result data type", + pg_catalog.oidvectortypes(p.proargtypes) AS "Argument data types", + CASE + WHEN p.proisagg THEN 'agg' + WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' + ELSE 'normal' + END AS "Type" +FROM pg_catalog.pg_proc p +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace +WHERE p.proname ~ :function_pattern + AND CASE + WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_function_is_visible(p.oid) + END + AND CASE + WHEN :schema_pattern = '.*' + AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + ELSE TRUE + END +ORDER BY 1, + 2, + 4 + + +-- name: list_datatype_verbose_9 +-- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT - n.nspname AS "schema", - p.proname AS "name", - pg_catalog.pg_get_function_result(p.oid) AS "result_data_type", - pg_catalog.pg_get_function_arguments(p.oid) AS "argument_data_types", + n.nspname AS "Schema", + pg_catalog.format_type(t.oid, NULL) AS "Name", + t.typname AS "Internal name", CASE - WHEN p.prokind = 'a' THEN 'agg' - WHEN p.prokind = 'w' THEN 'window' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' - ELSE 'normal' - END AS "type", - :provolatile AS "volatility", - pg_catalog.pg_get_userbyid(p.proowner) AS "owner", - l.lanname AS "language", - p.prosrc AS "source_code", - pg_catalog.obj_description(p.oid, 'pg_proc') AS "description" + WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) + WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) + ELSE CAST(t.typlen AS pg_catalog.text) + END AS "Size", + pg_catalog.array_to_string(ARRAY ( + SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE + e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS "Elements", + pg_catalog.array_to_string(t.typacl, E'\n') AS "Access_privileges", + pg_catalog.obj_description(t.oid, 'pg_type') AS "Description" FROM - pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang -WHERE - n.nspname ~ :schema_pattern - AND p.proname ~ :pattern -ORDER BY 1, 2, 4 + pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace +WHERE (t.typrelid = 0 + OR ( + SELECT + c.relkind = 'c' + FROM + pg_catalog.pg_class c + WHERE + c.oid = t.typrelid)) + AND NOT EXISTS( + SELECT 1 + FROM pg_catalog.pg_type el + WHERE el.oid = t.typelem + AND el.typarray = t.oid) + AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) + AND CASE WHEN :schema_pattern != '.*' + THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_type_is_visible(t.oid) + END + AND CASE WHEN :schema_pattern = '.*' AND :type_pattern = '.*' + THEN n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' + ELSE true + END +ORDER BY 1, 2 --- name: list_datatypes +-- name: list_datatypes_9 -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT - n.nspname AS "schema", - pg_catalog.format_type(t.oid, NULL) AS "name", - t.typname AS "internal_name", + n.nspname AS "Schema", + pg_catalog.format_type(t.oid, NULL) AS "Name", + pg_catalog.obj_description(t.oid, 'pg_type') as "Description" +FROM pg_catalog.pg_type t +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace +WHERE (t.typrelid = 0 + OR ( + SELECT + c.relkind = 'c' + FROM + pg_catalog.pg_class c + WHERE + c.oid = t.typrelid)) + AND NOT EXISTS( + SELECT 1 + FROM pg_catalog.pg_type el + WHERE el.oid = t.typelem + AND el.typarray = t.oid) + AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) + AND CASE WHEN :schema_pattern != '.*' + THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_type_is_visible(t.oid) + END + AND CASE WHEN :schema_pattern = '.*' and :type_pattern = '.*' + THEN n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' + ELSE true + END +ORDER BY 1, 2 +-- name: list_datatype_verbose +-- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") +SELECT + n.nspname AS "Schema", + pg_catalog.format_type(t.oid, NULL) AS "Name", + t.typname AS "Internal name", CASE WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) ELSE CAST(t.typlen AS pg_catalog.text) - END AS "size", + END AS "Size", pg_catalog.array_to_string(ARRAY ( SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE - e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS "elements", - pg_catalog.array_to_string(t.typacl, E'\n') AS "access_privileges", - pg_catalog.obj_description(t.oid, 'pg_type') AS "description" + e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS "Elements", + pg_catalog.array_to_string(t.typacl, E'\n') AS "Access_privileges", + pg_catalog.obj_description(t.oid, 'pg_type') AS "Description" FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace @@ -337,16 +607,62 @@ WHERE (t.typrelid = 0 pg_catalog.pg_class c WHERE c.oid = t.typrelid)) - AND n.nspname ~ :schema_pattern - AND (t.typname ~ :pattern OR pg_catalog.format_type(t.oid, NULL) ~ :pattern) + -- this is the only difference in the query + -- AND NOT EXISTS( + -- SELECT 1 + -- FROM pg_catalog.pg_type el + -- WHERE el.oid = t.typelem + -- AND el.typarray = t.oid) + AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) + AND CASE WHEN :schema_pattern != '.*' + THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_type_is_visible(t.oid) + END + AND CASE WHEN :schema_pattern = '.*' and :type_pattern = '.*' + THEN n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' + ELSE true + END ORDER BY 1, 2 --- name: list_domains +-- name: list_datatypes +-- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") +SELECT + n.nspname AS "Schema", + pg_catalog.format_type(t.oid, NULL) AS "Name", + pg_catalog.obj_description(t.oid, 'pg_type') as "Description" +FROM pg_catalog.pg_type t +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace +WHERE (t.typrelid = 0 + OR ( + SELECT + c.relkind = 'c' + FROM + pg_catalog.pg_class c + WHERE + c.oid = t.typrelid)) + -- this is the only difference in the query + -- AND NOT EXISTS( + -- SELECT 1 + -- FROM pg_catalog.pg_type el + -- WHERE el.oid = t.typelem + -- AND el.typarray = t.oid) + AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) + AND CASE WHEN :schema_pattern != '.*' + THEN n.nspname ~ :schema_pattern + ELSE pg_catalog.pg_type_is_visible(t.oid) + END + AND CASE WHEN :schema_pattern = '.*' and :type_pattern = '.*' + THEN n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' + ELSE true + END +ORDER BY 1, 2 + +-- name: list_domains_verbose -- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") SELECT - n.nspname AS "schema", - t.typname AS "name", - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "type", + n.nspname AS "Schema", + t.typname AS "Name", + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "Type", pg_catalog.ltrim((COALESCE(( SELECT (' collate ' || c.collname) @@ -356,14 +672,14 @@ SELECT AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation), '') || CASE WHEN t.typnotnull THEN ' not null' ELSE '' END) || - CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS "modifier", + CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS "Modifier", pg_catalog.array_to_string(ARRAY ( SELECT pg_catalog.pg_get_constraintdef(r.oid, TRUE) FROM pg_catalog.pg_constraint AS r - WHERE t.oid = r.contypid), ' ') AS "check", - pg_catalog.array_to_string(t.typacl, E'\n') AS "access_privileges", - d.description AS "description" + WHERE t.oid = r.contypid), ' ') AS "Check", + pg_catalog.array_to_string(t.typacl, E'\n') AS "Access privileges", + d.description AS "Description" FROM pg_catalog.pg_type AS t LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace @@ -374,6 +690,47 @@ WHERE t.typtype = 'd' AND n.nspname ~ :schema_pattern AND t.typname ~ :pattern + AND CASE WHEN :schema_pattern = '.*' AND :pattern = '.*' + THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND pg_catalog.pg_type_is_visible(t.oid) + ELSE true + END +ORDER BY 1, 2 +-- name: list_domains +-- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") +SELECT + n.nspname AS "Schema", + t.typname AS "Name", + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "Type", + pg_catalog.ltrim((COALESCE(( + SELECT + (' collate ' || c.collname) + FROM pg_catalog.pg_collation AS c, pg_catalog.pg_type AS bt + WHERE + c.oid = t.typcollation + AND bt.oid = t.typbasetype + AND t.typcollation <> bt.typcollation), '') || + CASE WHEN t.typnotnull THEN ' not null' ELSE '' END) || + CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS "Modifier", + pg_catalog.array_to_string(ARRAY ( + SELECT + pg_catalog.pg_get_constraintdef(r.oid, TRUE) + FROM pg_catalog.pg_constraint AS r + WHERE t.oid = r.contypid), ' ') AS "Check" +FROM + pg_catalog.pg_type AS t + LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace +WHERE + t.typtype = 'd' + AND n.nspname ~ :schema_pattern + AND t.typname ~ :pattern + AND CASE WHEN :schema_pattern = '.*' AND :pattern = '.*' + THEN n.nspname <> 'pg_catalog' + AND n.nspname <> 'information_schema' + AND pg_catalog.pg_type_is_visible(t.oid) + ELSE true + END ORDER BY 1, 2 -- name: describe_table_details -- docs: ( "\\d", "\\d[+] [pattern]", "List or describe tables, views and sequences.") @@ -630,7 +987,7 @@ ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text SELECT pg_catalog.pg_get_functiondef( CASE - WHEN :pattern LIKE :bracket_wildcard + WHEN :pattern ~ '\(' THEN :pattern::pg_catalog.regprocedure::pg_catalog.oid ELSE :pattern::pg_catalog.regproc::pg_catalog.oid END @@ -641,7 +998,7 @@ pg_catalog.pg_get_functiondef( SELECT n.nspname AS "Schema", c.relname AS "Name", - CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'table' WHEN 'I' THEN 'index' END as "Type", + 'foreign table' AS "Type", pg_catalog.pg_get_userbyid(c.relowner) AS "Owner", pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size", pg_catalog.obj_description(c.oid, 'pg_class') AS "Description" @@ -661,7 +1018,7 @@ ORDER BY 1, 2 SELECT n.nspname AS "Schema", c.relname AS "Name", - CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'table' WHEN 'I' THEN 'index' END as "Type", + 'foreign table' AS "Type", pg_catalog.pg_get_userbyid(c.relowner) AS "Owner" FROM pg_catalog.pg_class c diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 7d83b7c..85e3d94 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -65,8 +65,6 @@ def list_privileges(cur, pattern, verbose): """Returns (title, rows, headers, status)""" param = bool(pattern) schema, table = sql_name_pattern(pattern) - print((schema, table, pattern, param)) - print("+") schema = schema or ".*" table = table or ".*" cur.execute( @@ -244,116 +242,27 @@ def list_indexes(cur, pattern, verbose): @special_command("\\df", "\\df[+] [pattern]", "List functions.") def list_functions(cur, pattern, verbose): - if verbose: - verbose_columns = """ - ,CASE - WHEN p.provolatile = 'i' THEN 'immutable' - WHEN p.provolatile = 's' THEN 'stable' - WHEN p.provolatile = 'v' THEN 'volatile' - END as "Volatility", - pg_catalog.pg_get_userbyid(p.proowner) as "Owner", - l.lanname as "Language", - p.prosrc as "Source code", - pg_catalog.obj_description(p.oid, 'pg_proc') as "Description" """ - - verbose_table = """ LEFT JOIN pg_catalog.pg_language l - ON l.oid = p.prolang""" - else: - verbose_columns = verbose_table = "" + schema_pattern, function_pattern = sql_name_pattern(pattern) + params = { + "schema_pattern": schema_pattern or ".*", + "function_pattern": function_pattern or ".*", + } if cur.connection.info.server_version >= 110000: - sql = ( - """ - SELECT n.nspname as "Schema", - p.proname as "Name", - pg_catalog.pg_get_function_result(p.oid) - as "Result data type", - pg_catalog.pg_get_function_arguments(p.oid) - as "Argument data types", - CASE - WHEN p.prokind = 'a' THEN 'agg' - WHEN p.prokind = 'w' THEN 'window' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype - THEN 'trigger' - ELSE 'normal' - END as "Type" """ - + verbose_columns - + """ - FROM pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - """ - + verbose_table - + """ - WHERE """ - ) + if verbose: + cur.execute(queries.list_functions_verbose_11.sql, params) + else: + cur.execute(queries.list_functions_11.sql, params) elif cur.connection.info.server_version > 90000: - sql = ( - """ - SELECT n.nspname as "Schema", - p.proname as "Name", - pg_catalog.pg_get_function_result(p.oid) - as "Result data type", - pg_catalog.pg_get_function_arguments(p.oid) - as "Argument data types", - CASE - WHEN p.proisagg THEN 'agg' - WHEN p.proiswindow THEN 'window' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype - THEN 'trigger' - ELSE 'normal' - END as "Type" """ - + verbose_columns - + """ - FROM pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - """ - + verbose_table - + """ - WHERE """ - ) - else: - sql = ( - """ - SELECT n.nspname as "Schema", - p.proname as "Name", - pg_catalog.format_type(p.prorettype, NULL) as "Result data type", - pg_catalog.oidvectortypes(p.proargtypes) as "Argument data types", - CASE - WHEN p.proisagg THEN 'agg' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' - ELSE 'normal' - END as "Type" """ - + verbose_columns - + """ - FROM pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - """ - + verbose_table - + """ - WHERE """ - ) - - schema_pattern, func_pattern = sql_name_pattern(pattern) - params = {} - - if schema_pattern: - sql += " n.nspname ~ %(nspname)s " - params["nspname"] = schema_pattern + if verbose: + cur.execute(queries.list_functions_verbose_9.sql, params) + else: + cur.execute(queries.list_functions_9.sql, params) else: - sql += " pg_catalog.pg_function_is_visible(p.oid) " - - if func_pattern: - sql += " AND p.proname ~ %(proname)s " - params["proname"] = func_pattern - - if not (schema_pattern or func_pattern): - sql += """ AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' """ - - sql += " ORDER BY 1, 2, 4" - - log.debug("%s, %s", sql, params) - cur.execute(sql, params) + if verbose: + cur.execute(queries.list_functions_verbose.sql, params) + else: + cur.execute(queries.list_functions.sql, params) if cur.description: headers = [x.name for x in cur.description] @@ -362,76 +271,23 @@ def list_functions(cur, pattern, verbose): @special_command("\\dT", "\\dT[S+] [pattern]", "List data types") def list_datatypes(cur, pattern, verbose): - sql = """SELECT n.nspname as "Schema", - pg_catalog.format_type(t.oid, NULL) AS "Name", """ - - if verbose: - sql += r''' t.typname AS "Internal name", - CASE - WHEN t.typrelid != 0 - THEN CAST('tuple' AS pg_catalog.text) - WHEN t.typlen < 0 - THEN CAST('var' AS pg_catalog.text) - ELSE CAST(t.typlen AS pg_catalog.text) - END AS "Size", - pg_catalog.array_to_string( - ARRAY( - SELECT e.enumlabel - FROM pg_catalog.pg_enum e - WHERE e.enumtypid = t.oid - ORDER BY e.enumsortorder - ), E'\n') AS "Elements", - pg_catalog.array_to_string(t.typacl, E'\n') - AS "Access privileges", - pg_catalog.obj_description(t.oid, 'pg_type') - AS "Description"''' - else: - sql += """ pg_catalog.obj_description(t.oid, 'pg_type') - as "Description" """ - - if cur.connection.info.server_version > 90000: - sql += """ FROM pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n - ON n.oid = t.typnamespace - WHERE (t.typrelid = 0 OR - ( SELECT c.relkind = 'c' - FROM pg_catalog.pg_class c - WHERE c.oid = t.typrelid)) - AND NOT EXISTS( - SELECT 1 - FROM pg_catalog.pg_type el - WHERE el.oid = t.typelem - AND el.typarray = t.oid) """ - else: - sql += """ FROM pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n - ON n.oid = t.typnamespace - WHERE (t.typrelid = 0 OR - ( SELECT c.relkind = 'c' - FROM pg_catalog.pg_class c - WHERE c.oid = t.typrelid)) """ - schema_pattern, type_pattern = sql_name_pattern(pattern) - params = {} - if schema_pattern: - sql += " AND n.nspname ~ %(nspname)s " - params["nspname"] = schema_pattern + params = { + "schema_pattern": schema_pattern or ".*", + "type_pattern": type_pattern or ".*", + } + if cur.connection.info.server_version > 90000: + if verbose: + cur.execute(queries.list_datatypes_verbose_9.sql, params) + else: + cur.execute(queries.list_datatypes_9.sql, params) else: - sql += " AND pg_catalog.pg_type_is_visible(t.oid) " - - if type_pattern: - sql += """ AND (t.typname ~ %(typname)s - OR pg_catalog.format_type(t.oid, NULL) ~ %(typname)s) """ - params["typname"] = type_pattern - - if not (schema_pattern or type_pattern): - sql += """ AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' """ + if verbose: + cur.execute(queries.list_datatypes_verbose.sql, params) + else: + cur.execute(queries.list_datatypes.sql, params) - sql += " ORDER BY 1, 2" - log.debug("%s, %s", sql, params) - cur.execute(sql, params) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -439,61 +295,12 @@ def list_datatypes(cur, pattern, verbose): @special_command("\\dD", "\\dD[+] [pattern]", "List or describe domains.") def list_domains(cur, pattern, verbose): - if verbose: - extra_cols = r''', - pg_catalog.array_to_string(t.typacl, E'\n') AS "Access privileges", - d.description as "Description"''' - extra_joins = """ - LEFT JOIN pg_catalog.pg_description d ON d.classoid = t.tableoid - AND d.objoid = t.oid AND d.objsubid = 0""" - else: - extra_cols = extra_joins = "" - - sql = f"""\ - SELECT n.nspname AS "Schema", - t.typname AS "Name", - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "Type", - pg_catalog.ltrim((COALESCE((SELECT (' collate ' || c.collname) - FROM pg_catalog.pg_collation AS c, - pg_catalog.pg_type AS bt - WHERE c.oid = t.typcollation - AND bt.oid = t.typbasetype - AND t.typcollation <> bt.typcollation) , '') - || CASE - WHEN t.typnotnull - THEN ' not null' - ELSE '' - END) || CASE - WHEN t.typdefault IS NOT NULL - THEN(' default ' || t.typdefault) - ELSE '' - END) AS "Modifier", - pg_catalog.array_to_string(ARRAY( - SELECT pg_catalog.pg_get_constraintdef(r.oid, TRUE) - FROM pg_catalog.pg_constraint AS r - WHERE t.oid = r.contypid), ' ') AS "Check"{extra_cols} - FROM pg_catalog.pg_type AS t - LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace{extra_joins} - WHERE t.typtype = 'd' """ - schema_pattern, name_pattern = sql_name_pattern(pattern) - params = {} - if schema_pattern or name_pattern: - if schema_pattern: - sql += " AND n.nspname ~ %(nspname)s" - params["nspname"] = schema_pattern - if name_pattern: - sql += " AND t.typname ~ %(typname)s" - params["typname"] = name_pattern + params = {"schema_pattern": schema_pattern or ".*", "pattern": name_pattern or ".*"} + if verbose: + cur.execute(queries.list_domains_verbose.sql, params) else: - sql += """ - AND (n.nspname <> 'pg_catalog') - AND (n.nspname <> 'information_schema') - AND pg_catalog.pg_type_is_visible(t.oid)""" - - sql += " ORDER BY 1, 2" - log.debug("%s, %s", sql, params) - cur.execute(sql, params) + cur.execute(queries.list_domains.sql, params) if cur.description: headers = [x.name for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -1609,7 +1416,7 @@ def rowcount(self): def show_function_definition(cur, pattern, verbose): cur.execute( queries.show_function_definition.sql, - {"pattern": pattern, "bracket_wildcard": "%(%"}, + {"pattern": pattern}, ) if cur.description: headers = [x.name for x in cur.description] From 32279ede101f95a8e6731d3a66977a2796aa43e8 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 19:26:05 +0000 Subject: [PATCH 10/30] list extensions --- dbcommands.sql | 27 ++++++++++++++------------- pgspecial/dbcommands.py | 32 +++----------------------------- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 2e02a6b..8840b8b 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -190,6 +190,20 @@ CASE :pattern END ORDER BY 1 +-- name: find_extensions +SELECT e.extname, + e.oid +FROM pg_catalog.pg_extension e +WHERE e.extname ~ :schema +ORDER BY 1, + 2; +-- name: describe_extension +SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS "Object Description" +FROM pg_catalog.pg_depend +WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass + AND refobjid = :oid + AND deptype = 'e' +ORDER BY 1 -- name: list_extensions SELECT e.extname AS "Name", @@ -205,19 +219,6 @@ WHERE e.extname ~ :pattern ORDER BY 1, 2 --- name: list_extensions_verbose -SELECT - e.extname AS "name", - pg_catalog.pg_describe_object(classid, objid, 0) AS "object_description" -FROM - pg_catalog.pg_depend d - LEFT OUTER JOIN pg_catalog.pg_extension e ON e.oid = refobjid -WHERE - refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass - AND deptype = 'e' - AND e.extname ~ :pattern -ORDER BY 1 - -- name: list_objects_verbose -- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes SELECT diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 85e3d94..5e9fab3 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -116,39 +116,13 @@ def list_schemas(cur, pattern, verbose): @special_command("\\dx", "\\dx[+] [pattern]", "List extensions.") def list_extensions(cur, pattern, verbose): def _find_extensions(cur, pattern): - sql = SQL( - """ - SELECT e.extname, e.oid FROM pg_catalog.pg_extension e - {pattern} - ORDER BY 1, 2; - """ - ) - - params = {} - if pattern: - _, schema = sql_name_pattern(pattern) - params["pattern"] += SQL("WHERE e.extname ~ {}").format(schema) - else: - params["pattern"] = SQL("") + _, schema = sql_name_pattern(pattern) - formatted_query = sql.format(**params) - log.debug(formatted_query.as_string(cur)) - cur.execute(formatted_query) + cur.execute(queries.find_extensions.sql, {"schema": schema or ".*"}) return cur.fetchall() def _describe_extension(cur, oid): - sql = SQL( - """ - SELECT pg_catalog.pg_describe_object(classid, objid, 0) - AS "Object Description" - FROM pg_catalog.pg_depend - WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass - AND refobjid = {} - AND deptype = 'e' - ORDER BY 1""" - ).format(oid) - log.debug(sql.as_string(cur)) - cur.execute(sql) + cur.execute(queries.describe_extension.sql, {"oid": oid}) headers = [x.name for x in cur.description] return cur, headers, cur.statusmessage From 45dda92973cdd6a81d38745eb2ec4a9361cf0cde Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 19:35:35 +0000 Subject: [PATCH 11/30] list text search configurations --- dbcommands.sql | 42 +++++++++++++++++++++++++ pgspecial/dbcommands.py | 69 ++++------------------------------------- 2 files changed, 48 insertions(+), 63 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 8840b8b..6bd3940 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -219,6 +219,48 @@ WHERE e.extname ~ :pattern ORDER BY 1, 2 +-- name: find_text_search_configs +SELECT c.oid, + c.cfgname, + n.nspname, + p.prsname, + np.nspname AS pnspname +FROM pg_catalog.pg_ts_config c +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace, + pg_catalog.pg_ts_parser p +LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.prsnamespace +WHERE p.oid = c.cfgparser + AND c.cfgname ~ :schema +ORDER BY 1, + 2 +-- name: fetch_oid_details +SELECT + (SELECT t.alias + FROM pg_catalog.ts_token_type(c.cfgparser) AS t + WHERE t.tokid = m.maptokentype ) AS "Token", + pg_catalog.btrim(ARRAY + (SELECT mm.mapdict::pg_catalog.regdictionary + FROM pg_catalog.pg_ts_config_map AS mm + WHERE mm.mapcfg = m.mapcfg + AND mm.maptokentype = m.maptokentype + ORDER BY mapcfg, maptokentype, mapseqno) :: pg_catalog.text, '{}') AS "Dictionaries" +FROM pg_catalog.pg_ts_config AS c, + pg_catalog.pg_ts_config_map AS m +WHERE c.oid = :oid + AND m.mapcfg = c.oid +GROUP BY m.mapcfg, + m.maptokentype, + c.cfgparser +ORDER BY 1; +-- name: list_text_search_configurations +SELECT n.nspname AS "Schema", + c.cfgname AS "Name", + pg_catalog.obj_description(c.oid, 'pg_ts_config') AS "Description" +FROM pg_catalog.pg_ts_config c +LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace +WHERE c.cfgname ~ :schema +ORDER BY 1, + 2 -- name: list_objects_verbose -- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes SELECT diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 5e9fab3..7dab76c 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -283,56 +283,13 @@ def list_domains(cur, pattern, verbose): @special_command("\\dF", "\\dF[+] [pattern]", "List text search configurations.") def list_text_search_configurations(cur, pattern, verbose): def _find_text_search_configs(cur, pattern): - sql = """ - SELECT c.oid, - c.cfgname, - n.nspname, - p.prsname, - np.nspname AS pnspname - FROM pg_catalog.pg_ts_config c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace, - pg_catalog.pg_ts_parser p - LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.prsnamespace - WHERE p.oid = c.cfgparser - """ - - params = {} - if pattern: - _, schema = sql_name_pattern(pattern) - sql += "AND c.cfgname ~ %(cfgname)s" - params["cfgname"] = schema - - sql += " ORDER BY 1, 2;" - log.debug("%s, %s", sql, params) - cur.execute(sql, params) + _, schema = sql_name_pattern(pattern) + cur.execute(queries.find_text_search_configs.sql, {"schema": schema}) + return cur.fetchall() def _fetch_oid_details(cur, oid): - params = {"oid": oid} - sql = """ - SELECT - (SELECT t.alias - FROM pg_catalog.ts_token_type(c.cfgparser) AS t - WHERE t.tokid = m.maptokentype ) AS "Token", - pg_catalog.btrim(ARRAY - (SELECT mm.mapdict::pg_catalog.regdictionary - FROM pg_catalog.pg_ts_config_map AS mm - WHERE mm.mapcfg = m.mapcfg - AND mm.maptokentype = m.maptokentype - ORDER BY mapcfg, maptokentype, mapseqno) :: pg_catalog.text, '{}') AS "Dictionaries" - FROM pg_catalog.pg_ts_config AS c, - pg_catalog.pg_ts_config_map AS m - WHERE c.oid = %(oid)s - AND m.mapcfg = c.oid - GROUP BY m.mapcfg, - m.maptokentype, - c.cfgparser - ORDER BY 1; - """ - - log.debug("%s, %s", sql, params) - cur.execute(sql, params) - + cur.execute(queries.fetch_oid_details.sql, {"oid": oid}) headers = [x.name for x in cur.description] return cur, headers, cur.statusmessage @@ -361,23 +318,9 @@ def _fetch_oid_details(cur, oid): ) return - sql = """ - SELECT n.nspname AS "Schema", - c.cfgname AS "Name", - pg_catalog.obj_description(c.oid, 'pg_ts_config') AS "Description" - FROM pg_catalog.pg_ts_config c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace - """ - - params = {} - if pattern: - _, schema = sql_name_pattern(pattern) - sql += "WHERE c.cfgname ~ %(cfgname)s" - params["cfgname"] = schema + _, schema = sql_name_pattern(pattern) + cur.execute(queries.list_text_search_configurations.sql, {"schema": schema}) - sql += " ORDER BY 1, 2" - log.debug("%s, %s", sql, params) - cur.execute(sql, params) if cur.description: headers = [x.name for x in cur.description] yield None, cur, headers, cur.statusmessage From 93f6519bf967878034ff0a72d1678480c46d1794 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 20:08:26 +0000 Subject: [PATCH 12/30] describe one table details --- dbcommands.sql | 190 +++++++++++++++++++++++----------------- pgspecial/dbcommands.py | 77 +++------------- 2 files changed, 119 insertions(+), 148 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 6bd3940..4cc4b9f 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -788,25 +788,6 @@ WHERE AND c.relname OPERATOR(pg_catalog.~) :relname ORDER BY 2,3 --- name: describe_one_table_details -SELECT - c.relchecks, - c.relhasindex, - c.relhasrules, - c.relhastriggers, - pg_catalog.array_to_string(c.reloptions || ARRAY ( - SELECT 'toast.' || x FROM pg_catalog.unnest(tc.reloptions) x), ', ') as reloptions, - c.reltablespace, - :reloftype as reloftype, - :relkind as relkind, - :relpersistence as relpersistence, - c.relispartition -FROM - pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) -WHERE - c.oid = :oid - -- name: get_column_info SELECT a.attname AS "name", @@ -1074,67 +1055,114 @@ WHERE AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname OPERATOR (pg_catalog. ~) :pattern ORDER BY 1, 2 +-- name: describe_one_table_details_12 +SELECT c.relchecks, + c.relkind, + c.relhasindex, + c.relhasrules, + c.relhastriggers, + FALSE AS relhasoids, + pg_catalog.array_to_string(c.reloptions || array + (SELECT 'toast.' || x + FROM pg_catalog.unnest(tc.reloptions) x), ', '), + c.reltablespace, + CASE + WHEN c.reloftype = 0 THEN '' + ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text + END, + c.relpersistence, + c.relispartition +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE c.oid = :oid +-- name: describe_one_table_details_10 +SELECT c.relchecks, + c.relkind, + c.relhasindex, + c.relhasrules, + c.relhastriggers, + c.relhasoids, + pg_catalog.array_to_string(c.reloptions || array + (SELECT 'toast.' || x + FROM pg_catalog.unnest(tc.reloptions) x), ', '), + c.reltablespace, + CASE + WHEN c.reloftype = 0 THEN '' + ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text + END, + c.relpersistence, + c.relispartition +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE c.oid = oid --- name: relkind -CASE c.relkind -WHEN 'r' THEN 'table' -WHEN 'v' THEN 'view' -WHEN 'p' THEN 'partitioned table' -WHEN 'm' THEN 'materialized view' -WHEN 'i' THEN 'index' -WHEN 'I' THEN 'partitioned index' -WHEN 'S' THEN 'sequence' -WHEN 's' THEN 'special' -WHEN 'f' THEN 'foreign table' -WHEN 't' THEN 'toast table' -WHEN 'c' THEN 'composite type' -END - --- name: relpersistence -CASE c.relpersistence -WHEN 'p' THEN 'permanent' -WHEN 'u' THEN 'unlogged' -WHEN 't' THEN 'temporary' -END - --- name: reloftype -CASE -WHEN c.reloftype = 0 -THEN '' -ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text -END - --- name: defaclobjtype -CASE d.defaclobjtype -WHEN 'r' THEN 'table' -WHEN 'S' THEN 'sequence' -WHEN 'f' THEN 'function' -WHEN 'T' THEN 'type' -WHEN 'n' THEN 'schema' -END - --- name: provolatile -CASE p.provolatile -WHEN 'i' THEN 'immutable' -WHEN 's' THEN 'stable' -WHEN 'v' THEN 'volatile' -WHEN 'c' THEN 'volatile' -END - --- name: contype -CASE con.contype -WHEN 'c' THEN 'check constraint' -WHEN 'f' THEN 'foreign key constraint' -WHEN 'p' THEN 'primary key constraint' -WHEN 'u' THEN 'unique constraint' -WHEN 't' THEN 'constraint trigger' -WHEN 'x' THEN 'exclusion constraint' -END +-- name: describe_one_table_details_10 +SELECT c.relchecks, + c.relkind, + c.relhasindex, + c.relhasrules, + c.relhastriggers, + c.relhasoids, + pg_catalog.array_to_string(c.reloptions || array + (SELECT 'toast.' || x + FROM pg_catalog.unnest(tc.reloptions) x), ', '), + c.reltablespace, + CASE + WHEN c.reloftype = 0 THEN '' + ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text + END, + c.relpersistence, + FALSE AS relispartition +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE c.oid = :oid +-- name: describe_one_table_details_804 +SELECT c.relchecks, + c.relkind, + c.relhasindex, + c.relhasrules, + c.relhastriggers, + c.relhasoids, + pg_catalog.array_to_string(c.reloptions || array + (SELECT 'toast.' || x + FROM pg_catalog.unnest(tc.reloptions) x), ', '), + c.reltablespace, + 0 AS reloftype, + 'p' AS relpersistence, + FALSE AS relispartition +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE c.oid = :oid +-- name: describe_one_table_details_802 +SELECT c.relchecks, + c.relkind, + c.relhasindex, + c.relhasrules, + c.reltriggers > 0 AS relhastriggers, + c.relhasoids, + pg_catalog.array_to_string(c.reloptions || array + (SELECT 'toast.' || x + FROM pg_catalog.unnest(tc.reloptions) x), ', '), + c.reltablespace, + 0 AS reloftype, + 'p' AS relpersistence, + FALSE AS relispartition +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE c.oid = :oid --- name: ev_enabled -CASE ev_enabled -WHEN 'A' THEN 'always' -WHEN 'O' THEN 'origin' -WHEN 'R' THEN 'replica' -WHEN 'D' THEN 'disabled' -END +-- name: describe_one_table_details +-- docs: no suffix here +SELECT c.relchecks, + c.relkind, + c.relhasindex, + c.relhasrules, + c.reltriggers > 0 AS relhastriggers, + c.relhasoids, + c.reltablespace, + 0 AS reloftype, + 'p' AS relpersistence, + FALSE AS relispartition +FROM pg_catalog.pg_class c +LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) +WHERE c.oid = :oid diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 7dab76c..e769693 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -362,82 +362,25 @@ def describe_table_details(cur, pattern, verbose): def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): - if verbose and cur.connection.info.server_version >= 80200: - suffix = """pg_catalog.array_to_string(c.reloptions || array(select - 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')""" - else: - suffix = "''" - + params = {"oid": oid} if cur.connection.info.server_version >= 120000: - relhasoids = "false as relhasoids" - else: - relhasoids = "c.relhasoids" - - if cur.connection.info.server_version >= 100000: - sql = f"""SELECT c.relchecks, c.relkind, c.relhasindex, - c.relhasrules, c.relhastriggers, {relhasoids}, - {suffix}, - c.reltablespace, - CASE WHEN c.reloftype = 0 THEN '' - ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text - END, - c.relpersistence, - c.relispartition - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) - WHERE c.oid = '{oid}'""" + cur.execute(queries.describe_one_table_details_12.sql, params) + elif cur.connection.info.server_version >= 100000: + cur.execute(queries.describe_one_table_details_10.sql, params) elif cur.connection.info.server_version > 90000: - sql = f"""SELECT c.relchecks, c.relkind, c.relhasindex, - c.relhasrules, c.relhastriggers, c.relhasoids, - {suffix}, - c.reltablespace, - CASE WHEN c.reloftype = 0 THEN '' - ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text - END, - c.relpersistence, - false as relispartition - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) - WHERE c.oid = '{oid}'""" + cur.execute(queries.describe_one_table_details_9.sql, params) elif cur.connection.info.server_version >= 80400: - sql = f"""SELECT c.relchecks, - c.relkind, - c.relhasindex, - c.relhasrules, - c.relhastriggers, - c.relhasoids, - {suffix}, - c.reltablespace, - 0 AS reloftype, - 'p' AS relpersistence, - false as relispartition - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) - WHERE c.oid = '{oid}'""" + cur.execute(queries.describe_one_table_details_804.sql, params) + elif cur.connection.info.server_version >= 80200: + cur.execute(queries.describe_one_table_details_802.sql, params) else: - sql = f"""SELECT c.relchecks, - c.relkind, - c.relhasindex, - c.relhasrules, - c.reltriggers > 0 AS relhastriggers, - c.relhasoids, - {suffix}, - c.reltablespace, - 0 AS reloftype, - 'p' AS relpersistence, - false as relispartition - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) - WHERE c.oid = '{oid}'""" - - # Create a namedtuple called tableinfo and match what's in describe.c + cur.execute(queries.describe_one_table_details.sql, params) - log.debug(sql) - cur.execute(sql) if cur.rowcount > 0: + # TODO if not verbose - drop suffix tableinfo = TableInfo._make(cur.fetchone()) else: return None, None, None, f"Did not find any relation with OID {oid}." From 05f1c544de995204cf89313ce52cea79f29366f0 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 20:33:14 +0000 Subject: [PATCH 13/30] generate att_cols from headers --- pgspecial/dbcommands.py | 45 +++++++---------------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index e769693..d0970a3 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -367,15 +367,12 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): cur.execute(queries.describe_one_table_details_12.sql, params) elif cur.connection.info.server_version >= 100000: cur.execute(queries.describe_one_table_details_10.sql, params) - elif cur.connection.info.server_version > 90000: cur.execute(queries.describe_one_table_details_9.sql, params) - elif cur.connection.info.server_version >= 80400: cur.execute(queries.describe_one_table_details_804.sql, params) elif cur.connection.info.server_version >= 80200: cur.execute(queries.describe_one_table_details_802.sql, params) - else: cur.execute(queries.describe_one_table_details.sql, params) @@ -396,57 +393,36 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): seq_values = cur.fetchone() - # Get column info - cols = 0 - att_cols = {} sql = """SELECT a.attname, - pg_catalog.format_type(a.atttypid, a.atttypmod) + pg_catalog.format_type(a.atttypid, a.atttypmod) as atttype , (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) FROM pg_catalog.pg_attrdef d - WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) - , a.attnotnull""" - att_cols["attname"] = cols - cols += 1 - att_cols["atttype"] = cols - cols += 1 - att_cols["attrdef"] = cols - cols += 1 - att_cols["attnotnull"] = cols - cols += 1 + WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) as attrdef, + a.attnotnull""" if cur.connection.info.server_version >= 90100: sql += """,\n(SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation""" else: sql += ",\n NULL AS attcollation" - att_cols["attcollation"] = cols - cols += 1 if cur.connection.info.server_version >= 100000: sql += ",\n a.attidentity" else: sql += ",\n ''::pg_catalog.char AS attidentity" - att_cols["attidentity"] = cols - cols += 1 if cur.connection.info.server_version >= 120000: sql += ",\n a.attgenerated" else: sql += ",\n ''::pg_catalog.char AS attgenerated" - att_cols["attgenerated"] = cols - cols += 1 # index, or partitioned index if tableinfo.relkind == "i" or tableinfo.relkind == "I": if cur.connection.info.server_version >= 110000: sql += ( - f",\n CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i " - "WHERE i.indexrelid = '{oid}') THEN 'yes' ELSE 'no' END AS is_key" + ",\n CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i " + "WHERE i.indexrelid = '{oid}') THEN 'yes' ELSE 'no' END AS indexkey" ) - att_cols["indexkey"] = cols - cols += 1 sql += ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef" else: sql += """,\n NULL AS indexdef""" - att_cols["indexdef"] = cols - cols += 1 if tableinfo.relkind == "f" and cur.connection.info.server_version >= 90200: sql += """, CASE WHEN attfdwoptions IS NULL THEN '' ELSE '(' || array_to_string(ARRAY(SELECT quote_ident(option_name) || ' ' @@ -454,12 +430,8 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): pg_options_to_table(attfdwoptions)), ', ') || ')' END AS attfdwoptions""" else: sql += """, NULL AS attfdwoptions""" - att_cols["attfdwoptions"] = cols - cols += 1 if verbose: sql += """, a.attstorage""" - att_cols["attstorage"] = cols - cols += 1 if ( tableinfo.relkind == "r" or tableinfo.relkind == "i" @@ -472,8 +444,6 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): ",\n CASE WHEN a.attstattarget=-1 THEN " "NULL ELSE a.attstattarget END AS attstattarget" ) - att_cols["attstattarget"] = cols - cols += 1 if ( tableinfo.relkind == "r" or tableinfo.relkind == "v" @@ -482,9 +452,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): or tableinfo.relkind == "p" or tableinfo.relkind == "c" ): - sql += ",\n pg_catalog.col_description(a.attrelid, a.attnum)" - att_cols["attdescr"] = cols - cols += 1 + sql += ",\n pg_catalog.col_description(a.attrelid, a.attnum) as attdescr" sql += f""" FROM pg_catalog.pg_attribute a WHERE a.attrelid = '{oid}' AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; """ @@ -492,6 +460,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): log.debug(sql) cur.execute(sql) res = cur.fetchall() + att_cols = {x.name: i for i, x in enumerate(cur.description)} # Set the column names. headers = ["Column", "Type"] From 4e004e4128fbc495b93d629e57ef7fae851e6838 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 20:55:58 +0000 Subject: [PATCH 14/30] add index information --- dbcommands.sql | 118 +++++++++++++++++++++++++++++++++++++ pgspecial/dbcommands.py | 127 ++-------------------------------------- 2 files changed, 123 insertions(+), 122 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 4cc4b9f..31cc611 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1166,3 +1166,121 @@ SELECT c.relchecks, FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) WHERE c.oid = :oid + +-- name: get_sequence_column_name +SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) || '.' || pg_catalog.quote_ident(attname) +FROM pg_catalog.pg_class c +INNER JOIN pg_catalog.pg_depend d ON c.oid=d.refobjid +INNER JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace +INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid + AND a.attnum=d.refobjsubid) +WHERE d.classid='pg_catalog.pg_class'::pg_catalog.regclass + AND d.refclassid='pg_catalog.pg_class'::pg_catalog.regclass + AND d.objid=:oid \n + AND d.deptype='a' +-- name: footer_index_information_9 +SELECT i.indisunique, + i.indisprimary, + i.indisclustered, + i.indisvalid, (NOT i.indimmediate) +AND EXISTS + (SELECT 1 + FROM pg_catalog.pg_constraint + WHERE conrelid = i.indrelid + AND conindid = i.indexrelid + AND contype IN ('p', + 'u', + 'x') + AND condeferrable ) AS condeferrable, (NOT i.indimmediate) +AND EXISTS + (SELECT 1 + FROM pg_catalog.pg_constraint + WHERE conrelid = i.indrelid + AND conindid = i.indexrelid + AND contype IN ('p', + 'u', + 'x') + AND condeferred ) AS condeferred, + a.amname, + c2.relname, + pg_catalog.pg_get_expr(i.indpred, i.indrelid, TRUE) +FROM pg_catalog.pg_index i, + pg_catalog.pg_class c, + pg_catalog.pg_class c2, + pg_catalog.pg_am a +WHERE i.indexrelid = c.oid + AND c.oid = :oid + AND c.relam = a.oid + AND i.indrelid = c2.oid; +-- name: footer_index_information +SELECT i.indisunique, + i.indisprimary, + i.indisclustered, + 't' AS indisvalid, + 'f' AS condeferrable, + 'f' AS condeferred, + a.amname, + c2.relname, + pg_catalog.pg_get_expr(i.indpred, i.indrelid, TRUE) +FROM pg_catalog.pg_index i, + pg_catalog.pg_class c, + pg_catalog.pg_class c2, + pg_catalog.pg_am a +WHERE i.indexrelid = c.oid + AND c.oid = :oid + AND c.relam = a.oid + AND i.indrelid = c2.oid; +-- name: get_footer_table_index_information_9 +SELECT c2.relname, + i.indisprimary, + i.indisunique, + i.indisclustered, + i.indisvalid, + pg_catalog.pg_get_indexdef(i.indexrelid, 0, TRUE), + pg_catalog.pg_get_constraintdef(con.oid, TRUE), + contype, + condeferrable, + condeferred, + c2.reltablespace +FROM pg_catalog.pg_class c, + pg_catalog.pg_class c2, + pg_catalog.pg_index i +LEFT JOIN pg_catalog.pg_constraint con ON conrelid = i.indrelid +AND conindid = i.indexrelid +AND contype IN ('p', + 'u', + 'x') +WHERE c.oid = :oid + AND c.oid = i.indrelid + AND i.indexrelid = c2.oid +ORDER BY i.indisprimary DESC, + i.indisunique DESC, + c2.relname; +-- name: get_footer_table_index_information +SELECT c2.relname, + i.indisprimary, + i.indisunique, + i.indisclustered, + 't' AS indisvalid, + pg_catalog.pg_get_indexdef(i.indexrelid, 0, TRUE), + pg_catalog.pg_get_constraintdef(con.oid, TRUE), + contype, + condeferrable, + condeferred, + c2.reltablespace +FROM pg_catalog.pg_class c, + pg_catalog.pg_class c2, + pg_catalog.pg_index i +LEFT JOIN pg_catalog.pg_constraint con ON conrelid = i.indrelid +AND contype IN ('p', + 'u', + 'x') +WHERE c.oid = :oid + AND c.oid = i.indrelid + AND i.indexrelid = c2.oid +ORDER BY i.indisprimary DESC, + i.indisunique DESC, + c2.relname; +-- name: get_view_definition +SELECT pg_catalog.pg_get_viewdef(:oid::pg_catalog.oid, TRUE) + diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index d0970a3..12c3e67 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -587,60 +587,9 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* Footer information about an index */ if cur.connection.info.server_version > 90000: - sql = f"""SELECT i.indisunique, - i.indisprimary, - i.indisclustered, - i.indisvalid, - (NOT i.indimmediate) AND EXISTS ( - SELECT 1 - FROM pg_catalog.pg_constraint - WHERE conrelid = i.indrelid - AND conindid = i.indexrelid - AND contype IN ('p','u','x') - AND condeferrable - ) AS condeferrable, - (NOT i.indimmediate) AND EXISTS ( - SELECT 1 - FROM pg_catalog.pg_constraint - WHERE conrelid = i.indrelid - AND conindid = i.indexrelid - AND contype IN ('p','u','x') - AND condeferred - ) AS condeferred, - a.amname, - c2.relname, - pg_catalog.pg_get_expr(i.indpred, i.indrelid, true) - FROM pg_catalog.pg_index i, - pg_catalog.pg_class c, - pg_catalog.pg_class c2, - pg_catalog.pg_am a - WHERE i.indexrelid = c.oid - AND c.oid = '{oid}' - AND c.relam = a.oid - AND i.indrelid = c2.oid; - """ + cur.execute(queries.footer_index_information_9.sql, params) else: - sql = f"""SELECT i.indisunique, - i.indisprimary, - i.indisclustered, - 't' AS indisvalid, - 'f' AS condeferrable, - 'f' AS condeferred, - a.amname, - c2.relname, - pg_catalog.pg_get_expr(i.indpred, i.indrelid, true) - FROM pg_catalog.pg_index i, - pg_catalog.pg_class c, - pg_catalog.pg_class c2, - pg_catalog.pg_am a - WHERE i.indexrelid = c.oid - AND c.oid = '{oid}' - AND c.relam = a.oid - AND i.indrelid = c2.oid; - """ - - log.debug(sql) - cur.execute(sql) + cur.execute(queries.footer_index_information.sql, params) ( indisunique, @@ -685,23 +634,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): elif tableinfo.relkind == "S": # /* Footer information about a sequence */ # /* Get the column that owns this sequence */ - sql = ( - "SELECT pg_catalog.quote_ident(nspname) || '.' ||" - "\n pg_catalog.quote_ident(relname) || '.' ||" - "\n pg_catalog.quote_ident(attname)" - "\nFROM pg_catalog.pg_class c" - "\nINNER JOIN pg_catalog.pg_depend d ON c.oid=d.refobjid" - "\nINNER JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace" - "\nINNER JOIN pg_catalog.pg_attribute a ON (" - "\n a.attrelid=c.oid AND" - "\n a.attnum=d.refobjsubid)" - "\nWHERE d.classid='pg_catalog.pg_class'::pg_catalog.regclass" - "\n AND d.refclassid='pg_catalog.pg_class'::pg_catalog.regclass" - f"\n AND d.objid={oid} \n AND d.deptype='a'" - ) - - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_sequence_column_name.sql, params) result = cur.fetchone() if result: status.append(f"Owned by: {result[0]}") @@ -722,59 +655,9 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if tableinfo.hasindex: if cur.connection.info.server_version > 90000: - sql = f"""SELECT c2.relname, - i.indisprimary, - i.indisunique, - i.indisclustered, - i.indisvalid, - pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), - pg_catalog.pg_get_constraintdef(con.oid, true), - contype, - condeferrable, - condeferred, - c2.reltablespace - FROM pg_catalog.pg_class c, - pg_catalog.pg_class c2, - pg_catalog.pg_index i - LEFT JOIN pg_catalog.pg_constraint con - ON conrelid = i.indrelid - AND conindid = i.indexrelid - AND contype IN ('p','u','x') - WHERE c.oid = '{oid}' - AND c.oid = i.indrelid - AND i.indexrelid = c2.oid - ORDER BY i.indisprimary DESC, - i.indisunique DESC, - c2.relname; - """ + cur.execute(queries.get_footer_table_index_information_9.sql, params) else: - sql = f"""SELECT c2.relname, - i.indisprimary, - i.indisunique, - i.indisclustered, - 't' AS indisvalid, - pg_catalog.pg_get_indexdef(i.indexrelid, 0, true), - pg_catalog.pg_get_constraintdef(con.oid, true), - contype, - condeferrable, - condeferred, - c2.reltablespace - FROM pg_catalog.pg_class c, - pg_catalog.pg_class c2, - pg_catalog.pg_index i - LEFT JOIN pg_catalog.pg_constraint con - ON conrelid = i.indrelid - AND contype IN ('p','u','x') - WHERE c.oid = '{oid}' - AND c.oid = i.indrelid - AND i.indexrelid = c2.oid - ORDER BY i.indisprimary DESC, - i.indisunique DESC, - c2.relname; - """ - - log.debug(sql) - result = cur.execute(sql) + cur.execute(queries.get_footer_table_index_information.sql, params) if cur.rowcount > 0: status.append("Indexes:\n") From af9a18e3fc860a5f7d935e365a48c8e399df9dbe Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 7 Nov 2023 21:34:29 +0000 Subject: [PATCH 15/30] rest of describe functions --- dbcommands.sql | 102 +++++++++++++++++++++++++++ pgspecial/dbcommands.py | 153 ++++------------------------------------ 2 files changed, 117 insertions(+), 138 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 31cc611..f5de43f 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1283,4 +1283,106 @@ ORDER BY i.indisprimary DESC, c2.relname; -- name: get_view_definition SELECT pg_catalog.pg_get_viewdef(:oid::pg_catalog.oid, TRUE) +-- name: get_check_constraints +SELECT r.conname, + pg_catalog.pg_get_constraintdef(r.oid, + TRUE) +FROM pg_catalog.pg_constraint r +WHERE r.conrelid = :oid + AND r.contype = 'c' +ORDER BY 1; +-- name: get_foreign_key_constratints +SELECT conname, + pg_catalog.pg_get_constraintdef(r.oid, TRUE) AS condef +FROM pg_catalog.pg_constraint r +WHERE r.conrelid = :oid + AND r.contype = 'f' +ORDER BY 1; +-- name: get_foreign_key_references +SELECT conrelid::pg_catalog.regclass, + conname, + pg_catalog.pg_get_constraintdef(c.oid, TRUE) AS condef +FROM pg_catalog.pg_constraint c +WHERE c.confrelid = :oid + AND c.contype = 'f' +ORDER BY 1; +-- name: get_rules +SELECT r.rulename, + trim(TRAILING ';' + FROM pg_catalog.pg_get_ruledef(r.oid, TRUE)), + ev_enabled +FROM pg_catalog.pg_rewrite r +WHERE r.ev_class = :oid +ORDER BY 1; +-- name: get_partition_info +SELECT quote_ident(np.nspname) || '.' || quote_ident(cp.relname) || ' ' || pg_get_expr(cc.relpartbound, cc.oid, TRUE) AS partition_of, + pg_get_partition_constraintdef(cc.oid) AS partition_constraint +FROM pg_inherits i +INNER JOIN pg_class cp ON cp.oid = i.inhparent +INNER JOIN pg_namespace np ON np.oid = cp.relnamespace +INNER JOIN pg_class cc ON cc.oid = i.inhrelid +INNER JOIN pg_namespace nc ON nc.oid = cc.relnamespace +WHERE cc.oid = :oid +-- name: get_partition_key +select pg_get_partkeydef(:oid) +-- name: get_partitions_list +SELECT quote_ident(n.nspname) || '.' || quote_ident(c.relname) || ' ' || pg_get_expr(c.relpartbound, c.oid, TRUE) +FROM pg_inherits i +INNER JOIN pg_class c ON c.oid = i.inhrelid +INNER JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE i.inhparent = {oid} +ORDER BY 1 +-- name: get_view_rules +SELECT r.rulename, + trim(TRAILING ';' + FROM pg_catalog.pg_get_ruledef(r.oid, TRUE)) +FROM pg_catalog.pg_rewrite r +WHERE r.ev_class = :oid + AND r.rulename != '_RETURN' +ORDER BY 1; +-- name: get_triggers_info_9 +SELECT t.tgname, + pg_catalog.pg_get_triggerdef(t.oid, TRUE), + t.tgenabled +FROM pg_catalog.pg_trigger t +WHERE t.tgrelid = :oid + AND NOT t.tgisinternal +ORDER BY 1 +-- name: get_triggers_info +SELECT t.tgname, + pg_catalog.pg_get_triggerdef(t.oid), + t.tgenabled +FROM pg_catalog.pg_trigger t +WHERE t.tgrelid = :oid +ORDER BY 1 +-- name: get_foreign_table_name +SELECT s.srvname, + array_to_string(array + (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) + FROM pg_options_to_table(ftoptions)), ', ') +FROM pg_catalog.pg_foreign_table f, + pg_catalog.pg_foreign_server s +WHERE f.ftrelid = :oid + AND s.oid = f.ftserver; +-- name: get_foreign_inherited_tables +SELECT c.oid::pg_catalog.regclass +FROM pg_catalog.pg_class c, + pg_catalog.pg_inherits i +WHERE c.oid = i.inhparent + AND i.inhrelid = :oid +ORDER BY inhseqno +-- name: get_foreign_child_tables_9 +SELECT c.oid::pg_catalog.regclass +FROM pg_catalog.pg_class c, + pg_catalog.pg_inherits i +WHERE c.oid = i.inhrelid + AND i.inhparent = :oid +ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text; +-- name: get_foreign_child_tables +SELECT c.oid::pg_catalog.regclass +FROM pg_catalog.pg_class c, + pg_catalog.pg_inherits i +WHERE c.oid = i.inhrelid + AND i.inhparent = :oid +ORDER BY c.oid; diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 12c3e67..1b7d727 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -507,9 +507,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): view_def = "" # /* Check if table is a view or materialized view */ if (tableinfo.relkind == "v" or tableinfo.relkind == "m") and verbose: - sql = f"""SELECT pg_catalog.pg_get_viewdef('{oid}'::pg_catalog.oid, true)""" - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_view_definition.sql, params) if cur.rowcount > 0: (view_def,) = cur.fetchone() @@ -701,24 +699,10 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): status.append(" INVALID") status.append("\n") - # printTableAddFooter(&cont, buf.data); - - # /* Print tablespace of the index on the same line */ - # add_tablespace_footer(&cont, 'i', - # atooid(PQgetvalue(result, i, 10)), - # false); # /* print table (and column) check constraints */ if tableinfo.checks: - sql = ( - "SELECT r.conname, " - "pg_catalog.pg_get_constraintdef(r.oid, true)\n" - "FROM pg_catalog.pg_constraint r\n" - f"WHERE r.conrelid = '{oid}' AND r.contype = 'c'\n" - "ORDER BY 1;" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_check_constraints.sql, params) if cur.rowcount > 0: status.append("Check constraints:\n") for row in cur: @@ -728,14 +712,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print foreign-key constraints (there are none if no triggers) */ if tableinfo.hastriggers: - sql = ( - "SELECT conname,\n" - " pg_catalog.pg_get_constraintdef(r.oid, true) as condef\n" - "FROM pg_catalog.pg_constraint r\n" - f"WHERE r.conrelid = '{oid}' AND r.contype = 'f' ORDER BY 1;" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_foreign_key_constraints.sql, params) if cur.rowcount > 0: status.append("Foreign-key constraints:\n") for row in cur: @@ -744,14 +721,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print incoming foreign-key references (none if no triggers) */ if tableinfo.hastriggers: - sql = ( - "SELECT conrelid::pg_catalog.regclass, conname,\n" - " pg_catalog.pg_get_constraintdef(c.oid, true) as condef\n" - "FROM pg_catalog.pg_constraint c\n" - f"WHERE c.confrelid = '{oid}' AND c.contype = 'f' ORDER BY 1;" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_foreign_key_references.sql, params) if cur.rowcount > 0: status.append("Referenced by:\n") for row in cur: @@ -761,14 +731,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print rules */ if tableinfo.hasrules and tableinfo.relkind != "m": - sql = ( - "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), " - "ev_enabled\n" - "FROM pg_catalog.pg_rewrite r\n" - f"WHERE r.ev_class = '{oid}' ORDER BY 1;" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_foreign_key_references.sql, params) if cur.rowcount > 0: for category in range(4): have_heading = False @@ -802,49 +765,18 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print partition info */ if tableinfo.relispartition: - sql = ( - "select quote_ident(np.nspname) || '.' ||\n" - " quote_ident(cp.relname) || ' ' ||\n" - " pg_get_expr(cc.relpartbound, cc.oid, true) as partition_of,\n" - " pg_get_partition_constraintdef(cc.oid) as partition_constraint\n" - "from pg_inherits i\n" - "inner join pg_class cp\n" - "on cp.oid = i.inhparent\n" - "inner join pg_namespace np\n" - "on np.oid = cp.relnamespace\n" - "inner join pg_class cc\n" - "on cc.oid = i.inhrelid\n" - "inner join pg_namespace nc\n" - "on nc.oid = cc.relnamespace\n" - f"where cc.oid = {oid}" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_partintion_info.sql, params) for row in cur: status.append(f"Partition of: {row[0]}\n") status.append(f"Partition constraint: {row[1]}\n") if tableinfo.relkind == "p": # /* print partition key */ - sql = f"select pg_get_partkeydef({oid})" - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_partintion_key.sql, params) for row in cur: status.append(f"Partition key: {row[0]}\n") # /* print list of partitions */ - sql = ( - "select quote_ident(n.nspname) || '.' ||\n" - " quote_ident(c.relname) || ' ' ||\n" - " pg_get_expr(c.relpartbound, c.oid, true)\n" - "from pg_inherits i\n" - "inner join pg_class c\n" - "on c.oid = i.inhrelid\n" - "inner join pg_namespace n\n" - "on n.oid = c.relnamespace\n" - f"where i.inhparent = {oid} order by 1" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_partintions_list.sql, params) if cur.rowcount > 0: if verbose: first = True @@ -867,14 +799,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print rules */ if tableinfo.hasrules: - sql = ( - "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n" - "FROM pg_catalog.pg_rewrite r\n" - f"WHERE r.ev_class = '{oid}' AND r.rulename != '_RETURN' ORDER BY 1;" - ) - - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_view_rules.sql, params) if cur.rowcount > 0: status.append("Rules:\n") for row in cur: @@ -888,24 +813,9 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # */ if tableinfo.hastriggers: if cur.connection.info.server_version > 90000: - sql = f"""SELECT t.tgname, - pg_catalog.pg_get_triggerdef(t.oid, true), - t.tgenabled - FROM pg_catalog.pg_trigger t - WHERE t.tgrelid = '{oid}' AND NOT t.tgisinternal - ORDER BY 1 - """ + cur.execute(queries.get_triggers_info_9.sql, params) else: - sql = f"""SELECT t.tgname, - pg_catalog.pg_get_triggerdef(t.oid), - t.tgenabled - FROM pg_catalog.pg_trigger t - WHERE t.tgrelid = '{oid}' - ORDER BY 1 - """ - - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_triggers_info.sql, params) if cur.rowcount > 0: # /* # * split the output into 4 different categories. Enabled triggers, @@ -963,16 +873,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print foreign server name */ if tableinfo.relkind == "f": # /* Footer information about foreign table */ - sql = f"""SELECT s.srvname,\n - array_to_string(ARRAY(SELECT - quote_ident(option_name) || ' ' || - quote_literal(option_value) FROM - pg_options_to_table(ftoptions)), ', ') - FROM pg_catalog.pg_foreign_table f,\n - pg_catalog.pg_foreign_server s\n - WHERE f.ftrelid = {oid} AND s.oid = f.ftserver;""" - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_foreign_table_name.sql, params) row = cur.fetchone() # /* Print server name */ @@ -984,15 +885,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print inherited tables */ if not tableinfo.relispartition: - sql = ( - "SELECT c.oid::pg_catalog.regclass\n" - "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n" - "WHERE c.oid = i.inhparent\n" - f" AND i.inhrelid = '{oid}'\n" - "ORDER BY inhseqno" - ) - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_foreign_inherited_tables.sql, params) spacer = "" if cur.rowcount > 0: status.append("Inherits") @@ -1007,25 +900,9 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # /* print child tables */ if cur.connection.info.server_version > 90000: - sql = f"""SELECT c.oid::pg_catalog.regclass - FROM pg_catalog.pg_class c, - pg_catalog.pg_inherits i - WHERE c.oid = i.inhrelid - AND i.inhparent = '{oid}' - ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text; - """ + cur.execute(queries.get_foreign_child_tables_9.sql, params) else: - sql = f"""SELECT c.oid::pg_catalog.regclass - FROM pg_catalog.pg_class c, - pg_catalog.pg_inherits i - WHERE c.oid = i.inhrelid - AND i.inhparent = '{oid}' - ORDER BY c.oid; - """ - - log.debug(sql) - cur.execute(sql) - + cur.execute(queries.get_foreign_child_tables.sql, params) if not verbose: # /* print the number of child tables, if any */ if cur.rowcount > 0: From 512146d9d92dfefcae01bf3ecd03740369d0d7e5 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 8 Nov 2023 17:03:01 +0000 Subject: [PATCH 16/30] get footer info --- dbcommands.sql | 215 ++++++++++++++++++++++++++++++++++++++++ pgspecial/dbcommands.py | 130 +++++------------------- 2 files changed, 239 insertions(+), 106 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index f5de43f..910c436 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1385,4 +1385,219 @@ FROM pg_catalog.pg_class c, WHERE c.oid = i.inhrelid AND i.inhparent = :oid ORDER BY c.oid; +-- name: get_footer_info_12 +SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , + (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) + FOR 128) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS attrdef, + a.attnotnull, + (SELECT c.collname + FROM pg_catalog.pg_collation c, + pg_catalog.pg_type t + WHERE c.oid = a.attcollation + AND t.oid = a.atttypid + AND a.attcollation <> t.typcollation) AS attcollation, + a.attidentity, + a.attgenerated, + CASE + WHEN a.attnum <= + (SELECT i.indnkeyatts + FROM pg_catalog.pg_index i + WHERE i.indexrelid = :oid) THEN 'yes' + ELSE 'no' + END AS indexkey, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef, + CASE + WHEN attfdwoptions IS NULL THEN '' + ELSE '(' || array_to_string(array + (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) + FROM pg_options_to_table(attfdwoptions)), ', ') || ')' + END AS attfdwoptions, + a.attstorage, + CASE + WHEN a.attstattarget=-1 THEN NULL + ELSE a.attstattarget + END AS attstattarget, + pg_catalog.col_description(a.attrelid, a.attnum) AS attdescr +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum; +-- name: get_footer_info_11 +SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , + (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) + FOR 128) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS attrdef, + a.attnotnull, + (SELECT c.collname + FROM pg_catalog.pg_collation c, + pg_catalog.pg_type t + WHERE c.oid = a.attcollation + AND t.oid = a.atttypid + AND a.attcollation <> t.typcollation) AS attcollation, + a.attidentity, + ''::pg_catalog.char AS attgenerated, + CASE + WHEN a.attnum <= + (SELECT i.indnkeyatts + FROM pg_catalog.pg_index i + WHERE i.indexrelid = :oid) THEN 'yes' + ELSE 'no' + END AS indexkey, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef, + CASE + WHEN attfdwoptions IS NULL THEN '' + ELSE '(' || array_to_string(array + (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) + FROM pg_options_to_table(attfdwoptions)), ', ') || ')' + END AS attfdwoptions, + a.attstorage, + CASE + WHEN a.attstattarget=-1 THEN NULL + ELSE a.attstattarget + END AS attstattarget, + pg_catalog.col_description(a.attrelid, a.attnum) AS attdescr +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum; +-- name: get_footer_info_10 +SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , + (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) + FOR 128) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS attrdef, + a.attnotnull, + (SELECT c.collname + FROM pg_catalog.pg_collation c, + pg_catalog.pg_type t + WHERE c.oid = a.attcollation + AND t.oid = a.atttypid + AND a.attcollation <> t.typcollation) AS attcollation, + a.attidentity, + ''::pg_catalog.char AS attgenerated, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef, + CASE + WHEN attfdwoptions IS NULL THEN '' + ELSE '(' || array_to_string(array + (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) + FROM pg_options_to_table(attfdwoptions)), ', ') || ')' + END AS attfdwoptions, + a.attstorage, + CASE + WHEN a.attstattarget=-1 THEN NULL + ELSE a.attstattarget + END AS attstattarget, + pg_catalog.col_description(a.attrelid, a.attnum) AS attdescr +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum; +-- name: get_footer_info_902 +SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , + (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) + FOR 128) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS attrdef, + a.attnotnull, + (SELECT c.collname + FROM pg_catalog.pg_collation c, + pg_catalog.pg_type t + WHERE c.oid = a.attcollation + AND t.oid = a.atttypid + AND a.attcollation <> t.typcollation) AS attcollation, + ''::pg_catalog.char AS attidentity, + ''::pg_catalog.char AS attgenerated, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef, + CASE + WHEN attfdwoptions IS NULL THEN '' + ELSE '(' || array_to_string(array + (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) + FROM pg_options_to_table(attfdwoptions)), ', ') || ')' + END AS attfdwoptions, + a.attstorage, + CASE + WHEN a.attstattarget=-1 THEN NULL + ELSE a.attstattarget + END AS attstattarget, + pg_catalog.col_description(a.attrelid, a.attnum) AS attdescr +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum; +-- name: get_footer_info_901 +SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , + (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) + FOR 128) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS attrdef, + a.attnotnull, + (SELECT c.collname + FROM pg_catalog.pg_collation c, + pg_catalog.pg_type t + WHERE c.oid = a.attcollation + AND t.oid = a.atttypid + AND a.attcollation <> t.typcollation) AS attcollation, + ''::pg_catalog.char AS attidentity, + ''::pg_catalog.char AS attgenerated, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef, + NULL AS attfdwoptions, + a.attstorage, + CASE + WHEN a.attstattarget=-1 THEN NULL + ELSE a.attstattarget + END AS attstattarget, + pg_catalog.col_description(a.attrelid, a.attnum) AS attdescr +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum; +-- name: get_footer_info +SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype, + (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) + FOR 128) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef) AS attrdef, + a.attnotnull, + NULL AS attcollation, + ''::pg_catalog.char AS attidentity, + ''::pg_catalog.char AS attgenerated, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef, + NULL AS attfdwoptions, + a.attstorage, + CASE + WHEN a.attstattarget=-1 THEN NULL + ELSE a.attstattarget + END AS attstattarget, + pg_catalog.col_description(a.attrelid, a.attnum) AS attdescr +FROM pg_catalog.pg_attribute a +WHERE a.attrelid = :oid + AND a.attnum > 0 + AND NOT a.attisdropped +ORDER BY a.attnum; diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 1b7d727..ed79f4d 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -393,72 +393,19 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): seq_values = cur.fetchone() - sql = """SELECT a.attname, - pg_catalog.format_type(a.atttypid, a.atttypmod) as atttype - , (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128) - FROM pg_catalog.pg_attrdef d - WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) as attrdef, - a.attnotnull""" - if cur.connection.info.server_version >= 90100: - sql += """,\n(SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t - WHERE c.oid = a.attcollation - AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation""" - else: - sql += ",\n NULL AS attcollation" - if cur.connection.info.server_version >= 100000: - sql += ",\n a.attidentity" - else: - sql += ",\n ''::pg_catalog.char AS attidentity" if cur.connection.info.server_version >= 120000: - sql += ",\n a.attgenerated" - else: - sql += ",\n ''::pg_catalog.char AS attgenerated" - # index, or partitioned index - if tableinfo.relkind == "i" or tableinfo.relkind == "I": - if cur.connection.info.server_version >= 110000: - sql += ( - ",\n CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i " - "WHERE i.indexrelid = '{oid}') THEN 'yes' ELSE 'no' END AS indexkey" - ) - sql += ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef" - else: - sql += """,\n NULL AS indexdef""" - if tableinfo.relkind == "f" and cur.connection.info.server_version >= 90200: - sql += """, CASE WHEN attfdwoptions IS NULL THEN '' ELSE '(' || - array_to_string(ARRAY(SELECT quote_ident(option_name) || ' ' - || quote_literal(option_value) FROM - pg_options_to_table(attfdwoptions)), ', ') || ')' END AS attfdwoptions""" + cur.execute(queries.get_footer_info_12.sql, params) + elif cur.connection.info.server_version >= 110000: + cur.execute(queries.get_footer_info_11.sql, params) + elif cur.connection.info.server_version >= 100000: + cur.execute(queries.get_footer_info_10.sql, params) + elif cur.connection.info.server_version >= 90200: + cur.execute(queries.get_footer_info_902.sql, params) + elif cur.connection.info.server_version >= 90100: + cur.execute(queries.get_footer_info_901.sql, params) else: - sql += """, NULL AS attfdwoptions""" - if verbose: - sql += """, a.attstorage""" - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "i" - or tableinfo.relkind == "I" - or tableinfo.relkind == "m" - or tableinfo.relkind == "f" - or tableinfo.relkind == "p" - ): - sql += ( - ",\n CASE WHEN a.attstattarget=-1 THEN " - "NULL ELSE a.attstattarget END AS attstattarget" - ) - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "v" - or tableinfo.relkind == "m" - or tableinfo.relkind == "f" - or tableinfo.relkind == "p" - or tableinfo.relkind == "c" - ): - sql += ",\n pg_catalog.col_description(a.attrelid, a.attnum) as attdescr" - - sql += f""" FROM pg_catalog.pg_attribute a WHERE a.attrelid = '{oid}' AND - a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum; """ - - log.debug(sql) - cur.execute(sql) + cur.execute(queries.get_footer_info.sql, params) + res = cur.fetchall() att_cols = {x.name: i for i, x in enumerate(cur.description)} @@ -466,14 +413,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): headers = ["Column", "Type"] show_modifiers = False - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "p" - or tableinfo.relkind == "v" - or tableinfo.relkind == "m" - or tableinfo.relkind == "f" - or tableinfo.relkind == "c" - ): + if tableinfo.relkind in ["r", "p", "v", "m", "f", "c"]: headers.append("Modifiers") show_modifiers = True @@ -488,20 +428,11 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if verbose: headers.append("Storage") - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "m" - or tableinfo.relkind == "f" - ): + if tableinfo.relkind in ["r", "m", "f"]: headers.append("Stats target") # Column comments, if the relkind supports this feature. */ - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "v" - or tableinfo.relkind == "m" - or tableinfo.relkind == "c" - or tableinfo.relkind == "f" - ): + if tableinfo.relkind in ["r", "v", "m", "c", "f"]: + # do something headers.append("Description") view_def = "" @@ -560,24 +491,15 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): else: cell.append("???") - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "m" - or tableinfo.relkind == "f" - ): + # the logic had additional types when adding to the query + # but it was only used with the ones here. + if tableinfo.relkind in ["r", "m", "f"]: # ["i", "I", "p"] cell.append(row[att_cols["attstattarget"]]) # /* Column comments, if the relkind supports this feature. */ - if ( - tableinfo.relkind == "r" - or tableinfo.relkind == "v" - or tableinfo.relkind == "m" - or tableinfo.relkind == "c" - or tableinfo.relkind == "f" - ): + if tableinfo.relkind in ["r", "v", "m", "c", "f"]: # ["p"] cell.append(row[att_cols["attdescr"]]) cells.append(cell) - # Make Footers status = [] @@ -643,12 +565,8 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # * don't print anything. # */ - elif ( - tableinfo.relkind == "r" - or tableinfo.relkind == "p" - or tableinfo.relkind == "m" - or tableinfo.relkind == "f" - ): + elif tableinfo.relkind in ["r", "p", "m", "f"]: + # do something # /* Footer information about a table */ if tableinfo.hasindex: @@ -831,10 +749,10 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): # */ tgenabled = row[2] if category == 0: - if tgenabled == "O" or tgenabled == True: + if tgenabled == "O" or tgenabled is True: list_trigger = True elif category == 1: - if tgenabled == "D" or tgenabled == False: + if tgenabled == "D" or tgenabled is False: list_trigger = True elif category == 2: if tgenabled == "A": @@ -842,7 +760,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): elif category == 3: if tgenabled == "R": list_trigger = True - if list_trigger == False: + if list_trigger is False: continue # /* Print the category heading once */ From 0675802888cc54af9a94eba17074b6130017c304 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 8 Nov 2023 17:06:22 +0000 Subject: [PATCH 17/30] update changelog --- changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.rst b/changelog.rst index cbdb573..665dca7 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,3 +1,7 @@ +unrelased +================== +* refactor dbcommands.py to use queries stored in external file + 2.1.1 (2023-10-29) ================== From a5c69f01bb1dbb4ee3953971ca7cc56170f3c01d Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 8 Nov 2023 17:17:26 +0000 Subject: [PATCH 18/30] remove unused import --- pgspecial/dbcommands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index ed79f4d..7c96f87 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -4,7 +4,6 @@ import subprocess from collections import namedtuple -from psycopg.sql import SQL import aiosql from .main import special_command From da8668ad972ab356a06de5059237c6307b8ea00a Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 8 Nov 2023 18:35:32 +0000 Subject: [PATCH 19/30] add docker-compose for testing --- docker-compose.yml | 18 ++++++++++++++++++ tests/test_specials.py | 7 +------ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5bde1da --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.8' + +services: + db: + image: postgres:15 + restart: always + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + LC_COLLATE: "en_US.UTF-8" + LC_CTYPE: "en_US.UTF-8" + ports: + - "5432:5432" # default port for postgres + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: diff --git a/tests/test_specials.py b/tests/test_specials.py index 9f93951..49c40ed 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import pytest -from dbutils import dbtest, POSTGRES_USER, foreign_db_environ, fdw_test +from dbutils import dbtest, POSTGRES_USER, foreign_db_environ, fdw_test, SERVER_VERSION import itertools import locale @@ -16,9 +16,6 @@ # instead as that matches the C library function LC_COLLATE = locale.setlocale(locale.LC_COLLATE, None) LC_CTYPE = locale.setlocale(locale.LC_CTYPE, None) -# TODO remove this line -LC_COLLATE = "en_US.UTF-8" -LC_CTYPE = "en_US.UTF-8" @dbtest @@ -325,8 +322,6 @@ def test_slash_dn(executor): """List all schemas.""" results = executor(r"\dn") title = None - # TODO remove this line - SERVER_VERSION = 1 owner = "pg_database_owner" if SERVER_VERSION >= 150001 else POSTGRES_USER rows = [ ("public", owner), From e962548474c4fc691109f20f28605368054920dc Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 8 Nov 2023 19:53:37 +0000 Subject: [PATCH 20/30] fix typos --- dbcommands.sql | 18 +++++++++--------- pgspecial/dbcommands.py | 4 +--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index 910c436..fcfab88 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1064,7 +1064,7 @@ SELECT c.relchecks, FALSE AS relhasoids, pg_catalog.array_to_string(c.reloptions || array (SELECT 'toast.' || x - FROM pg_catalog.unnest(tc.reloptions) x), ', '), + FROM pg_catalog.unnest(tc.reloptions) x), ', ') as suffix, c.reltablespace, CASE WHEN c.reloftype = 0 THEN '' @@ -1084,7 +1084,7 @@ SELECT c.relchecks, c.relhasoids, pg_catalog.array_to_string(c.reloptions || array (SELECT 'toast.' || x - FROM pg_catalog.unnest(tc.reloptions) x), ', '), + FROM pg_catalog.unnest(tc.reloptions) x), ', ') as suffix, c.reltablespace, CASE WHEN c.reloftype = 0 THEN '' @@ -1105,7 +1105,7 @@ SELECT c.relchecks, c.relhasoids, pg_catalog.array_to_string(c.reloptions || array (SELECT 'toast.' || x - FROM pg_catalog.unnest(tc.reloptions) x), ', '), + FROM pg_catalog.unnest(tc.reloptions) x), ', ') as suffix, c.reltablespace, CASE WHEN c.reloftype = 0 THEN '' @@ -1125,9 +1125,9 @@ SELECT c.relchecks, c.relhasoids, pg_catalog.array_to_string(c.reloptions || array (SELECT 'toast.' || x - FROM pg_catalog.unnest(tc.reloptions) x), ', '), + FROM pg_catalog.unnest(tc.reloptions) x), ', ') as suffix, c.reltablespace, - 0 AS reloftype, + '' AS reloftype, 'p' AS relpersistence, FALSE AS relispartition FROM pg_catalog.pg_class c @@ -1142,9 +1142,9 @@ SELECT c.relchecks, c.relhasoids, pg_catalog.array_to_string(c.reloptions || array (SELECT 'toast.' || x - FROM pg_catalog.unnest(tc.reloptions) x), ', '), + FROM pg_catalog.unnest(tc.reloptions) x), ', ') as suffix, c.reltablespace, - 0 AS reloftype, + '' AS reloftype, 'p' AS relpersistence, FALSE AS relispartition FROM pg_catalog.pg_class c @@ -1152,15 +1152,15 @@ LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) WHERE c.oid = :oid -- name: describe_one_table_details --- docs: no suffix here SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, c.reltriggers > 0 AS relhastriggers, c.relhasoids, + '' as suffix, c.reltablespace, - 0 AS reloftype, + '' AS reloftype, 'p' AS relpersistence, FALSE AS relispartition FROM pg_catalog.pg_class c diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 7c96f87..efbe754 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -133,9 +133,6 @@ def _describe_extension(cur, oid): return if verbose: - # TODO - use the join query instead of looping. - # May need refactoring some more code. - # cur.execute(queries.list_extensions_verbose.sql, {"pattern": pattern}) extensions = _find_extensions(cur, pattern) if extensions: @@ -377,6 +374,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if cur.rowcount > 0: # TODO if not verbose - drop suffix + # But it seems that the suffix is not included? tableinfo = TableInfo._make(cur.fetchone()) else: return None, None, None, f"Did not find any relation with OID {oid}." From a2d35bbee84265c0795692740c859e360f6402d1 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 8 Nov 2023 23:06:10 +0000 Subject: [PATCH 21/30] add multiple test envs --- docker-compose.yml | 54 +++++++++++++++++++++++++++++++++++----------- run_test_matrix.sh | 4 ++++ 2 files changed, 46 insertions(+), 12 deletions(-) create mode 100755 run_test_matrix.sh diff --git a/docker-compose.yml b/docker-compose.yml index 5bde1da..c9688b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,48 @@ -version: '3.8' +version: '3' services: - db: - image: postgres:15 - restart: always + postgres11: + image: postgres:11.18-alpine + command: postgres -c shared_preload_libraries=pg_stat_statements environment: + POSTGRES_USER: postgres + POSTGRES_DB: ecto_psql_extras_test POSTGRES_PASSWORD: postgres + ports: + - '5441:5432' + postgres12: + image: postgres:12.13-alpine + command: postgres -c shared_preload_libraries=pg_stat_statements + environment: POSTGRES_USER: postgres - LC_COLLATE: "en_US.UTF-8" - LC_CTYPE: "en_US.UTF-8" + POSTGRES_DB: ecto_psql_extras_test + POSTGRES_PASSWORD: postgres ports: - - "5432:5432" # default port for postgres - volumes: - - postgres_data:/var/lib/postgresql/data - -volumes: - postgres_data: + - '5442:5432' + postgres13: + image: postgres:13.3-alpine + command: postgres -c shared_preload_libraries=pg_stat_statements + environment: + POSTGRES_USER: postgres + POSTGRES_DB: ecto_psql_extras_test + POSTGRES_PASSWORD: postgres + ports: + - '5443:5432' + postgres14: + image: postgres:14.6-alpine + command: postgres -c shared_preload_libraries=pg_stat_statements + environment: + POSTGRES_USER: postgres + POSTGRES_DB: ecto_psql_extras_test + POSTGRES_PASSWORD: postgres + ports: + - '5444:5432' + postgres15: + image: postgres:15.1-alpine + command: postgres -c shared_preload_libraries=pg_stat_statements + environment: + POSTGRES_USER: postgres + POSTGRES_DB: ecto_psql_extras_test + POSTGRES_PASSWORD: postgres + ports: + - '5445:5432' diff --git a/run_test_matrix.sh b/run_test_matrix.sh new file mode 100755 index 0000000..b5eab5b --- /dev/null +++ b/run_test_matrix.sh @@ -0,0 +1,4 @@ +for port in {5441..5445}; do + PGPORT=$port py.test +done + From f3c65ab937fcb0a03aeee7757383dcfe67ec2258 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Fri, 10 Nov 2023 19:06:37 +0000 Subject: [PATCH 22/30] use title helper from other pr --- dbcommands.sql | 333 ++++++++++++++++++++-------------------- pgspecial/dbcommands.py | 36 +++-- tests/test_specials.py | 8 +- 3 files changed, 190 insertions(+), 187 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index fcfab88..a9c6ef4 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1,29 +1,29 @@ -- name: version SELECT version() -- name: list_databases -SELECT d.datname AS "Name", - pg_catalog.pg_get_userbyid(d.datdba) AS "Owner", - pg_catalog.pg_encoding_to_char(d.encoding) AS "Encoding", - d.datcollate AS "Collate", - d.datctype AS "Ctype", - pg_catalog.array_to_string(d.datacl, e'\n') AS "Access privileges" +SELECT d.datname AS name, + pg_catalog.pg_get_userbyid(d.datdba) AS owner, + pg_catalog.pg_encoding_to_char(d.encoding) AS encoding, + d.datcollate AS collate, + d.datctype AS ctype, + pg_catalog.array_to_string(d.datacl, e'\n') AS access_privileges FROM pg_catalog.pg_database d WHERE d.datname ~ %s ORDER BY 1 -- name: list_databases_verbose -SELECT d.datname AS "Name", - pg_catalog.pg_get_userbyid(d.datdba) AS "Owner", - pg_catalog.pg_encoding_to_char(d.encoding) AS "Encoding", - d.datcollate AS "Collate", - d.datctype AS "Ctype", - pg_catalog.array_to_string(d.datacl, e'\n') AS "Access privileges", +SELECT d.datname AS name, + pg_catalog.pg_get_userbyid(d.datdba) AS owner, + pg_catalog.pg_encoding_to_char(d.encoding) AS encoding, + d.datcollate AS collate, + d.datctype AS ctype, + pg_catalog.array_to_string(d.datacl, e'\n') AS access_privileges, CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname)) ELSE 'No Access' - END AS "Size", - t.spcname AS "Tablespace", - pg_catalog.shobj_description(d.oid, 'pg_database') AS "Description" + END AS size, + t.spcname AS tablespace, + pg_catalog.shobj_description(d.oid, 'pg_database') AS description FROM pg_catalog.pg_database d JOIN pg_catalog.pg_tablespace t ON d.dattablespace = t.oid WHERE d.datname ~ %s @@ -70,8 +70,8 @@ SELECT u.usename AS rolname, FROM pg_catalog.pg_user u -- name: list_privileges -- docs: ("\\dp", "\\dp [pattern]", "List roles.", aliases=("\\z",)) -SELECT n.nspname AS "Schema", - c.relname AS "Name", +SELECT n.nspname AS schema, + c.relname AS name, CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' @@ -84,14 +84,14 @@ SELECT n.nspname AS "Schema", WHEN 'f' THEN 'foreign table' WHEN 't' THEN 'toast table' WHEN 'c' THEN 'composite type' - END AS "Type", - pg_catalog.array_to_string(c.relacl, e'\n') AS "Access privileges", + END AS type, + pg_catalog.array_to_string(c.relacl, e'\n') AS access_privileges, pg_catalog.array_to_string(ARRAY (SELECT attname || e':\n ' || pg_catalog.array_to_string(attacl, e'\n ') FROM pg_catalog.pg_attribute a WHERE attrelid = c.oid AND NOT attisdropped - AND attacl IS NOT NULL), e'\n') AS "Column privileges", + AND attacl IS NOT NULL), e'\n') AS column_privileges, pg_catalog.array_to_string(ARRAY (SELECT polname || CASE @@ -116,7 +116,7 @@ SELECT n.nspname AS "Schema", ELSE e'' END FROM pg_catalog.pg_policy pol - WHERE polrelid = c.oid), e'\n') AS "Policies" + WHERE polrelid = c.oid), e'\n') AS policies FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'p') @@ -130,16 +130,16 @@ ORDER BY 1, 2 -- name: list_default_privileges -- docs: ("\\ddp", "\\ddp [pattern]", "Lists default access privilege settings.") -SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS "Owner", - n.nspname AS "Schema", +SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS owner, + n.nspname AS schema, CASE d.defaclobjtype WHEN 'r' THEN 'table' WHEN 'S' THEN 'sequence' WHEN 'f' THEN 'function' WHEN 'T' THEN 'type' WHEN 'n' THEN 'schema' - END as "Type", - pg_catalog.array_to_string(d.defaclacl, e'\n') AS "Access privileges" + END as Type, + pg_catalog.array_to_string(d.defaclacl, e'\n') AS access_privileges FROM pg_catalog.pg_default_acl d LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace WHERE (n.nspname OPERATOR(pg_catalog. ~) %s COLLATE pg_catalog.default @@ -149,13 +149,13 @@ ORDER BY 1, 2, 3 -- name: list_tablespaces -- docs: ("\\db", "\\db[+] [pattern]", "List tablespaces.") SELECT - n.spcname AS "Name", - pg_catalog.pg_get_userbyid(n.spcowner) AS "Owner", + n.spcname AS name, + pg_catalog.pg_get_userbyid(n.spcowner) AS owner, CASE WHEN (EXISTS ( SELECT * FROM pg_proc WHERE proname = 'pg_tablespace_location')) THEN pg_catalog.pg_tablespace_location(n.oid) ELSE 'Not supported' - END AS "Location" + END AS location FROM pg_catalog.pg_tablespace n WHERE @@ -164,10 +164,10 @@ ORDER BY 1 -- name: list_schemas_verbose -- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") SELECT - n.nspname AS "Name", - pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner", - pg_catalog.array_to_string(n.nspacl, E'\\n') AS "Access privileges", - pg_catalog.obj_description(n.oid, 'pg_namespace') AS "Description" + n.nspname AS name, + pg_catalog.pg_get_userbyid(n.nspowner) AS owner, + pg_catalog.array_to_string(n.nspacl, E'\\n') AS access_privileges, + pg_catalog.obj_description(n.oid, 'pg_namespace') AS description FROM pg_catalog.pg_namespace n WHERE @@ -179,8 +179,8 @@ ORDER BY 1 -- name: list_schemas -- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") SELECT - n.nspname AS "Name", - pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner" + n.nspname AS name, + pg_catalog.pg_get_userbyid(n.nspowner) AS owner FROM pg_catalog.pg_namespace n WHERE @@ -198,7 +198,7 @@ WHERE e.extname ~ :schema ORDER BY 1, 2; -- name: describe_extension -SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS "Object Description" +SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS object_description FROM pg_catalog.pg_depend WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND refobjid = :oid @@ -206,10 +206,10 @@ WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass ORDER BY 1 -- name: list_extensions SELECT - e.extname AS "Name", - e.extversion AS "Version", - n.nspname AS "Schema", - c.description AS "Description" + e.extname AS name, + e.extversion AS version, + n.nspname AS schema, + c.description AS description FROM pg_catalog.pg_extension e LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace @@ -237,13 +237,13 @@ ORDER BY 1, SELECT (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t - WHERE t.tokid = m.maptokentype ) AS "Token", + WHERE t.tokid = m.maptokentype ) AS token, pg_catalog.btrim(ARRAY (SELECT mm.mapdict::pg_catalog.regdictionary FROM pg_catalog.pg_ts_config_map AS mm WHERE mm.mapcfg = m.mapcfg AND mm.maptokentype = m.maptokentype - ORDER BY mapcfg, maptokentype, mapseqno) :: pg_catalog.text, '{}') AS "Dictionaries" + ORDER BY mapcfg, maptokentype, mapseqno) :: pg_catalog.text, '{}') AS dictionaries FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m WHERE c.oid = :oid @@ -253,9 +253,9 @@ GROUP BY m.mapcfg, c.cfgparser ORDER BY 1; -- name: list_text_search_configurations -SELECT n.nspname AS "Schema", - c.cfgname AS "Name", - pg_catalog.obj_description(c.oid, 'pg_ts_config') AS "Description" +SELECT n.nspname AS schema, + c.cfgname AS name, + pg_catalog.obj_description(c.oid, 'pg_ts_config') AS description FROM pg_catalog.pg_ts_config c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace WHERE c.cfgname ~ :schema @@ -264,8 +264,8 @@ ORDER BY 1, -- name: list_objects_verbose -- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes SELECT - n.nspname AS "Schema", - c.relname AS "Name", + n.nspname AS schema, + c.relname AS name, CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' @@ -275,10 +275,10 @@ SELECT WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' END - as "Type", - pg_catalog.pg_get_userbyid(c.relowner) AS "Owner", - pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as "Size", - pg_catalog.obj_description(c.oid, 'pg_class') as "Description" + AS type, + pg_catalog.pg_get_userbyid(c.relowner) AS owner, + pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) as size, + pg_catalog.obj_description(c.oid, 'pg_class') as description FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace @@ -297,8 +297,8 @@ ORDER BY 1, 2 -- name: list_objects -- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes SELECT - n.nspname AS "Schema", - c.relname AS "Name", + n.nspname AS schema, + c.relname AS name, CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' @@ -308,8 +308,8 @@ SELECT WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' END - as "Type", - pg_catalog.pg_get_userbyid(c.relowner) AS "Owner" + as type, + pg_catalog.pg_get_userbyid(c.relowner) AS owner FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace @@ -328,21 +328,21 @@ ORDER BY 1, 2 -- name: list_functions -- docs: ("\\df", "\\df[+] [pattern]", "List functions.") -SELECT n.nspname AS "schema", - p.proname AS "name", - pg_catalog.pg_get_function_result(p.oid) AS "result_data_type", - pg_catalog.pg_get_function_arguments(p.oid) AS "argument_data_types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_function_result(p.oid) AS result_data_type, + pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, CASE WHEN p.prokind = 'a' THEN 'agg' WHEN p.prokind = 'w' THEN 'window' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "type", - :provolatile AS "volatility", - pg_catalog.pg_get_userbyid(p.proowner) AS "owner", - l.lanname AS "language", - p.prosrc AS "source_code", - pg_catalog.obj_description(p.oid, 'pg_proc') AS "description" + END AS type, + :provolatile AS volatility, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + l.lanname AS language, + p.prosrc AS source_code, + pg_catalog.obj_description(p.oid, 'pg_proc') AS description FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang @@ -352,25 +352,25 @@ ORDER BY 1, 2, 4 -- name: list_functions__verbose_11 -SELECT n.nspname AS "Schema", - p.proname AS "Name", - pg_catalog.pg_get_function_result(p.oid) AS "Result data type", - pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_function_result(p.oid) AS result_data_type, + pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, CASE WHEN p.prokind = 'a' THEN 'agg' WHEN p.prokind = 'w' THEN 'window' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "Type", + END AS type, CASE WHEN p.provolatile = 'i' THEN 'immutable' WHEN p.provolatile = 's' THEN 'stable' WHEN p.provolatile = 'v' THEN 'volatile' - END AS "Volatility", - pg_catalog.pg_get_userbyid(p.proowner) AS "Owner", - l.lanname AS "Language", - p.prosrc AS "Source code", - pg_catalog.obj_description(p.oid, 'pg_proc') AS "Description" + END AS volatility, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + l.lanname AS language, + p.prosrc AS source_code, + pg_catalog.obj_description(p.oid, 'pg_proc') AS description FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang @@ -391,25 +391,25 @@ ORDER BY 1, -- name: list_functions_verbose_9 -SELECT n.nspname AS "Schema", - p.proname AS "Name", - pg_catalog.pg_get_function_result(p.oid) AS "Result data type", - pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_function_result(p.oid) AS result_data_type, + pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, CASE WHEN p.proisagg THEN 'agg' WHEN p.proiswindow THEN 'window' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "Type", + END AS type, CASE WHEN p.provolatile = 'i' THEN 'immutable' WHEN p.provolatile = 's' THEN 'stable' WHEN p.provolatile = 'v' THEN 'volatile' - END AS "Volatility", - pg_catalog.pg_get_userbyid(p.proowner) AS "Owner", - l.lanname AS "Language", - p.prosrc AS "Source code", - pg_catalog.obj_description(p.oid, 'pg_proc') AS "Description" + END AS volatility, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + l.lanname AS language, + p.prosrc AS source_code, + pg_catalog.obj_description(p.oid, 'pg_proc') AS description FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang @@ -430,23 +430,23 @@ ORDER BY 1, -- name: list_functions__verbose_9 -SELECT n.nspname AS "Schema", - p.proname AS "Name", - pg_catalog.format_type(p.prorettype, NULL) AS "Result data type", - pg_catalog.oidvectortypes(p.proargtypes) AS "Argument data types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.format_type(p.prorettype, NULL) AS result_data_type, + pg_catalog.oidvectortypes(p.proargtypes) AS argument_data_types, CASE WHEN p.proisagg THEN 'agg' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "Type" CASE + END AS type CASE WHEN p.provolatile = 'i' THEN 'immutable' WHEN p.provolatile = 's' THEN 'stable' WHEN p.provolatile = 'v' THEN 'volatile' - END AS "Volatility", - pg_catalog.pg_get_userbyid(p.proowner) AS "Owner", - l.lanname AS "Language", - p.prosrc AS "Source code", - pg_catalog.obj_description(p.oid, 'pg_proc') AS "Description" + END AS volatility, + pg_catalog.pg_get_userbyid(p.proowner) AS owner, + l.lanname AS language, + p.prosrc AS source_code, + pg_catalog.obj_description(p.oid, 'pg_proc') AS description FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang @@ -466,16 +466,16 @@ ORDER BY 1, 4 -- name: list_functions_11 -SELECT n.nspname AS "Schema", - p.proname AS "Name", - pg_catalog.pg_get_function_result(p.oid) AS "Result data type", - pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_function_result(p.oid) AS result_data_type, + pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, CASE WHEN p.prokind = 'a' THEN 'agg' WHEN p.prokind = 'w' THEN 'window' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "Type" + END AS type FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE p.proname ~ :function_pattern @@ -494,16 +494,16 @@ ORDER BY 1, 4 -- name: list_functions_9 -SELECT n.nspname AS "Schema", - p.proname AS "Name", - pg_catalog.pg_get_function_result(p.oid) AS "Result data type", - pg_catalog.pg_get_function_arguments(p.oid) AS "Argument data types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.pg_get_function_result(p.oid) AS result_data_type, + pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, CASE WHEN p.proisagg THEN 'agg' WHEN p.proiswindow THEN 'window' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "Type" + END AS type FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE p.proname ~ :function_pattern @@ -522,15 +522,15 @@ ORDER BY 1, 4 -- name: list_functions_9 -SELECT n.nspname AS "Schema", - p.proname AS "Name", - pg_catalog.format_type(p.prorettype, NULL) AS "Result data type", - pg_catalog.oidvectortypes(p.proargtypes) AS "Argument data types", +SELECT n.nspname AS schema, + p.proname AS name, + pg_catalog.format_type(p.prorettype, NULL) AS result_data_type, + pg_catalog.oidvectortypes(p.proargtypes) AS argument_data_types, CASE WHEN p.proisagg THEN 'agg' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS "Type" + END AS type FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE p.proname ~ :function_pattern @@ -552,19 +552,19 @@ ORDER BY 1, -- name: list_datatype_verbose_9 -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT - n.nspname AS "Schema", - pg_catalog.format_type(t.oid, NULL) AS "Name", - t.typname AS "Internal name", + n.nspname AS schema, + pg_catalog.format_type(t.oid, NULL) AS name, + t.typname AS internal_name, CASE WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) ELSE CAST(t.typlen AS pg_catalog.text) - END AS "Size", + END AS size, pg_catalog.array_to_string(ARRAY ( SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE - e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS "Elements", - pg_catalog.array_to_string(t.typacl, E'\n') AS "Access_privileges", - pg_catalog.obj_description(t.oid, 'pg_type') AS "Description" + e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS elements, + pg_catalog.array_to_string(t.typacl, E'\n') AS access_privileges, + pg_catalog.obj_description(t.oid, 'pg_type') AS description FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace @@ -595,9 +595,9 @@ ORDER BY 1, 2 -- name: list_datatypes_9 -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT - n.nspname AS "Schema", - pg_catalog.format_type(t.oid, NULL) AS "Name", - pg_catalog.obj_description(t.oid, 'pg_type') as "Description" + n.nspname AS schema, + pg_catalog.format_type(t.oid, NULL) AS name, + pg_catalog.obj_description(t.oid, 'pg_type') as description FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE (t.typrelid = 0 @@ -626,19 +626,19 @@ ORDER BY 1, 2 -- name: list_datatype_verbose -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT - n.nspname AS "Schema", - pg_catalog.format_type(t.oid, NULL) AS "Name", - t.typname AS "Internal name", + n.nspname AS schema, + pg_catalog.format_type(t.oid, NULL) AS name, + t.typname AS internal_name, CASE WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) ELSE CAST(t.typlen AS pg_catalog.text) - END AS "Size", + END AS size, pg_catalog.array_to_string(ARRAY ( SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE - e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS "Elements", - pg_catalog.array_to_string(t.typacl, E'\n') AS "Access_privileges", - pg_catalog.obj_description(t.oid, 'pg_type') AS "Description" + e.enumtypid = t.oid ORDER BY e.enumsortorder), E'\n') AS elements, + pg_catalog.array_to_string(t.typacl, E'\n') AS access_privileges, + pg_catalog.obj_description(t.oid, 'pg_type') AS description FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace @@ -670,9 +670,9 @@ ORDER BY 1, 2 -- name: list_datatypes -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT - n.nspname AS "Schema", - pg_catalog.format_type(t.oid, NULL) AS "Name", - pg_catalog.obj_description(t.oid, 'pg_type') as "Description" + n.nspname AS schema, + pg_catalog.format_type(t.oid, NULL) AS name, + pg_catalog.obj_description(t.oid, 'pg_type') as description FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE (t.typrelid = 0 @@ -703,9 +703,9 @@ ORDER BY 1, 2 -- name: list_domains_verbose -- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") SELECT - n.nspname AS "Schema", - t.typname AS "Name", - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "Type", + n.nspname AS schema, + t.typname AS name, + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS type, pg_catalog.ltrim((COALESCE(( SELECT (' collate ' || c.collname) @@ -715,14 +715,14 @@ SELECT AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation), '') || CASE WHEN t.typnotnull THEN ' not null' ELSE '' END) || - CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS "Modifier", + CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS modifier, pg_catalog.array_to_string(ARRAY ( SELECT pg_catalog.pg_get_constraintdef(r.oid, TRUE) FROM pg_catalog.pg_constraint AS r - WHERE t.oid = r.contypid), ' ') AS "Check", - pg_catalog.array_to_string(t.typacl, E'\n') AS "Access privileges", - d.description AS "Description" + WHERE t.oid = r.contypid), ' ') AS check, + pg_catalog.array_to_string(t.typacl, E'\n') AS access_privileges, + d.description AS description FROM pg_catalog.pg_type AS t LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace @@ -743,9 +743,9 @@ ORDER BY 1, 2 -- name: list_domains -- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") SELECT - n.nspname AS "Schema", - t.typname AS "Name", - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS "Type", + n.nspname AS schema, + t.typname AS name, + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS type, pg_catalog.ltrim((COALESCE(( SELECT (' collate ' || c.collname) @@ -755,12 +755,12 @@ SELECT AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation), '') || CASE WHEN t.typnotnull THEN ' not null' ELSE '' END) || - CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS "Modifier", + CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS modifier, pg_catalog.array_to_string(ARRAY ( SELECT pg_catalog.pg_get_constraintdef(r.oid, TRUE) FROM pg_catalog.pg_constraint AS r - WHERE t.oid = r.contypid), ' ') AS "Check" + WHERE t.oid = r.contypid), ' ') AS check FROM pg_catalog.pg_type AS t LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace @@ -790,14 +790,14 @@ ORDER BY 2,3 -- name: get_column_info SELECT - a.attname AS "name", - a.attnotnull AS "not_null", - a.attidentity AS "identity", - a.attgenerated AS "generated", - pg_catalog.format_type(a.atttypid, a.atttypmod) AS "data_type", - pg_catalog.col_description(a.attrelid, a.attnum) AS "description", - pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS "index_definition", - pg_catalog.pg_get_viewdef(:oid::pg_catalog.oid, TRUE) AS "view_definition", + a.attname AS name, + a.attnotnull AS not_null, + a.attidentity AS identity, + a.attgenerated AS generated, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type, + pg_catalog.col_description(a.attrelid, a.attnum) AS description, + pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS index_definition, + pg_catalog.pg_get_viewdef(:oid::pg_catalog.oid, TRUE) AS view_definition, ( SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) FOR 128) FROM @@ -805,7 +805,7 @@ SELECT WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum - AND a.atthasdef) AS "default_value", + AND a.atthasdef) AS default_value, ( SELECT c.collname @@ -815,20 +815,20 @@ SELECT WHERE c.oid = a.attcollation AND t.oid = a.atttypid - AND a.attcollation <> t.typcollation) AS "collation", + AND a.attcollation <> t.typcollation) AS collation, CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i WHERE i.indexrelid = :oid) THEN 'yes' ELSE 'no' - END AS "is_key", + END AS is_key, CASE WHEN a.attstattarget = - 1 THEN NULL ELSE a.attstattarget - END AS "stat_target", + END AS stat_target, CASE WHEN attfdwoptions IS NULL THEN '' ELSE '(' || array_to_string(ARRAY (SELECT quote_ident(option_name) || ' ' || quote_literal(option_value) FROM pg_options_to_table(attfdwoptions)), ', ') || ')' - END AS "fdw_options" + END AS fdw_options FROM pg_catalog.pg_attribute a WHERE @@ -878,7 +878,7 @@ WHERE SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) || '.' || - pg_catalog.quote_ident(attname) AS "column" + pg_catalog.quote_ident(attname) AS column FROM pg_catalog.pg_class c INNER JOIN pg_catalog.pg_depend d ON c.oid = d.refobjid @@ -1020,12 +1020,12 @@ pg_catalog.pg_get_functiondef( -- name: list_foreign_tables_verbose -- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) SELECT - n.nspname AS "Schema", - c.relname AS "Name", - 'foreign table' AS "Type", - pg_catalog.pg_get_userbyid(c.relowner) AS "Owner", - pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size", - pg_catalog.obj_description(c.oid, 'pg_class') AS "Description" + n.nspname AS schema, + c.relname AS name, + 'foreign table' AS type, + pg_catalog.pg_get_userbyid(c.relowner) AS owner, + pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS size, + pg_catalog.obj_description(c.oid, 'pg_class') AS description FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace @@ -1040,10 +1040,10 @@ ORDER BY 1, 2 -- name: list_foreign_tables -- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) SELECT - n.nspname AS "Schema", - c.relname AS "Name", - 'foreign table' AS "Type", - pg_catalog.pg_get_userbyid(c.relowner) AS "Owner" + n.nspname AS schema, + c.relname AS name, + 'foreign table' AS type, + pg_catalog.pg_get_userbyid(c.relowner) AS owner FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace @@ -1387,7 +1387,7 @@ WHERE c.oid = i.inhrelid ORDER BY c.oid; -- name: get_footer_info_12 SELECT a.attname, - pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , + pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype, (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) FOR 128) FROM pg_catalog.pg_attrdef d @@ -1474,7 +1474,6 @@ ORDER BY a.attnum; -- name: get_footer_info_10 SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod) AS atttype , - (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, TRUE) FOR 128) FROM pg_catalog.pg_attrdef d diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index efbe754..d5df441 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -37,7 +37,7 @@ def list_databases(cur, pattern, verbose): else: cur.execute(queries.list_databases.sql, (pattern,)) - headers = [x.name for x in cur.description] if cur.description else None + headers = [titleize(x.name) for x in cur.description] if cur.description else None return [(None, cur, headers, cur.statusmessage)] @@ -71,7 +71,7 @@ def list_privileges(cur, pattern, verbose): (param, table, schema), ) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -82,7 +82,7 @@ def list_default_privileges(cur, pattern, verbose): pattern = f"^({pattern})$" if pattern else ".*" cur.execute(queries.list_default_privileges.sql, (pattern, pattern)) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -93,7 +93,7 @@ def list_tablespaces(cur, pattern, **_): pattern = pattern or ".*" cur.execute(queries.list_tablespaces.sql, {"pattern": pattern}) - headers = [x.name for x in cur.description] if cur.description else None + headers = [titleize(x.name) for x in cur.description] if cur.description else None return [(None, cur, headers, cur.statusmessage)] @@ -107,7 +107,7 @@ def list_schemas(cur, pattern, verbose): cur.execute(queries.list_schemas.sql, {"pattern": pattern}) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -123,7 +123,7 @@ def _find_extensions(cur, pattern): def _describe_extension(cur, oid): cur.execute(queries.describe_extension.sql, {"oid": oid}) - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return cur, headers, cur.statusmessage if cur.connection.info.server_version < 90100: @@ -152,7 +152,7 @@ def _describe_extension(cur, oid): cur.execute(queries.list_extensions.sql, {"pattern": pattern}) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] yield None, cur, headers, cur.statusmessage @@ -181,7 +181,7 @@ def list_objects(cur, pattern, verbose, relkinds): cur.execute(queries.list_objects.sql, params) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -235,7 +235,7 @@ def list_functions(cur, pattern, verbose): cur.execute(queries.list_functions.sql, params) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -259,7 +259,7 @@ def list_datatypes(cur, pattern, verbose): cur.execute(queries.list_datatypes.sql, params) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -272,7 +272,7 @@ def list_domains(cur, pattern, verbose): else: cur.execute(queries.list_domains.sql, params) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] @@ -286,7 +286,7 @@ def _find_text_search_configs(cur, pattern): def _fetch_oid_details(cur, oid): cur.execute(queries.fetch_oid_details.sql, {"oid": oid}) - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return cur, headers, cur.statusmessage if cur.connection.info.server_version < 80300: @@ -318,7 +318,7 @@ def _fetch_oid_details(cur, oid): cur.execute(queries.list_text_search_configurations.sql, {"schema": schema}) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] yield None, cur, headers, cur.statusmessage @@ -575,7 +575,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if cur.rowcount > 0: status.append("Indexes:\n") for row in cur: - # /* untranslated index name */ + # /* untranslated indetitleize(x.name) */ status.append(f''' "{row[0]}"''') # /* If exclusion constraint, print the constraintdef */ @@ -923,7 +923,7 @@ def show_function_definition(cur, pattern, verbose): {"pattern": pattern}, ) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] if verbose: (source,) = cur.fetchone() rows = _FakeCursor() @@ -958,7 +958,11 @@ def list_foreign_tables(cur, pattern, verbose): else: cur.execute(queries.list_foreign_tables.sql, {"pattern": pattern}) if cur.description: - headers = [x.name for x in cur.description] + headers = [titleize(x.name) for x in cur.description] return [(None, cur, headers, cur.statusmessage)] else: return [(None, None, None, cur.statusmessage)] + + +def titleize(column): + return column[0].capitalize() + " ".join(c for c in column[1:].split("_")) diff --git a/tests/test_specials.py b/tests/test_specials.py index 49c40ed..1254cfe 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -631,7 +631,7 @@ def test_slash_dx_verbose(executor): ("function plpgsql_validator(oid)",), ("language plpgsql",), ] - headers = ["Object Description"] + headers = ["Object description"] status = "SELECT %s" % len(row) expected = [title, row, headers, status] assert results == expected @@ -1046,7 +1046,7 @@ def test_slash_sf(executor): "AS $function$select 1$function$\n", ), ] - headers = ["source"] + headers = ["Source"] status = None expected = [title, rows, headers, status] assert results == expected @@ -1074,7 +1074,7 @@ def test_slash_sf_parens(executor): "AS $function$select 1$function$\n", ), ] - headers = ["source"] + headers = ["Source"] status = None expected = [title, rows, headers, status] assert results == expected @@ -1092,7 +1092,7 @@ def test_slash_sf_verbose(executor): "1 AS $function$select 2$function$\n", ), ] - headers = ["source"] + headers = ["Source"] status = None expected = [title, rows, headers, status] assert results == expected From 7ff4d105922fcbc89bd89d8ad4b6894deef80726 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 14 Nov 2023 18:47:31 +0000 Subject: [PATCH 23/30] remove verbose columns in postprocessing --- dbcommands.sql | 356 ++++------------------------------------ pgspecial/dbcommands.py | 153 +++++++++-------- run_test_matrix.sh | 2 +- 3 files changed, 107 insertions(+), 404 deletions(-) diff --git a/dbcommands.sql b/dbcommands.sql index a9c6ef4..decb0ee 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1,16 +1,6 @@ -- name: version SELECT version() -- name: list_databases -SELECT d.datname AS name, - pg_catalog.pg_get_userbyid(d.datdba) AS owner, - pg_catalog.pg_encoding_to_char(d.encoding) AS encoding, - d.datcollate AS collate, - d.datctype AS ctype, - pg_catalog.array_to_string(d.datacl, e'\n') AS access_privileges -FROM pg_catalog.pg_database d -WHERE d.datname ~ %s -ORDER BY 1 --- name: list_databases_verbose SELECT d.datname AS name, pg_catalog.pg_get_userbyid(d.datdba) AS owner, pg_catalog.pg_encoding_to_char(d.encoding) AS encoding, @@ -26,22 +16,7 @@ SELECT d.datname AS name, pg_catalog.shobj_description(d.oid, 'pg_database') AS description FROM pg_catalog.pg_database d JOIN pg_catalog.pg_tablespace t ON d.dattablespace = t.oid -WHERE d.datname ~ %s -ORDER BY 1 --- name: list_roles_9_verbose -SELECT r.rolname, - r.rolsuper, - r.rolinherit, - r.rolcreaterole, - r.rolcreatedb, - r.rolcanlogin, - r.rolconnlimit, - r.rolvaliduntil, -ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) as memberof, -pg_catalog.shobj_description(r.oid, 'pg_authid') AS description, -r.rolreplication -FROM pg_catalog.pg_roles r -WHERE r.rolname ~ %s +WHERE d.datname ~ :pattern ORDER BY 1 -- name: list_roles_9 SELECT r.rolname, @@ -53,9 +28,10 @@ SELECT r.rolname, r.rolconnlimit, r.rolvaliduntil, ARRAY(SELECT b.rolname FROM pg_catalog.pg_auth_members m JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) WHERE m.member = r.oid) as memberof, +pg_catalog.shobj_description(r.oid, 'pg_authid') AS description, r.rolreplication FROM pg_catalog.pg_roles r -WHERE r.rolname ~ %s +WHERE r.rolname ~ :pattern ORDER BY 1 -- name: list_roles SELECT u.usename AS rolname, @@ -66,7 +42,7 @@ SELECT u.usename AS rolname, TRUE AS rolcanlogin, -1 AS rolconnlimit, u.valuntil AS rolvaliduntil, - array(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = any(g.grolist)) AS memberof + ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = any(g.grolist)) AS memberof FROM pg_catalog.pg_user u -- name: list_privileges -- docs: ("\\dp", "\\dp [pattern]", "List roles.", aliases=("\\z",)) @@ -161,7 +137,7 @@ FROM WHERE n.spcname ~ :pattern ORDER BY 1 --- name: list_schemas_verbose +-- name: list_schemas -- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") SELECT n.nspname AS name, @@ -176,19 +152,6 @@ CASE :pattern ELSE n.nspname ~ :pattern END ORDER BY 1 --- name: list_schemas --- docs: ("\\dn", "\\dn[+] [pattern]", "List schemas.") -SELECT - n.nspname AS name, - pg_catalog.pg_get_userbyid(n.nspowner) AS owner -FROM - pg_catalog.pg_namespace n -WHERE -CASE :pattern - WHEN '.*' THEN n.nspname !~ '^pg_' AND n.nspname <> 'information_schema' - ELSE n.nspname ~ :pattern -END -ORDER BY 1 -- name: find_extensions SELECT e.extname, @@ -261,7 +224,7 @@ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace WHERE c.cfgname ~ :schema ORDER BY 1, 2 --- name: list_objects_verbose +-- name: list_objects -- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes SELECT n.nspname AS schema, @@ -294,64 +257,8 @@ WHERE AND n.nspname !~ '^pg_toast' END ORDER BY 1, 2 --- name: list_objects --- docs: This method is used by list_tables, list_views, list_materialized views and list_indexes -SELECT - n.nspname AS schema, - c.relname AS name, - CASE c.relkind - WHEN 'r' THEN 'table' - WHEN 'v' THEN 'view' - WHEN 'p' THEN 'partitioned table' - WHEN 'm' THEN 'materialized view' - WHEN 'i' THEN 'index' - WHEN 'S' THEN 'sequence' - WHEN 's' THEN 'special' - WHEN 'f' THEN 'foreign table' END - as type, - pg_catalog.pg_get_userbyid(c.relowner) AS owner -FROM - pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace -WHERE - c.relkind = ANY(:relkinds) - AND c.relname ~ :table_pattern - AND CASE WHEN :schema_pattern != '.*' - THEN n.nspname ~ :schema_pattern - ELSE - pg_catalog.pg_table_is_visible(c.oid) - AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND n.nspname !~ '^pg_toast' - END -ORDER BY 1, 2 - --- name: list_functions --- docs: ("\\df", "\\df[+] [pattern]", "List functions.") -SELECT n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_function_result(p.oid) AS result_data_type, - pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, - CASE - WHEN p.prokind = 'a' THEN 'agg' - WHEN p.prokind = 'w' THEN 'window' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' - ELSE 'normal' - END AS type, - :provolatile AS volatility, - pg_catalog.pg_get_userbyid(p.proowner) AS owner, - l.lanname AS language, - p.prosrc AS source_code, - pg_catalog.obj_description(p.oid, 'pg_proc') AS description -FROM pg_catalog.pg_proc p -LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace -LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang -WHERE n.nspname ~ :schema_pattern - AND p.proname ~ :pattern -ORDER BY 1, - 2, - 4 -- name: list_functions__verbose_11 +-- name: list_functions_11 SELECT n.nspname AS schema, p.proname AS name, pg_catalog.pg_get_function_result(p.oid) AS result_data_type, @@ -389,7 +296,7 @@ ORDER BY 1, 2, 4 --- name: list_functions_verbose_9 +-- name: list_functions_9 SELECT n.nspname AS schema, p.proname AS name, @@ -428,8 +335,7 @@ ORDER BY 1, 2, 4 --- name: list_functions__verbose_9 - +-- name: list_functions SELECT n.nspname AS schema, p.proname AS name, pg_catalog.format_type(p.prorettype, NULL) AS result_data_type, @@ -438,11 +344,12 @@ SELECT n.nspname AS schema, WHEN p.proisagg THEN 'agg' WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' ELSE 'normal' - END AS type CASE - WHEN p.provolatile = 'i' THEN 'immutable' - WHEN p.provolatile = 's' THEN 'stable' - WHEN p.provolatile = 'v' THEN 'volatile' - END AS volatility, + END AS type, + CASE + WHEN p.provolatile = 'i' THEN 'immutable' + WHEN p.provolatile = 's' THEN 'stable' + WHEN p.provolatile = 'v' THEN 'volatile' + END AS volatility, pg_catalog.pg_get_userbyid(p.proowner) AS owner, l.lanname AS language, p.prosrc AS source_code, @@ -465,100 +372,16 @@ ORDER BY 1, 2, 4 --- name: list_functions_11 -SELECT n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_function_result(p.oid) AS result_data_type, - pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, - CASE - WHEN p.prokind = 'a' THEN 'agg' - WHEN p.prokind = 'w' THEN 'window' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' - ELSE 'normal' - END AS type -FROM pg_catalog.pg_proc p -LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace -WHERE p.proname ~ :function_pattern - AND CASE - WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern - ELSE pg_catalog.pg_function_is_visible(p.oid) - END - AND CASE - WHEN :schema_pattern = '.*' - AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - ELSE TRUE - END -ORDER BY 1, - 2, - 4 - --- name: list_functions_9 -SELECT n.nspname AS schema, - p.proname AS name, - pg_catalog.pg_get_function_result(p.oid) AS result_data_type, - pg_catalog.pg_get_function_arguments(p.oid) AS argument_data_types, - CASE - WHEN p.proisagg THEN 'agg' - WHEN p.proiswindow THEN 'window' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' - ELSE 'normal' - END AS type -FROM pg_catalog.pg_proc p -LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace -WHERE p.proname ~ :function_pattern - AND CASE - WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern - ELSE pg_catalog.pg_function_is_visible(p.oid) - END - AND CASE - WHEN :schema_pattern = '.*' - AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - ELSE TRUE - END -ORDER BY 1, - 2, - 4 - --- name: list_functions_9 -SELECT n.nspname AS schema, - p.proname AS name, - pg_catalog.format_type(p.prorettype, NULL) AS result_data_type, - pg_catalog.oidvectortypes(p.proargtypes) AS argument_data_types, - CASE - WHEN p.proisagg THEN 'agg' - WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger' - ELSE 'normal' - END AS type -FROM pg_catalog.pg_proc p -LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace -WHERE p.proname ~ :function_pattern - AND CASE - WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern - ELSE pg_catalog.pg_function_is_visible(p.oid) - END - AND CASE - WHEN :schema_pattern = '.*' - AND :function_pattern = '.*' THEN n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - ELSE TRUE - END -ORDER BY 1, - 2, - 4 - - --- name: list_datatype_verbose_9 +-- name: list_datatypes_9 -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT n.nspname AS schema, pg_catalog.format_type(t.oid, NULL) AS name, t.typname AS internal_name, CASE - WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) - WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) - ELSE CAST(t.typlen AS pg_catalog.text) + WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) + WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) + ELSE CAST(t.typlen AS pg_catalog.text) END AS size, pg_catalog.array_to_string(ARRAY ( SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE @@ -592,47 +415,16 @@ WHERE (t.typrelid = 0 END ORDER BY 1, 2 --- name: list_datatypes_9 --- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") -SELECT - n.nspname AS schema, - pg_catalog.format_type(t.oid, NULL) AS name, - pg_catalog.obj_description(t.oid, 'pg_type') as description -FROM pg_catalog.pg_type t -LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace -WHERE (t.typrelid = 0 - OR ( - SELECT - c.relkind = 'c' - FROM - pg_catalog.pg_class c - WHERE - c.oid = t.typrelid)) - AND NOT EXISTS( - SELECT 1 - FROM pg_catalog.pg_type el - WHERE el.oid = t.typelem - AND el.typarray = t.oid) - AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) - AND CASE WHEN :schema_pattern != '.*' - THEN n.nspname ~ :schema_pattern - ELSE pg_catalog.pg_type_is_visible(t.oid) - END - AND CASE WHEN :schema_pattern = '.*' and :type_pattern = '.*' - THEN n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' - ELSE true - END -ORDER BY 1, 2 --- name: list_datatype_verbose +-- name: list_datatypes -- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") SELECT n.nspname AS schema, pg_catalog.format_type(t.oid, NULL) AS name, t.typname AS internal_name, CASE - WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) - WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) - ELSE CAST(t.typlen AS pg_catalog.text) + WHEN t.typrelid != 0 THEN CAST('tuple' AS pg_catalog.text) + WHEN t.typlen < 0 THEN CAST('var' AS pg_catalog.text) + ELSE CAST(t.typlen AS pg_catalog.text) END AS size, pg_catalog.array_to_string(ARRAY ( SELECT e.enumlabel FROM pg_catalog.pg_enum e WHERE @@ -650,45 +442,11 @@ WHERE (t.typrelid = 0 pg_catalog.pg_class c WHERE c.oid = t.typrelid)) - -- this is the only difference in the query - -- AND NOT EXISTS( - -- SELECT 1 - -- FROM pg_catalog.pg_type el - -- WHERE el.oid = t.typelem - -- AND el.typarray = t.oid) - AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) - AND CASE WHEN :schema_pattern != '.*' - THEN n.nspname ~ :schema_pattern - ELSE pg_catalog.pg_type_is_visible(t.oid) - END - AND CASE WHEN :schema_pattern = '.*' and :type_pattern = '.*' - THEN n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema' - ELSE true - END -ORDER BY 1, 2 - --- name: list_datatypes --- docs: ("\\dT", "\\dT[S+] [pattern]", "List data types") -SELECT - n.nspname AS schema, - pg_catalog.format_type(t.oid, NULL) AS name, - pg_catalog.obj_description(t.oid, 'pg_type') as description -FROM pg_catalog.pg_type t -LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace -WHERE (t.typrelid = 0 - OR ( - SELECT - c.relkind = 'c' - FROM - pg_catalog.pg_class c - WHERE - c.oid = t.typrelid)) - -- this is the only difference in the query - -- AND NOT EXISTS( - -- SELECT 1 - -- FROM pg_catalog.pg_type el - -- WHERE el.oid = t.typelem - -- AND el.typarray = t.oid) + AND NOT EXISTS( + SELECT 1 + FROM pg_catalog.pg_type el + WHERE el.oid = t.typelem + AND el.typarray = t.oid) AND (t.typname ~ :type_pattern OR pg_catalog.format_type(t.oid, NULL) ~ :type_pattern) AND CASE WHEN :schema_pattern != '.*' THEN n.nspname ~ :schema_pattern @@ -700,7 +458,7 @@ WHERE (t.typrelid = 0 END ORDER BY 1, 2 --- name: list_domains_verbose +-- name: list_domains -- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") SELECT n.nspname AS schema, @@ -740,41 +498,7 @@ WHERE ELSE true END ORDER BY 1, 2 --- name: list_domains --- docs: ("\\dD", "\\dD[+] [pattern]", "List or describe domains.") -SELECT - n.nspname AS schema, - t.typname AS name, - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS type, - pg_catalog.ltrim((COALESCE(( - SELECT - (' collate ' || c.collname) - FROM pg_catalog.pg_collation AS c, pg_catalog.pg_type AS bt - WHERE - c.oid = t.typcollation - AND bt.oid = t.typbasetype - AND t.typcollation <> bt.typcollation), '') || - CASE WHEN t.typnotnull THEN ' not null' ELSE '' END) || - CASE WHEN t.typdefault IS NOT NULL THEN (' default ' || t.typdefault) ELSE '' END) AS modifier, - pg_catalog.array_to_string(ARRAY ( - SELECT - pg_catalog.pg_get_constraintdef(r.oid, TRUE) - FROM pg_catalog.pg_constraint AS r - WHERE t.oid = r.contypid), ' ') AS check -FROM - pg_catalog.pg_type AS t - LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = t.typnamespace -WHERE - t.typtype = 'd' - AND n.nspname ~ :schema_pattern - AND t.typname ~ :pattern - AND CASE WHEN :schema_pattern = '.*' AND :pattern = '.*' - THEN n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND pg_catalog.pg_type_is_visible(t.oid) - ELSE true - END -ORDER BY 1, 2 + -- name: describe_table_details -- docs: ( "\\d", "\\d[+] [pattern]", "List or describe tables, views and sequences.") SELECT c.oid, n.nspname, c.relname @@ -1017,7 +741,7 @@ pg_catalog.pg_get_functiondef( END ) AS source --- name: list_foreign_tables_verbose +-- name: list_foreign_tables -- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) SELECT n.nspname AS schema, @@ -1037,24 +761,6 @@ WHERE AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname OPERATOR (pg_catalog. ~) :pattern ORDER BY 1, 2 --- name: list_foreign_tables --- docs: ("\\dE", "\\dE[+] [pattern]", "List foreign tables.", aliases=()) -SELECT - n.nspname AS schema, - c.relname AS name, - 'foreign table' AS type, - pg_catalog.pg_get_userbyid(c.relowner) AS owner -FROM - pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace -WHERE - c.relkind IN ('f', '') - AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND n.nspname !~ '^pg_toast' - AND pg_catalog.pg_table_is_visible(c.oid) - AND c.relname OPERATOR (pg_catalog. ~) :pattern -ORDER BY 1, 2 -- name: describe_one_table_details_12 SELECT c.relchecks, c.relkind, diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index d5df441..658eac7 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -32,13 +32,10 @@ @special_command("\\l", "\\l[+] [pattern]", "List databases.", aliases=("\\list",)) def list_databases(cur, pattern, verbose): pattern = pattern or ".*" - if verbose: - cur.execute(queries.list_databases_verbose.sql, (pattern,)) - else: - cur.execute(queries.list_databases.sql, (pattern,)) - - headers = [titleize(x.name) for x in cur.description] if cur.description else None - return [(None, cur, headers, cur.statusmessage)] + cur.execute(queries.list_databases.sql, {"pattern": pattern}) + hidden_columns = ["size", "tablespace", "description"] if not verbose else [] + results = prepare_results(cur, hidden_columns) + return results @special_command("\\du", "\\du[+] [pattern]", "List roles.") @@ -47,21 +44,19 @@ def list_roles(cur, pattern, verbose): pattern = pattern or ".*" if cur.connection.info.server_version > 90000: - if verbose: - cur.execute(queries.list_roles_9_verbose.sql, (pattern,)) - else: - cur.execute(queries.list_roles_9.sql, (pattern,)) + cur.execute(queries.list_roles_9.sql, {"pattern": pattern}) else: cur.execute(queries.list_roles.sql) - if cur.description: - headers = [x.name for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + hidden_columns = ["description"] if not verbose else [] + results = prepare_results(cur, hidden_columns, pretty=False) + return results @special_command("\\dp", "\\dp [pattern]", "List privileges.", aliases=("\\z",)) def list_privileges(cur, pattern, verbose): """Returns (title, rows, headers, status)""" + param = bool(pattern) schema, table = sql_name_pattern(pattern) schema = schema or ".*" @@ -70,9 +65,8 @@ def list_privileges(cur, pattern, verbose): queries.list_privileges.sql, (param, table, schema), ) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + results = prepare_results(cur) + return results @special_command("\\ddp", "\\ddp [pattern]", "Lists default access privilege settings.") @@ -81,9 +75,8 @@ def list_default_privileges(cur, pattern, verbose): pattern = f"^({pattern})$" if pattern else ".*" cur.execute(queries.list_default_privileges.sql, (pattern, pattern)) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + results = prepare_results(cur) + return results @special_command("\\db", "\\db[+] [pattern]", "List tablespaces.") @@ -92,23 +85,19 @@ def list_tablespaces(cur, pattern, **_): pattern = pattern or ".*" cur.execute(queries.list_tablespaces.sql, {"pattern": pattern}) - - headers = [titleize(x.name) for x in cur.description] if cur.description else None - return [(None, cur, headers, cur.statusmessage)] + results = prepare_results(cur) + return results @special_command("\\dn", "\\dn[+] [pattern]", "List schemas.") def list_schemas(cur, pattern, verbose): """Returns (title, rows, headers, status)""" pattern = pattern or ".*" - if verbose: - cur.execute(queries.list_schemas_verbose.sql, {"pattern": pattern}) - else: - cur.execute(queries.list_schemas.sql, {"pattern": pattern}) + cur.execute(queries.list_schemas.sql, {"pattern": pattern}) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + hidden_columns = ["access_privileges", "description"] if not verbose else [] + results = prepare_results(cur, hidden_columns) + return results # https://github.com/postgres/postgres/blob/master/src/bin/psql/describe.c#L5471-L5638 @@ -175,10 +164,10 @@ def list_objects(cur, pattern, verbose, relkinds): "table_pattern": table_pattern, "relkinds": relkinds, } - if verbose: - cur.execute(queries.list_objects_verbose.sql, params) - else: - cur.execute(queries.list_objects.sql, params) + cur.execute(queries.list_objects.sql, params) + hidden_columns = ["size", "description"] if not verbose else [] + results = prepare_results(cur, hidden_columns) + return results if cur.description: headers = [titleize(x.name) for x in cur.description] @@ -219,24 +208,19 @@ def list_functions(cur, pattern, verbose): } if cur.connection.info.server_version >= 110000: - if verbose: - cur.execute(queries.list_functions_verbose_11.sql, params) - else: - cur.execute(queries.list_functions_11.sql, params) + cur.execute(queries.list_functions_11.sql, params) elif cur.connection.info.server_version > 90000: - if verbose: - cur.execute(queries.list_functions_verbose_9.sql, params) - else: - cur.execute(queries.list_functions_9.sql, params) + cur.execute(queries.list_functions_9.sql, params) else: - if verbose: - cur.execute(queries.list_functions_verbose.sql, params) - else: - cur.execute(queries.list_functions.sql, params) + cur.execute(queries.list_functions.sql, params) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + hidden_columns = ( + ["volatility", "owner", "language", "source_code", "description"] + if not verbose + else [] + ) + results = prepare_results(cur, hidden_columns) + return results @special_command("\\dT", "\\dT[S+] [pattern]", "List data types") @@ -248,32 +232,27 @@ def list_datatypes(cur, pattern, verbose): "type_pattern": type_pattern or ".*", } if cur.connection.info.server_version > 90000: - if verbose: - cur.execute(queries.list_datatypes_verbose_9.sql, params) - else: - cur.execute(queries.list_datatypes_9.sql, params) + cur.execute(queries.list_datatypes_9.sql, params) else: - if verbose: - cur.execute(queries.list_datatypes_verbose.sql, params) - else: - cur.execute(queries.list_datatypes.sql, params) + cur.execute(queries.list_datatypes.sql, params) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + hidden_columns = ( + ["internal_name", "size", "elements", "access_privileges"] + if not verbose + else [] + ) + results = prepare_results(cur, hidden_columns) + return results @special_command("\\dD", "\\dD[+] [pattern]", "List or describe domains.") def list_domains(cur, pattern, verbose): schema_pattern, name_pattern = sql_name_pattern(pattern) params = {"schema_pattern": schema_pattern or ".*", "pattern": name_pattern or ".*"} - if verbose: - cur.execute(queries.list_domains_verbose.sql, params) - else: - cur.execute(queries.list_domains.sql, params) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] + cur.execute(queries.list_domains.sql, params) + hidden_columns = ["access_privileges", "description"] if not verbose else [] + results = prepare_results(cur, hidden_columns) + return results @special_command("\\dF", "\\dF[+] [pattern]", "List text search configurations.") @@ -953,16 +932,34 @@ def shell_command(cur, pattern, verbose): def list_foreign_tables(cur, pattern, verbose): _, tbl_name = sql_name_pattern(pattern) pattern = f"^({tbl_name})$" if tbl_name else ".*" - if verbose: - cur.execute(queries.list_foreign_tables_verbose.sql, {"pattern": pattern}) - else: - cur.execute(queries.list_foreign_tables.sql, {"pattern": pattern}) - if cur.description: - headers = [titleize(x.name) for x in cur.description] - return [(None, cur, headers, cur.statusmessage)] - else: - return [(None, None, None, cur.statusmessage)] + cur.execute(queries.list_foreign_tables.sql, {"pattern": pattern}) + hidden_columns = ["size", "description"] if not verbose else [] + results = prepare_results(cur, hidden_columns) + return results + +def prepare_results(cur, hidden_columns=[], pretty=True): + headers = [ + titleize(h.name, pretty) + for h in cur.description + if h.name not in hidden_columns + ] + rows = map( + lambda row: tuple( + [ + item + for item, header in zip(row, cur.description) + if header.name not in hidden_columns + ] + ), + cur, + ) + + return [(None, rows, headers, cur.statusmessage)] -def titleize(column): - return column[0].capitalize() + " ".join(c for c in column[1:].split("_")) + +def titleize(column, pretty=True): + if pretty: + return column[0].capitalize() + " ".join(c for c in column[1:].split("_")) + else: + return column diff --git a/run_test_matrix.sh b/run_test_matrix.sh index b5eab5b..4695a08 100755 --- a/run_test_matrix.sh +++ b/run_test_matrix.sh @@ -1,4 +1,4 @@ for port in {5441..5445}; do - PGPORT=$port py.test + PGPORT=$port py.test -vv -s done From 616f1df83faf7be7ee3e4f07a12cf0cf963ca9a9 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 14 Nov 2023 21:37:01 +0000 Subject: [PATCH 24/30] rename duplicate --- dbcommands.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbcommands.sql b/dbcommands.sql index decb0ee..1b91d78 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -781,7 +781,7 @@ SELECT c.relchecks, FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid) WHERE c.oid = :oid --- name: describe_one_table_details_10 +-- name: describe_one_table_details_11 SELECT c.relchecks, c.relkind, c.relhasindex, From f73d71160c1b7ce9deb021d29644739e47f4f229 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 15 Nov 2023 20:57:58 +0000 Subject: [PATCH 25/30] change server version query --- dbcommands.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbcommands.sql b/dbcommands.sql index 1b91d78..a831a86 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1,5 +1,5 @@ -- name: version -SELECT version() +SELECT setting FROM pg_settings WHERE name = 'server_version_num' -- name: list_databases SELECT d.datname AS name, pg_catalog.pg_get_userbyid(d.datdba) AS owner, From 55ab3c918f96377c76bfab5e2b0c57222a4247ad Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 15 Nov 2023 20:59:56 +0000 Subject: [PATCH 26/30] change server version query --- dbcommands.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbcommands.sql b/dbcommands.sql index a831a86..5e50397 100644 --- a/dbcommands.sql +++ b/dbcommands.sql @@ -1,5 +1,5 @@ -- name: version -SELECT setting FROM pg_settings WHERE name = 'server_version_num' +SELECT setting::integer AS version FROM pg_settings WHERE name = 'server_version_num' -- name: list_databases SELECT d.datname AS name, pg_catalog.pg_get_userbyid(d.datdba) AS owner, From 144f921f7beb40be54cd83ec6bf5aee99ef4dd00 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Thu, 23 Nov 2023 17:01:42 +0000 Subject: [PATCH 27/30] move sql file --- pgspecial/dbcommands.py | 2 +- dbcommands.sql => pgspecial/dbcommands.sql | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename dbcommands.sql => pgspecial/dbcommands.sql (100%) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 658eac7..2db9c6e 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -7,7 +7,7 @@ import aiosql from .main import special_command -queries = aiosql.from_path("dbcommands.sql", "psycopg2") +queries = aiosql.from_path("pgspecial/dbcommands.sql", "psycopg2") TableInfo = namedtuple( "TableInfo", diff --git a/dbcommands.sql b/pgspecial/dbcommands.sql similarity index 100% rename from dbcommands.sql rename to pgspecial/dbcommands.sql From 3aa28f24083d527166f321493c0244c2713531bd Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Thu, 23 Nov 2023 17:37:59 +0000 Subject: [PATCH 28/30] try to include sql file in package --- MANIFEST.in | 1 + pgspecial/dbcommands.py | 6 +++++- pgspecial/{ => sql}/dbcommands.sql | 0 setup.py | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) rename pgspecial/{ => sql}/dbcommands.sql (100%) diff --git a/MANIFEST.in b/MANIFEST.in index de21dbd..19397f9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,6 @@ exclude * include changelog.rst DEVELOP.rst License.txt pyproject.toml README.rst requirements-dev.txt setup.py include scripts/docparser.py scripts/README.rst tox.ini recursive-include pgspecial *.py +recursive-include pgspecial *.sql recursive-include tests *.py recursive-include tests *.ini diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index 2db9c6e..c1b4582 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals import logging +from pathlib import Path import shlex import subprocess from collections import namedtuple @@ -7,7 +8,10 @@ import aiosql from .main import special_command -queries = aiosql.from_path("pgspecial/dbcommands.sql", "psycopg2") + +dir_path = Path(__file__).parent +sql_path = dir_path / "sql" / "dbcommands.sql" +queries = aiosql.from_path(sql_path, "psycopg2") TableInfo = namedtuple( "TableInfo", diff --git a/pgspecial/dbcommands.sql b/pgspecial/sql/dbcommands.sql similarity index 100% rename from pgspecial/dbcommands.sql rename to pgspecial/sql/dbcommands.sql diff --git a/setup.py b/setup.py index 7a9bf2a..bde43dc 100644 --- a/setup.py +++ b/setup.py @@ -22,10 +22,14 @@ packages=find_packages(), description=description, long_description=open("README.rst").read(), + package_data={ + "pgspecial": ["sql/dbcommands.sql"], + }, install_requires=[ "click >= 4.1", "sqlparse >= 0.1.19", "psycopg >= 3.0.10", + "aiosql >= 9.0", ], classifiers=[ "Intended Audience :: Developers", From b26a06d06425075cd16d4dc8134fe664c14746c1 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Fri, 24 Nov 2023 22:21:35 +0000 Subject: [PATCH 29/30] typo --- pgspecial/sql/dbcommands.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgspecial/sql/dbcommands.sql b/pgspecial/sql/dbcommands.sql index 5e50397..ed2f534 100644 --- a/pgspecial/sql/dbcommands.sql +++ b/pgspecial/sql/dbcommands.sql @@ -997,7 +997,7 @@ FROM pg_catalog.pg_constraint r WHERE r.conrelid = :oid AND r.contype = 'c' ORDER BY 1; --- name: get_foreign_key_constratints +-- name: get_foreign_key_constraints SELECT conname, pg_catalog.pg_get_constraintdef(r.oid, TRUE) AS condef FROM pg_catalog.pg_constraint r From a02694212b97288f6ed18c48703eb46b05ed8d2b Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Sun, 7 Jan 2024 13:46:39 +0000 Subject: [PATCH 30/30] restore tests --- pgspecial/dbcommands.py | 2 +- tests/test_specials.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pgspecial/dbcommands.py b/pgspecial/dbcommands.py index e5ece74..cd06165 100644 --- a/pgspecial/dbcommands.py +++ b/pgspecial/dbcommands.py @@ -558,7 +558,7 @@ def describe_one_table_details(cur, schema_name, relation_name, oid, verbose): if cur.rowcount > 0: status.append("Indexes:\n") for row in cur: - # /* untranslated index titleize(x.name) */ + # /* untranslated index name */ status.append(f''' "{row[0]}"''') # /* If exclusion constraint, print the constraintdef */ diff --git a/tests/test_specials.py b/tests/test_specials.py index 49c40ed..1254cfe 100755 --- a/tests/test_specials.py +++ b/tests/test_specials.py @@ -631,7 +631,7 @@ def test_slash_dx_verbose(executor): ("function plpgsql_validator(oid)",), ("language plpgsql",), ] - headers = ["Object Description"] + headers = ["Object description"] status = "SELECT %s" % len(row) expected = [title, row, headers, status] assert results == expected @@ -1046,7 +1046,7 @@ def test_slash_sf(executor): "AS $function$select 1$function$\n", ), ] - headers = ["source"] + headers = ["Source"] status = None expected = [title, rows, headers, status] assert results == expected @@ -1074,7 +1074,7 @@ def test_slash_sf_parens(executor): "AS $function$select 1$function$\n", ), ] - headers = ["source"] + headers = ["Source"] status = None expected = [title, rows, headers, status] assert results == expected @@ -1092,7 +1092,7 @@ def test_slash_sf_verbose(executor): "1 AS $function$select 2$function$\n", ), ] - headers = ["source"] + headers = ["Source"] status = None expected = [title, rows, headers, status] assert results == expected