From 81bdb1832b7a5b6f46c54d112e03dd1d6d8413ff Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Wed, 6 Nov 2024 12:23:48 -0600 Subject: [PATCH 01/16] Squashed 'pgxntool/' changes from c0af00f..bed3604 bed3604 Fix pg_regress on versions > 12 (#5) (#6) git-subtree-dir: pgxntool git-subtree-split: bed36044679d6b53ad7cd2875272552a4ad6508a --- HISTORY.asc | 3 +++ base.mk | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/HISTORY.asc b/HISTORY.asc index b134482..9cb793b 100644 --- a/HISTORY.asc +++ b/HISTORY.asc @@ -1,5 +1,8 @@ STABLE ------ +== Support 13+ +The `--load-language` option was removed from `pg_regress` in 13. + == Reduce verbosity from test setup As part of this change, you will want to review the changes to test/deps.sql. diff --git a/base.mk b/base.mk index 0634f2e..a976ebb 100644 --- a/base.mk +++ b/base.mk @@ -36,7 +36,7 @@ TEST_SQL_FILES += $(wildcard $(TESTDIR)/sql/*.sql) TEST_RESULT_FILES = $(patsubst $(TESTDIR)/sql/%.sql,$(TESTDIR)/expected/%.out,$(TEST_SQL_FILES)) TEST_FILES = $(TEST_SOURCE_FILES) $(TEST_SQL_FILES) REGRESS = $(sort $(notdir $(subst .source,,$(TEST_FILES:.sql=)))) # Sort is to get unique list -REGRESS_OPTS = --inputdir=$(TESTDIR) --outputdir=$(TESTOUT) --load-language=plpgsql +REGRESS_OPTS = --inputdir=$(TESTDIR) --outputdir=$(TESTOUT) # See additional setup below MODULES = $(patsubst %.c,%,$(wildcard src/*.c)) ifeq ($(strip $(MODULES)),) MODULES =# Set to NUL so PGXS doesn't puke @@ -57,8 +57,10 @@ GE91 = $(call test, $(MAJORVER), -ge, 91) ifeq ($(GE91),yes) all: $(EXTENSION_VERSION_FILES) +endif -#DATA = $(wildcard sql/*--*.sql) +ifeq ($($call test, $(MAJORVER), -lt 13), yes) + REGRESS_OPTS += --load-language=plpgsql endif PGXS := $(shell $(PG_CONFIG) --pgxs) From d231a5896337f869b6327874166ed0f3a965505d Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Thu, 7 Nov 2024 14:53:01 -0600 Subject: [PATCH 02/16] Add support for Postgres 13 Biggest change is that starting with Postgres 12 OID columns in catalog tables were no longer hidden, which required adjusting a few views and tests. Also update pgxntool. --- sql/cat_tools.sql.in | 70 ++++++++++++++++++++++++++++++------- sql/omit_column.sql | 16 +++++++++ test/expected/general.out | 3 +- test/expected/zzz_build.out | 4 +++ test/setup.sql | 17 +++++++++ test/sql/attribute.sql | 15 ++++++-- test/sql/extension.sql | 2 +- 7 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 sql/omit_column.sql diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index dda82d8..fba34a6 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -22,14 +22,6 @@ CREATE SCHEMA __cat_tools; GRANT USAGE ON SCHEMA cat_tools TO cat_tools__usage; CREATE SCHEMA _cat_tools; --- No permissions checks -CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS - SELECT c.oid AS reloid, c.*, n.nspname AS relschema - FROM pg_class c - LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace ) -; -REVOKE ALL ON _cat_tools.pg_class_v FROM public; - @generated@ CREATE FUNCTION __cat_tools.exec( @@ -41,8 +33,44 @@ BEGIN END $body$; +-- See also test/setup.sql +CREATE FUNCTION __cat_tools.omit_column( + rel text + , omit name[] DEFAULT array['oid'] +) RETURNS text LANGUAGE sql IMMUTABLE AS $body$ +SELECT array_to_string(array( + SELECT attname + FROM pg_attribute a + WHERE attrelid = rel::regclass + AND NOT attisdropped + AND attnum >= 0 + AND attname != ANY( omit ) + ORDER BY attnum + ) + , ', ' +) +$body$; + @generated@ +/* + * Starting in 12 oid columns in catalog tables are no longer hidden, so we + * need a way to include all the fields in a table *except* for the OID column. + */ +SELECT __cat_tools.exec(format($fmt$ +CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS + SELECT c.oid AS reloid + , %s + , n.nspname AS relschema + FROM pg_class c + LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace ) +; +$fmt$ + , __cat_tools.omit_column('pg_catalog.pg_class') +)); +REVOKE ALL ON _cat_tools.pg_class_v FROM public; + + /* * Temporary stub function. We do this so we can use the nice create_function * function that we're about to create to create the real version of this @@ -717,15 +745,20 @@ GRANT SELECT ON cat_tools.pg_class_v TO cat_tools__usage; @generated@ +SELECT __cat_tools.exec(format($fmt$ CREATE OR REPLACE VIEW _cat_tools.pg_attribute_v AS - SELECT a.* + SELECT %s , c.* , t.oid AS typoid - , t.* + , %s FROM pg_attribute a LEFT JOIN _cat_tools.pg_class_v c ON ( c.reloid = a.attrelid ) LEFT JOIN pg_type t ON ( t.oid = a.atttypid ) ; +$fmt$ + , __cat_tools.omit_column('pg_catalog.pg_attribute', array['attmissingval']) + , __cat_tools.omit_column('pg_catalog.pg_type') +)); REVOKE ALL ON _cat_tools.pg_attribute_v FROM public; CREATE OR REPLACE VIEW _cat_tools.column AS @@ -756,8 +789,10 @@ REVOKE ALL ON _cat_tools.column FROM public; @generated@ -- No perms on extension visibility +SELECT __cat_tools.exec(format($fmt$ CREATE OR REPLACE VIEW cat_tools.pg_extension_v AS - SELECT e.oid, e.* + SELECT e.oid + , %s , extnamespace::regnamespace AS extschema -- SED: REQUIRES 9.5! , nspname AS extschema -- SED: PRIOR TO 9.5! @@ -766,6 +801,9 @@ CREATE OR REPLACE VIEW cat_tools.pg_extension_v AS FROM pg_catalog.pg_extension e LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace ; +$fmt$ + , __cat_tools.omit_column('pg_catalog.pg_extension') +)); GRANT SELECT ON cat_tools.pg_extension_v TO cat_tools__usage; CREATE OR REPLACE VIEW cat_tools.column AS @@ -1318,7 +1356,11 @@ BEGIN RAISE DEBUG 'v_work "%"', v_work; -- Get function arguments - v_execute_clause := ' EXECUTE PROCEDURE ' || r_trigger.tgfoid::pg_catalog.regproc || E'\\('; + IF current_setting('server_version')::real >= 11.0 THEN + v_execute_clause := ' EXECUTE FUNCTION ' || r_trigger.tgfoid::pg_catalog.regproc || E'\\('; + ELSE + v_execute_clause := ' EXECUTE PROCEDURE ' || r_trigger.tgfoid::pg_catalog.regproc || E'\\('; + END IF; v_array := regexp_split_to_array( v_work, v_execute_clause ); EXECUTE format( 'SELECT array[ %s ]' @@ -1454,6 +1496,10 @@ CLUSTER _cat_tools.catalog_metadata USING catalog_metadata__pk_object_catalog; /* * Drop "temporary" objects */ +DROP FUNCTION __cat_tools.omit_column( + rel text + , omit name[] -- DEFAULT array['oid'] +); DROP FUNCTION __cat_tools.exec( sql text ); diff --git a/sql/omit_column.sql b/sql/omit_column.sql new file mode 100644 index 0000000..1d95832 --- /dev/null +++ b/sql/omit_column.sql @@ -0,0 +1,16 @@ +CREATE FUNCTION :s.omit_column( + rel text + , omit name[] DEFAULT array['oid'] +) RETURNS text LANGUAGE sql IMMUTABLE AS $body$ +SELECT array_to_string(array( + SELECT attname + FROM pg_attribute a + WHERE attrelid = rel::regclass + AND NOT attisdropped + AND attnum >= 0 + AND attname != ANY( omit ) + ORDER BY attnum + ) + , ', ' +) +$body$; diff --git a/test/expected/general.out b/test/expected/general.out index 008cc56..84e08e2 100644 --- a/test/expected/general.out +++ b/test/expected/general.out @@ -1,5 +1,4 @@ \set ECHO none -1..2 +1..1 ok 1 - Schema __cat_tools should not exist -ok 2 - verify pg_get_object_address # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index 754863c..dbe9615 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -94,6 +94,10 @@ + + + + diff --git a/test/setup.sql b/test/setup.sql index ae7b502..f6735ed 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -16,4 +16,21 @@ RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT current_setting('server_version_num')::int/100 $$; +CREATE FUNCTION pg_temp.omit_column( + rel text + , omit name[] DEFAULT array['oid'] +) RETURNS text LANGUAGE sql IMMUTABLE AS $body$ +SELECT array_to_string(array( + SELECT attname + FROM pg_attribute a + WHERE attrelid = rel::regclass + AND NOT attisdropped + AND attnum >= 0 + AND attname != ANY( omit ) + ORDER BY attnum + ) + , ', ' +) +$body$; + -- vi: expandtab ts=2 sw=2 diff --git a/test/sql/attribute.sql b/test/sql/attribute.sql index afcfe13..59c9486 100644 --- a/test/sql/attribute.sql +++ b/test/sql/attribute.sql @@ -41,6 +41,7 @@ SET LOCAL ROLE :use_role; \set call 'SELECT * FROM %I.%I( %L, %L )' \set n pg_attribute__get + SELECT throws_ok( format( :'call', :'s', :'n' @@ -62,13 +63,23 @@ SELECT throws_ok( , 'Non-existent column throws error' ); +/* + * pg_attributes.attmissingval is type anyarray, which doesn't have an equality + * operator. That breaks results_eq(), so we have to omit it from the column + * list. + */ +SELECT pg_temp.omit_column('pg_catalog.pg_attribute', array['attmissingval']) AS atts +\gset +\set get_attributes 'SELECT ' :atts ' FROM pg_attribute ' +\set call 'SELECT ' :atts ' FROM %I.%I( %L, %L )' + SELECT results_eq( format( :'call', :'s', :'n' , 'pg_catalog.pg_class' , 'relname' ) - , $$SELECT * FROM pg_attribute WHERE attrelid = 'pg_class'::regclass AND attname='relname'$$ + , :'get_attributes' || $$WHERE attrelid = 'pg_class'::regclass AND attname='relname'$$ , 'Verify details of pg_class.relname' ); SELECT results_eq( @@ -77,7 +88,7 @@ SELECT results_eq( , 'pg_catalog.pg_tables' , 'tablename' ) - , $$SELECT * FROM pg_attribute WHERE attrelid = 'pg_tables'::regclass AND attname='tablename'$$ + , :'get_attributes' || $$WHERE attrelid = 'pg_tables'::regclass AND attname='tablename'$$ , 'Verify details of pg_tables.tablename' ); diff --git a/test/sql/extension.sql b/test/sql/extension.sql index b79f160..39cf7f7 100644 --- a/test/sql/extension.sql +++ b/test/sql/extension.sql @@ -32,7 +32,7 @@ SELECT isnt_empty( SELECT bag_eq( $$SELECT * FROM cat_tools.pg_extension__get('cat_tools')$$ , format( - $$SELECT e.oid, e.*, %s, extconfig::regclass[] AS ext_config_table + $$SELECT e.*, %s, extconfig::regclass[] AS ext_config_table FROM pg_extension e JOIN pg_namespace n ON n.oid = extnamespace WHERE extname = 'cat_tools' From e8e2717a4c57c0da1acd85bdef15cd67a8fcc798 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Thu, 7 Nov 2024 15:20:48 -0600 Subject: [PATCH 03/16] Add pgxn-tools CI --- .github/workflows/ci.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b889baa --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +name: CI +on: [push, pull_request] +jobs: + test: + strategy: + matrix: + pg: [17, 16, 15, 14, 13, 12, 11, 10, 9.6, 9.5, 9.4, 9.3, 9.2, 9.1, 9.0, 8.4, 8.3, 8.2] + name: 🐘 PostgreSQL ${{ matrix.pg }} + runs-on: ubuntu-latest + container: pgxn/pgxn-tools + steps: + - name: Start PostgreSQL ${{ matrix.pg }} + run: pg-start ${{ matrix.pg }} + - name: Check out the repo + uses: actions/checkout@v4 + - name: Test on PostgreSQL ${{ matrix.pg }} + run: pg-build-test From 0ff8a9a20ed028ad6994af2f4f30377543348e67 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Thu, 7 Nov 2024 15:46:32 -0600 Subject: [PATCH 04/16] Fix CI version matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b889baa..0946c32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - pg: [17, 16, 15, 14, 13, 12, 11, 10, 9.6, 9.5, 9.4, 9.3, 9.2, 9.1, 9.0, 8.4, 8.3, 8.2] + pg: [17, 16, 15, 14, 13, 12, 11, 10, 9.6, 9.5, 9.4, 9.3, 9.2] name: 🐘 PostgreSQL ${{ matrix.pg }} runs-on: ubuntu-latest container: pgxn/pgxn-tools From 5c59f66e492068b11aea5103117fe97a20bc39b4 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Mon, 11 Nov 2024 13:08:41 -0600 Subject: [PATCH 05/16] Remove .travis.yml --- .travis.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ddca483..0000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: c -before_install: - - wget https://gist.github.com/petere/5893799/raw/apt.postgresql.org.sh - - sudo sh ./apt.postgresql.org.sh - - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs 2>/dev/null)-pgdg main $PGVERSION >> /etc/apt/sources.list.d/pgdg.list" -env: - - PGVERSION=9.6 - - PGVERSION=9.5 - - PGVERSION=9.4 - - PGVERSION=9.3 - - PGVERSION=9.2 - - - PGVERSION=9.6 TARGET='set-test-upgrade test' - - PGVERSION=9.5 TARGET='set-test-upgrade test' - - PGVERSION=9.4 TARGET='set-test-upgrade test' - - PGVERSION=9.3 TARGET='set-test-upgrade test' - - PGVERSION=9.2 TARGET='set-test-upgrade test' - -script: bash ./pg-travis-test.sh From ca7c8c1f2b8acf0e03bd3b748714a6fc6d81cfa5 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Thu, 28 Aug 2025 17:08:55 -0500 Subject: [PATCH 06/16] Add new relkind entries --- META.in.json | 4 +- META.json | 4 +- README.asc | 5 +- cat_tools.control | 2 +- sql/cat_tools--0.2.1--0.3.0.sql | 155 +++++++++++++ sql/cat_tools.sql.in | 12 + test/expected/object_type.out | 378 ++++++++++++++++---------------- test/expected/relation_type.out | 40 ++-- test/sql/object_type.sql | 2 +- test/sql/relation_type.sql | 2 +- 10 files changed, 391 insertions(+), 213 deletions(-) create mode 100644 sql/cat_tools--0.2.1--0.3.0.sql diff --git a/META.in.json b/META.in.json index cb35668..7501152 100644 --- a/META.in.json +++ b/META.in.json @@ -14,7 +14,7 @@ "name": "cat_tools", "X_comment": "REQUIRED. Version of the distribution. http://pgxn.org/spec/#version", - "version": "0.2.1", + "version": "0.3.0", "X_comment": "REQUIRED. Short description of distribution.", "abstract": "Tools for interfacing with the Postgres catalog", @@ -37,7 +37,7 @@ "file": "sql/cat_tools.sql", "X_comment": "REQUIRED. Version the extension is at.", - "version": "0.2.1", + "version": "0.3.0", "X_comment": "Optional: \"abstract\": Description of the extension.", "abstract": "Tools for interfacing with the catalog", diff --git a/META.json b/META.json index ad50c0f..a4c7e7d 100644 --- a/META.json +++ b/META.json @@ -14,7 +14,7 @@ "name": "cat_tools", "X_comment": "REQUIRED. Version of the distribution. http://pgxn.org/spec/#version", - "version": "0.2.1", + "version": "0.3.0", "X_comment": "REQUIRED. Short description of distribution.", "abstract": "Tools for interfacing with the Postgres catalog", @@ -37,7 +37,7 @@ "file": "sql/cat_tools.sql", "X_comment": "REQUIRED. Version the extension is at.", - "version": "0.2.1", + "version": "0.3.0", "X_comment": "Optional: \"abstract\": Description of the extension.", "abstract": "Tools for interfacing with the catalog", diff --git a/README.asc b/README.asc index 9fa8761..bf35c9e 100644 --- a/README.asc +++ b/README.asc @@ -6,9 +6,6 @@ To make use of them, you need to grant `cat_tools__usage` to any roles that need == Current Status -image:https://badge.fury.io/pg/cat_tools.svg["PGXN version",link="https://badge.fury.io/pg/cat_tools"] -image:https://travis-ci.org/decibel/cat_tools.png["Build Status",link="https://travis-ci.org/decibel/cat_tools"] - This is very much a work in progress. If it doesn't do something you need, please https://github.com/decibel/cat_tools/issues[open an issue]! === Supported Versions @@ -58,4 +55,4 @@ Copyright and License Cat Tools is released under a https://github.com/decibel/cattools/blob/master/LICENSE[MIT license]. -Copyright (c) 2016 Jim Nasby . +Copyright (c) 2025 Jim Nasby . diff --git a/cat_tools.control b/cat_tools.control index 5248506..d798380 100644 --- a/cat_tools.control +++ b/cat_tools.control @@ -1,4 +1,4 @@ comment = 'Tools for intorfacing with the catalog' -default_version = '0.2.1' +default_version = '0.3.0' relocatable = false schema = 'cat_tools' diff --git a/sql/cat_tools--0.2.1--0.3.0.sql b/sql/cat_tools--0.2.1--0.3.0.sql new file mode 100644 index 0000000..54c086e --- /dev/null +++ b/sql/cat_tools--0.2.1--0.3.0.sql @@ -0,0 +1,155 @@ +CREATE SCHEMA __cat_tools; + +CREATE FUNCTION __cat_tools.exec( + sql text +) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + RAISE DEBUG 'sql = %', sql; + EXECUTE sql; +END +$body$; + +CREATE FUNCTION __cat_tools.create_function( + function_name text + , args text + , options text + , body text + , grants text DEFAULT NULL + , comment text DEFAULT NULL +) RETURNS void LANGUAGE plpgsql AS $body$ +DECLARE + c_simple_args CONSTANT text := cat_tools.function__arg_types_text(args); + + create_template CONSTANT text := $template$ +CREATE OR REPLACE FUNCTION %s( +%s +) RETURNS %s AS +%L +$template$ + ; + + revoke_template CONSTANT text := $template$ +REVOKE ALL ON FUNCTION %s( +%s +) FROM public; +$template$ + ; + + grant_template CONSTANT text := $template$ +GRANT EXECUTE ON FUNCTION %s( +%s +) TO %s; +$template$ + ; + + comment_template CONSTANT text := $template$ +COMMENT ON FUNCTION %s( +%s +) IS %L; +$template$ + ; + +BEGIN + PERFORM __cat_tools.exec( format( + create_template + , function_name + , args + , options -- TODO: Force search_path if options ~* 'definer' + , body + ) ) + ; + PERFORM __cat_tools.exec( format( + revoke_template + , function_name + , c_simple_args + ) ) + ; + + IF grants IS NOT NULL THEN + PERFORM __cat_tools.exec( format( + grant_template + , function_name + , c_simple_args + , grants + ) ) + ; + END IF; + + IF comment IS NOT NULL THEN + PERFORM __cat_tools.exec( format( + comment_template + , function_name + , c_simple_args + , comment + ) ) + ; + END IF; +END +$body$; + +ALTER TYPE cat_tools.relation_type ADD VALUE 'partitioned table'; +ALTER TYPE cat_tools.relation_type ADD VALUE 'partitioned index'; + +ALTER TYPE cat_tools.relation_relkind ADD VALUE 'p'; +ALTER TYPE cat_tools.relation_relkind ADD VALUE 'I'; + +ALTER TYPE cat_tools.object_type ADD VALUE 'partitioned table' AFTER 'foreign table'; +ALTER TYPE cat_tools.object_type ADD VALUE 'partitioned index' AFTER 'partitioned table'; + + +SELECT __cat_tools.create_function( + 'cat_tools.relation__kind' + , 'relkind cat_tools.relation_relkind' + , 'cat_tools.relation_type LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT CASE relkind + WHEN 'r' THEN 'table' + WHEN 'i' THEN 'index' + WHEN 'S' THEN 'sequence' + WHEN 't' THEN 'toast table' + WHEN 'v' THEN 'view' + WHEN 'c' THEN 'materialized view' + WHEN 'f' THEN 'composite type' + WHEN 'm' THEN 'foreign table' + WHEN 'p' THEN 'partitioned table' + WHEN 'I' THEN 'partitioned index' +END::cat_tools.relation_type +$body$ + , 'cat_tools__usage' + , 'Mapping from to a ' +); + +SELECT __cat_tools.create_function( + 'cat_tools.relation__relkind' + , 'kind cat_tools.relation_type' + , 'cat_tools.relation_relkind LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT CASE kind + WHEN 'table' THEN 'r' + WHEN 'index' THEN 'i' + WHEN 'sequence' THEN 'S' + WHEN 'toast table' THEN 't' + WHEN 'view' THEN 'v' + WHEN 'materialized view' THEN 'c' + WHEN 'composite type' THEN 'f' + WHEN 'foreign table' THEN 'm' + WHEN 'partitioned table' THEN 'p' + WHEN 'partitioned index' THEN 'I' +END::cat_tools.relation_relkind +$body$ + , 'cat_tools__usage' + , 'Mapping from to a value' +); + +DROP FUNCTION __cat_tools.exec( + sql text +); +DROP FUNCTION __cat_tools.create_function( + function_name text + , args text + , options text + , body text + , grants text + , comment text +); +DROP SCHEMA __cat_tools; diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index fba34a6..273374b 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -283,6 +283,8 @@ CREATE TYPE cat_tools.relation_type AS ENUM( , 'materialized view' , 'composite type' , 'foreign table' + , 'partitioned table' + , 'partitioned index' ); COMMENT ON TYPE cat_tools.relation_type IS $$Types of objects stored in `pg_class`$$; @@ -295,6 +297,8 @@ CREATE TYPE cat_tools.relation_relkind AS ENUM( , 'c' , 'f' , 'm' + , 'p' + , 'I' ); COMMENT ON TYPE cat_tools.relation_relkind IS $$Valid values for `pg_class.relkind`$$; @@ -310,6 +314,8 @@ CREATE TYPE cat_tools.object_type AS ENUM( , 'materialized view' , 'composite type' , 'foreign table' + , 'partitioned table' + , 'partitioned index' /* * NOTE! These are a bit weird because columns live in pg_attribute, but * address stuff recognizes columns as part of pg_class with a subobjid <> 0! @@ -471,6 +477,8 @@ SELECT ( , 'materialized view' , 'composite type' , 'foreign table' + , 'partitioned table' + , 'partitioned index' ]::cat_tools.object_type[] ) THEN 'pg_class' WHEN object_type = ANY( '{domain constraint,table constraint}'::cat_tools.object_type[] ) @@ -655,6 +663,8 @@ SELECT CASE relkind WHEN 'c' THEN 'materialized view' WHEN 'f' THEN 'composite type' WHEN 'm' THEN 'foreign table' + WHEN 'p' THEN 'partitioned table' + WHEN 'I' THEN 'partitioned index' END::cat_tools.relation_type $body$ , 'cat_tools__usage' @@ -675,6 +685,8 @@ SELECT CASE kind WHEN 'materialized view' THEN 'c' WHEN 'composite type' THEN 'f' WHEN 'foreign table' THEN 'm' + WHEN 'partitioned table' THEN 'p' + WHEN 'partitioned index' THEN 'I' END::cat_tools.relation_relkind $body$ , 'cat_tools__usage' diff --git a/test/expected/object_type.out b/test/expected/object_type.out index ea37620..1c9029b 100644 --- a/test/expected/object_type.out +++ b/test/expected/object_type.out @@ -1,5 +1,5 @@ \set ECHO none -1..222 +1..230 ok 1 - sanity check size of pg_temp.extra_types() ok 2 - sanity check size of pg_temp.obj_type ok 3 - Permission denied trying to use types @@ -38,188 +38,196 @@ ok 35 - check addressability for object type 'operator' ok 36 - check addressability for object type 'operator class' ok 37 - check addressability for object type 'operator family' ok 38 - check addressability for object type 'operator of access method' -ok 39 - check addressability for object type 'policy' -ok 40 - check addressability for object type 'role' -ok 41 - check addressability for object type 'rule' -ok 42 - check addressability for object type 'schema' -ok 43 - check addressability for object type 'sequence' -ok 44 - check addressability for object type 'sequence column' -ok 45 - check addressability for object type 'server' -ok 46 - check addressability for object type 'table' -ok 47 - check addressability for object type 'table column' -ok 48 - check addressability for object type 'table constraint' -ok 49 - check addressability for object type 'tablespace' -ok 50 - check addressability for object type 'text search configuration' -ok 51 - check addressability for object type 'text search dictionary' -ok 52 - check addressability for object type 'text search parser' -ok 53 - check addressability for object type 'text search template' -ok 54 - check addressability for object type 'toast table' -ok 55 - check addressability for object type 'toast table column' -ok 56 - check addressability for object type 'transform' -ok 57 - check addressability for object type 'trigger' -ok 58 - check addressability for object type 'type' -ok 59 - check addressability for object type 'user mapping' -ok 60 - check addressability for object type 'view' -ok 61 - check addressability for object type 'view column' -ok 62 - lives_ok: SELECT * FROM cat_tools.object__catalog('access method') -ok 63 - lives_ok: SELECT * FROM cat_tools.object__catalog('aggregate') -ok 64 - lives_ok: SELECT * FROM cat_tools.object__catalog('cast') -ok 65 - lives_ok: SELECT * FROM cat_tools.object__catalog('collation') -ok 66 - lives_ok: SELECT * FROM cat_tools.object__catalog('composite type') -ok 67 - lives_ok: SELECT * FROM cat_tools.object__catalog('composite type column') -ok 68 - lives_ok: SELECT * FROM cat_tools.object__catalog('conversion') -ok 69 - lives_ok: SELECT * FROM cat_tools.object__catalog('database') -ok 70 - lives_ok: SELECT * FROM cat_tools.object__catalog('default acl') -ok 71 - lives_ok: SELECT * FROM cat_tools.object__catalog('default value') -ok 72 - lives_ok: SELECT * FROM cat_tools.object__catalog('domain constraint') -ok 73 - lives_ok: SELECT * FROM cat_tools.object__catalog('event trigger') -ok 74 - lives_ok: SELECT * FROM cat_tools.object__catalog('extension') -ok 75 - lives_ok: SELECT * FROM cat_tools.object__catalog('foreign table') -ok 76 - lives_ok: SELECT * FROM cat_tools.object__catalog('foreign table column') -ok 77 - lives_ok: SELECT * FROM cat_tools.object__catalog('foreign-data wrapper') -ok 78 - lives_ok: SELECT * FROM cat_tools.object__catalog('function') -ok 79 - lives_ok: SELECT * FROM cat_tools.object__catalog('function of access method') -ok 80 - lives_ok: SELECT * FROM cat_tools.object__catalog('index') -ok 81 - lives_ok: SELECT * FROM cat_tools.object__catalog('index column') -ok 82 - lives_ok: SELECT * FROM cat_tools.object__catalog('language') -ok 83 - lives_ok: SELECT * FROM cat_tools.object__catalog('large object') -ok 84 - lives_ok: SELECT * FROM cat_tools.object__catalog('materialized view') -ok 85 - lives_ok: SELECT * FROM cat_tools.object__catalog('materialized view column') -ok 86 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator') -ok 87 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator class') -ok 88 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator family') -ok 89 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator of access method') -ok 90 - lives_ok: SELECT * FROM cat_tools.object__catalog('policy') -ok 91 - lives_ok: SELECT * FROM cat_tools.object__catalog('role') -ok 92 - lives_ok: SELECT * FROM cat_tools.object__catalog('rule') -ok 93 - lives_ok: SELECT * FROM cat_tools.object__catalog('schema') -ok 94 - lives_ok: SELECT * FROM cat_tools.object__catalog('sequence') -ok 95 - lives_ok: SELECT * FROM cat_tools.object__catalog('sequence column') -ok 96 - lives_ok: SELECT * FROM cat_tools.object__catalog('server') -ok 97 - lives_ok: SELECT * FROM cat_tools.object__catalog('table') -ok 98 - lives_ok: SELECT * FROM cat_tools.object__catalog('table column') -ok 99 - lives_ok: SELECT * FROM cat_tools.object__catalog('table constraint') -ok 100 - lives_ok: SELECT * FROM cat_tools.object__catalog('tablespace') -ok 101 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search configuration') -ok 102 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search dictionary') -ok 103 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search parser') -ok 104 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search template') -ok 105 - lives_ok: SELECT * FROM cat_tools.object__catalog('toast table') -ok 106 - lives_ok: SELECT * FROM cat_tools.object__catalog('toast table column') -ok 107 - lives_ok: SELECT * FROM cat_tools.object__catalog('transform') -ok 108 - lives_ok: SELECT * FROM cat_tools.object__catalog('trigger') -ok 109 - lives_ok: SELECT * FROM cat_tools.object__catalog('type') -ok 110 - lives_ok: SELECT * FROM cat_tools.object__catalog('user mapping') -ok 111 - lives_ok: SELECT * FROM cat_tools.object__catalog('view') -ok 112 - lives_ok: SELECT * FROM cat_tools.object__catalog('view column') -ok 113 - lives_ok: SELECT * FROM cat_tools.object__reg_type('access method') -ok 114 - lives_ok: SELECT * FROM cat_tools.object__reg_type('aggregate') -ok 115 - lives_ok: SELECT * FROM cat_tools.object__reg_type('cast') -ok 116 - lives_ok: SELECT * FROM cat_tools.object__reg_type('collation') -ok 117 - lives_ok: SELECT * FROM cat_tools.object__reg_type('composite type') -ok 118 - lives_ok: SELECT * FROM cat_tools.object__reg_type('composite type column') -ok 119 - lives_ok: SELECT * FROM cat_tools.object__reg_type('conversion') -ok 120 - lives_ok: SELECT * FROM cat_tools.object__reg_type('database') -ok 121 - lives_ok: SELECT * FROM cat_tools.object__reg_type('default acl') -ok 122 - lives_ok: SELECT * FROM cat_tools.object__reg_type('default value') -ok 123 - lives_ok: SELECT * FROM cat_tools.object__reg_type('domain constraint') -ok 124 - lives_ok: SELECT * FROM cat_tools.object__reg_type('event trigger') -ok 125 - lives_ok: SELECT * FROM cat_tools.object__reg_type('extension') -ok 126 - lives_ok: SELECT * FROM cat_tools.object__reg_type('foreign table') -ok 127 - lives_ok: SELECT * FROM cat_tools.object__reg_type('foreign table column') -ok 128 - lives_ok: SELECT * FROM cat_tools.object__reg_type('foreign-data wrapper') -ok 129 - lives_ok: SELECT * FROM cat_tools.object__reg_type('function') -ok 130 - lives_ok: SELECT * FROM cat_tools.object__reg_type('function of access method') -ok 131 - lives_ok: SELECT * FROM cat_tools.object__reg_type('index') -ok 132 - lives_ok: SELECT * FROM cat_tools.object__reg_type('index column') -ok 133 - lives_ok: SELECT * FROM cat_tools.object__reg_type('language') -ok 134 - lives_ok: SELECT * FROM cat_tools.object__reg_type('large object') -ok 135 - lives_ok: SELECT * FROM cat_tools.object__reg_type('materialized view') -ok 136 - lives_ok: SELECT * FROM cat_tools.object__reg_type('materialized view column') -ok 137 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator') -ok 138 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator class') -ok 139 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator family') -ok 140 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator of access method') -ok 141 - lives_ok: SELECT * FROM cat_tools.object__reg_type('policy') -ok 142 - lives_ok: SELECT * FROM cat_tools.object__reg_type('role') -ok 143 - lives_ok: SELECT * FROM cat_tools.object__reg_type('rule') -ok 144 - lives_ok: SELECT * FROM cat_tools.object__reg_type('schema') -ok 145 - lives_ok: SELECT * FROM cat_tools.object__reg_type('sequence') -ok 146 - lives_ok: SELECT * FROM cat_tools.object__reg_type('sequence column') -ok 147 - lives_ok: SELECT * FROM cat_tools.object__reg_type('server') -ok 148 - lives_ok: SELECT * FROM cat_tools.object__reg_type('table') -ok 149 - lives_ok: SELECT * FROM cat_tools.object__reg_type('table column') -ok 150 - lives_ok: SELECT * FROM cat_tools.object__reg_type('table constraint') -ok 151 - lives_ok: SELECT * FROM cat_tools.object__reg_type('tablespace') -ok 152 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search configuration') -ok 153 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search dictionary') -ok 154 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search parser') -ok 155 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search template') -ok 156 - lives_ok: SELECT * FROM cat_tools.object__reg_type('toast table') -ok 157 - lives_ok: SELECT * FROM cat_tools.object__reg_type('toast table column') -ok 158 - lives_ok: SELECT * FROM cat_tools.object__reg_type('transform') -ok 159 - lives_ok: SELECT * FROM cat_tools.object__reg_type('trigger') -ok 160 - lives_ok: SELECT * FROM cat_tools.object__reg_type('type') -ok 161 - lives_ok: SELECT * FROM cat_tools.object__reg_type('user mapping') -ok 162 - lives_ok: SELECT * FROM cat_tools.object__reg_type('view') -ok 163 - lives_ok: SELECT * FROM cat_tools.object__reg_type('view column') -ok 164 - Verify cat_tools.object__address_classid('access method') -ok 165 - Verify cat_tools.object__address_classid('aggregate') -ok 166 - Verify cat_tools.object__address_classid('cast') -ok 167 - Verify cat_tools.object__address_classid('collation') -ok 168 - Verify cat_tools.object__address_classid('composite type') -ok 169 - Verify cat_tools.object__address_classid('composite type column') -ok 170 - Verify cat_tools.object__address_classid('conversion') -ok 171 - Verify cat_tools.object__address_classid('database') -ok 172 - Verify cat_tools.object__address_classid('default acl') -ok 173 - Verify cat_tools.object__address_classid('default value') -ok 174 - Verify cat_tools.object__address_classid('domain constraint') -ok 175 - Verify cat_tools.object__address_classid('event trigger') -ok 176 - Verify cat_tools.object__address_classid('extension') -ok 177 - Verify cat_tools.object__address_classid('foreign table') -ok 178 - Verify cat_tools.object__address_classid('foreign table column') -ok 179 - Verify cat_tools.object__address_classid('foreign-data wrapper') -ok 180 - Verify cat_tools.object__address_classid('function') -ok 181 - Verify cat_tools.object__address_classid('function of access method') -ok 182 - Verify cat_tools.object__address_classid('index') -ok 183 - Verify cat_tools.object__address_classid('index column') -ok 184 - Verify cat_tools.object__address_classid('language') -ok 185 - Verify cat_tools.object__address_classid('large object') -ok 186 - Verify cat_tools.object__address_classid('materialized view') -ok 187 - Verify cat_tools.object__address_classid('materialized view column') -ok 188 - Verify cat_tools.object__address_classid('operator') -ok 189 - Verify cat_tools.object__address_classid('operator class') -ok 190 - Verify cat_tools.object__address_classid('operator family') -ok 191 - Verify cat_tools.object__address_classid('operator of access method') -ok 192 - Verify cat_tools.object__address_classid('policy') -ok 193 - Verify cat_tools.object__address_classid('role') -ok 194 - Verify cat_tools.object__address_classid('rule') -ok 195 - Verify cat_tools.object__address_classid('schema') -ok 196 - Verify cat_tools.object__address_classid('sequence') -ok 197 - Verify cat_tools.object__address_classid('sequence column') -ok 198 - Verify cat_tools.object__address_classid('server') -ok 199 - Verify cat_tools.object__address_classid('table') -ok 200 - Verify cat_tools.object__address_classid('table column') -ok 201 - Verify cat_tools.object__address_classid('table constraint') -ok 202 - Verify cat_tools.object__address_classid('tablespace') -ok 203 - Verify cat_tools.object__address_classid('text search configuration') -ok 204 - Verify cat_tools.object__address_classid('text search dictionary') -ok 205 - Verify cat_tools.object__address_classid('text search parser') -ok 206 - Verify cat_tools.object__address_classid('text search template') -ok 207 - Verify cat_tools.object__address_classid('toast table') -ok 208 - Verify cat_tools.object__address_classid('toast table column') -ok 209 - Verify cat_tools.object__address_classid('transform') -ok 210 - Verify cat_tools.object__address_classid('trigger') -ok 211 - Verify cat_tools.object__address_classid('type') -ok 212 - Verify cat_tools.object__address_classid('user mapping') -ok 213 - Verify cat_tools.object__address_classid('view') -ok 214 - Verify cat_tools.object__address_classid('view column') -ok 215 - Change search_path -ok 216 - Create bogus pg_class table -ok 217 - Create bogus regclass type -ok 218 - Simple 'pg_class'::pg_catalog.regclass should not return pg_catalog.pg_class -ok 219 - Simple 'regclass'::regtype should not return pg_catalog.regtype -ok 220 - cat_tools.object__catalog('table') returns pg_catalog.pg_class -ok 221 - cat_tools.object__catalog('table') returns pg_catalog.pg_class -ok 222 - Verify objects__shared_src() returns correct values +ok 39 - check addressability for object type 'partitioned index' +ok 40 - check addressability for object type 'partitioned table' +ok 41 - check addressability for object type 'policy' +ok 42 - check addressability for object type 'role' +ok 43 - check addressability for object type 'rule' +ok 44 - check addressability for object type 'schema' +ok 45 - check addressability for object type 'sequence' +ok 46 - check addressability for object type 'sequence column' +ok 47 - check addressability for object type 'server' +ok 48 - check addressability for object type 'table' +ok 49 - check addressability for object type 'table column' +ok 50 - check addressability for object type 'table constraint' +ok 51 - check addressability for object type 'tablespace' +ok 52 - check addressability for object type 'text search configuration' +ok 53 - check addressability for object type 'text search dictionary' +ok 54 - check addressability for object type 'text search parser' +ok 55 - check addressability for object type 'text search template' +ok 56 - check addressability for object type 'toast table' +ok 57 - check addressability for object type 'toast table column' +ok 58 - check addressability for object type 'transform' +ok 59 - check addressability for object type 'trigger' +ok 60 - check addressability for object type 'type' +ok 61 - check addressability for object type 'user mapping' +ok 62 - check addressability for object type 'view' +ok 63 - check addressability for object type 'view column' +ok 64 - lives_ok: SELECT * FROM cat_tools.object__catalog('access method') +ok 65 - lives_ok: SELECT * FROM cat_tools.object__catalog('aggregate') +ok 66 - lives_ok: SELECT * FROM cat_tools.object__catalog('cast') +ok 67 - lives_ok: SELECT * FROM cat_tools.object__catalog('collation') +ok 68 - lives_ok: SELECT * FROM cat_tools.object__catalog('composite type') +ok 69 - lives_ok: SELECT * FROM cat_tools.object__catalog('composite type column') +ok 70 - lives_ok: SELECT * FROM cat_tools.object__catalog('conversion') +ok 71 - lives_ok: SELECT * FROM cat_tools.object__catalog('database') +ok 72 - lives_ok: SELECT * FROM cat_tools.object__catalog('default acl') +ok 73 - lives_ok: SELECT * FROM cat_tools.object__catalog('default value') +ok 74 - lives_ok: SELECT * FROM cat_tools.object__catalog('domain constraint') +ok 75 - lives_ok: SELECT * FROM cat_tools.object__catalog('event trigger') +ok 76 - lives_ok: SELECT * FROM cat_tools.object__catalog('extension') +ok 77 - lives_ok: SELECT * FROM cat_tools.object__catalog('foreign table') +ok 78 - lives_ok: SELECT * FROM cat_tools.object__catalog('foreign table column') +ok 79 - lives_ok: SELECT * FROM cat_tools.object__catalog('foreign-data wrapper') +ok 80 - lives_ok: SELECT * FROM cat_tools.object__catalog('function') +ok 81 - lives_ok: SELECT * FROM cat_tools.object__catalog('function of access method') +ok 82 - lives_ok: SELECT * FROM cat_tools.object__catalog('index') +ok 83 - lives_ok: SELECT * FROM cat_tools.object__catalog('index column') +ok 84 - lives_ok: SELECT * FROM cat_tools.object__catalog('language') +ok 85 - lives_ok: SELECT * FROM cat_tools.object__catalog('large object') +ok 86 - lives_ok: SELECT * FROM cat_tools.object__catalog('materialized view') +ok 87 - lives_ok: SELECT * FROM cat_tools.object__catalog('materialized view column') +ok 88 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator') +ok 89 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator class') +ok 90 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator family') +ok 91 - lives_ok: SELECT * FROM cat_tools.object__catalog('operator of access method') +ok 92 - lives_ok: SELECT * FROM cat_tools.object__catalog('partitioned index') +ok 93 - lives_ok: SELECT * FROM cat_tools.object__catalog('partitioned table') +ok 94 - lives_ok: SELECT * FROM cat_tools.object__catalog('policy') +ok 95 - lives_ok: SELECT * FROM cat_tools.object__catalog('role') +ok 96 - lives_ok: SELECT * FROM cat_tools.object__catalog('rule') +ok 97 - lives_ok: SELECT * FROM cat_tools.object__catalog('schema') +ok 98 - lives_ok: SELECT * FROM cat_tools.object__catalog('sequence') +ok 99 - lives_ok: SELECT * FROM cat_tools.object__catalog('sequence column') +ok 100 - lives_ok: SELECT * FROM cat_tools.object__catalog('server') +ok 101 - lives_ok: SELECT * FROM cat_tools.object__catalog('table') +ok 102 - lives_ok: SELECT * FROM cat_tools.object__catalog('table column') +ok 103 - lives_ok: SELECT * FROM cat_tools.object__catalog('table constraint') +ok 104 - lives_ok: SELECT * FROM cat_tools.object__catalog('tablespace') +ok 105 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search configuration') +ok 106 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search dictionary') +ok 107 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search parser') +ok 108 - lives_ok: SELECT * FROM cat_tools.object__catalog('text search template') +ok 109 - lives_ok: SELECT * FROM cat_tools.object__catalog('toast table') +ok 110 - lives_ok: SELECT * FROM cat_tools.object__catalog('toast table column') +ok 111 - lives_ok: SELECT * FROM cat_tools.object__catalog('transform') +ok 112 - lives_ok: SELECT * FROM cat_tools.object__catalog('trigger') +ok 113 - lives_ok: SELECT * FROM cat_tools.object__catalog('type') +ok 114 - lives_ok: SELECT * FROM cat_tools.object__catalog('user mapping') +ok 115 - lives_ok: SELECT * FROM cat_tools.object__catalog('view') +ok 116 - lives_ok: SELECT * FROM cat_tools.object__catalog('view column') +ok 117 - lives_ok: SELECT * FROM cat_tools.object__reg_type('access method') +ok 118 - lives_ok: SELECT * FROM cat_tools.object__reg_type('aggregate') +ok 119 - lives_ok: SELECT * FROM cat_tools.object__reg_type('cast') +ok 120 - lives_ok: SELECT * FROM cat_tools.object__reg_type('collation') +ok 121 - lives_ok: SELECT * FROM cat_tools.object__reg_type('composite type') +ok 122 - lives_ok: SELECT * FROM cat_tools.object__reg_type('composite type column') +ok 123 - lives_ok: SELECT * FROM cat_tools.object__reg_type('conversion') +ok 124 - lives_ok: SELECT * FROM cat_tools.object__reg_type('database') +ok 125 - lives_ok: SELECT * FROM cat_tools.object__reg_type('default acl') +ok 126 - lives_ok: SELECT * FROM cat_tools.object__reg_type('default value') +ok 127 - lives_ok: SELECT * FROM cat_tools.object__reg_type('domain constraint') +ok 128 - lives_ok: SELECT * FROM cat_tools.object__reg_type('event trigger') +ok 129 - lives_ok: SELECT * FROM cat_tools.object__reg_type('extension') +ok 130 - lives_ok: SELECT * FROM cat_tools.object__reg_type('foreign table') +ok 131 - lives_ok: SELECT * FROM cat_tools.object__reg_type('foreign table column') +ok 132 - lives_ok: SELECT * FROM cat_tools.object__reg_type('foreign-data wrapper') +ok 133 - lives_ok: SELECT * FROM cat_tools.object__reg_type('function') +ok 134 - lives_ok: SELECT * FROM cat_tools.object__reg_type('function of access method') +ok 135 - lives_ok: SELECT * FROM cat_tools.object__reg_type('index') +ok 136 - lives_ok: SELECT * FROM cat_tools.object__reg_type('index column') +ok 137 - lives_ok: SELECT * FROM cat_tools.object__reg_type('language') +ok 138 - lives_ok: SELECT * FROM cat_tools.object__reg_type('large object') +ok 139 - lives_ok: SELECT * FROM cat_tools.object__reg_type('materialized view') +ok 140 - lives_ok: SELECT * FROM cat_tools.object__reg_type('materialized view column') +ok 141 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator') +ok 142 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator class') +ok 143 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator family') +ok 144 - lives_ok: SELECT * FROM cat_tools.object__reg_type('operator of access method') +ok 145 - lives_ok: SELECT * FROM cat_tools.object__reg_type('partitioned index') +ok 146 - lives_ok: SELECT * FROM cat_tools.object__reg_type('partitioned table') +ok 147 - lives_ok: SELECT * FROM cat_tools.object__reg_type('policy') +ok 148 - lives_ok: SELECT * FROM cat_tools.object__reg_type('role') +ok 149 - lives_ok: SELECT * FROM cat_tools.object__reg_type('rule') +ok 150 - lives_ok: SELECT * FROM cat_tools.object__reg_type('schema') +ok 151 - lives_ok: SELECT * FROM cat_tools.object__reg_type('sequence') +ok 152 - lives_ok: SELECT * FROM cat_tools.object__reg_type('sequence column') +ok 153 - lives_ok: SELECT * FROM cat_tools.object__reg_type('server') +ok 154 - lives_ok: SELECT * FROM cat_tools.object__reg_type('table') +ok 155 - lives_ok: SELECT * FROM cat_tools.object__reg_type('table column') +ok 156 - lives_ok: SELECT * FROM cat_tools.object__reg_type('table constraint') +ok 157 - lives_ok: SELECT * FROM cat_tools.object__reg_type('tablespace') +ok 158 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search configuration') +ok 159 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search dictionary') +ok 160 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search parser') +ok 161 - lives_ok: SELECT * FROM cat_tools.object__reg_type('text search template') +ok 162 - lives_ok: SELECT * FROM cat_tools.object__reg_type('toast table') +ok 163 - lives_ok: SELECT * FROM cat_tools.object__reg_type('toast table column') +ok 164 - lives_ok: SELECT * FROM cat_tools.object__reg_type('transform') +ok 165 - lives_ok: SELECT * FROM cat_tools.object__reg_type('trigger') +ok 166 - lives_ok: SELECT * FROM cat_tools.object__reg_type('type') +ok 167 - lives_ok: SELECT * FROM cat_tools.object__reg_type('user mapping') +ok 168 - lives_ok: SELECT * FROM cat_tools.object__reg_type('view') +ok 169 - lives_ok: SELECT * FROM cat_tools.object__reg_type('view column') +ok 170 - Verify cat_tools.object__address_classid('access method') +ok 171 - Verify cat_tools.object__address_classid('aggregate') +ok 172 - Verify cat_tools.object__address_classid('cast') +ok 173 - Verify cat_tools.object__address_classid('collation') +ok 174 - Verify cat_tools.object__address_classid('composite type') +ok 175 - Verify cat_tools.object__address_classid('composite type column') +ok 176 - Verify cat_tools.object__address_classid('conversion') +ok 177 - Verify cat_tools.object__address_classid('database') +ok 178 - Verify cat_tools.object__address_classid('default acl') +ok 179 - Verify cat_tools.object__address_classid('default value') +ok 180 - Verify cat_tools.object__address_classid('domain constraint') +ok 181 - Verify cat_tools.object__address_classid('event trigger') +ok 182 - Verify cat_tools.object__address_classid('extension') +ok 183 - Verify cat_tools.object__address_classid('foreign table') +ok 184 - Verify cat_tools.object__address_classid('foreign table column') +ok 185 - Verify cat_tools.object__address_classid('foreign-data wrapper') +ok 186 - Verify cat_tools.object__address_classid('function') +ok 187 - Verify cat_tools.object__address_classid('function of access method') +ok 188 - Verify cat_tools.object__address_classid('index') +ok 189 - Verify cat_tools.object__address_classid('index column') +ok 190 - Verify cat_tools.object__address_classid('language') +ok 191 - Verify cat_tools.object__address_classid('large object') +ok 192 - Verify cat_tools.object__address_classid('materialized view') +ok 193 - Verify cat_tools.object__address_classid('materialized view column') +ok 194 - Verify cat_tools.object__address_classid('operator') +ok 195 - Verify cat_tools.object__address_classid('operator class') +ok 196 - Verify cat_tools.object__address_classid('operator family') +ok 197 - Verify cat_tools.object__address_classid('operator of access method') +ok 198 - Verify cat_tools.object__address_classid('partitioned index') +ok 199 - Verify cat_tools.object__address_classid('partitioned table') +ok 200 - Verify cat_tools.object__address_classid('policy') +ok 201 - Verify cat_tools.object__address_classid('role') +ok 202 - Verify cat_tools.object__address_classid('rule') +ok 203 - Verify cat_tools.object__address_classid('schema') +ok 204 - Verify cat_tools.object__address_classid('sequence') +ok 205 - Verify cat_tools.object__address_classid('sequence column') +ok 206 - Verify cat_tools.object__address_classid('server') +ok 207 - Verify cat_tools.object__address_classid('table') +ok 208 - Verify cat_tools.object__address_classid('table column') +ok 209 - Verify cat_tools.object__address_classid('table constraint') +ok 210 - Verify cat_tools.object__address_classid('tablespace') +ok 211 - Verify cat_tools.object__address_classid('text search configuration') +ok 212 - Verify cat_tools.object__address_classid('text search dictionary') +ok 213 - Verify cat_tools.object__address_classid('text search parser') +ok 214 - Verify cat_tools.object__address_classid('text search template') +ok 215 - Verify cat_tools.object__address_classid('toast table') +ok 216 - Verify cat_tools.object__address_classid('toast table column') +ok 217 - Verify cat_tools.object__address_classid('transform') +ok 218 - Verify cat_tools.object__address_classid('trigger') +ok 219 - Verify cat_tools.object__address_classid('type') +ok 220 - Verify cat_tools.object__address_classid('user mapping') +ok 221 - Verify cat_tools.object__address_classid('view') +ok 222 - Verify cat_tools.object__address_classid('view column') +ok 223 - Change search_path +ok 224 - Create bogus pg_class table +ok 225 - Create bogus regclass type +ok 226 - Simple 'pg_class'::pg_catalog.regclass should not return pg_catalog.pg_class +ok 227 - Simple 'regclass'::regtype should not return pg_catalog.regtype +ok 228 - cat_tools.object__catalog('table') returns pg_catalog.pg_class +ok 229 - cat_tools.object__catalog('table') returns pg_catalog.pg_class +ok 230 - Verify objects__shared_src() returns correct values # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/relation_type.out b/test/expected/relation_type.out index c8d4172..f65ad92 100644 --- a/test/expected/relation_type.out +++ b/test/expected/relation_type.out @@ -1,5 +1,5 @@ \set ECHO none -1..31 +1..37 ok 1 - Verify count from kinds ok 2 - Simple sanity check of relation__kind() ok 3 - Simple sanity check of relation__relkind() @@ -15,20 +15,26 @@ ok 12 - SELECT cat_tools.relation_relkind('view') ok 13 - SELECT cat_tools.relation_relkind('materialized view') ok 14 - SELECT cat_tools.relation_relkind('composite type') ok 15 - SELECT cat_tools.relation_relkind('foreign table') -ok 16 - SELECT cat_tools.relation_type('r') -ok 17 - SELECT cat_tools.relation_type('i') -ok 18 - SELECT cat_tools.relation_type('S') -ok 19 - SELECT cat_tools.relation_type('t') -ok 20 - SELECT cat_tools.relation_type('v') -ok 21 - SELECT cat_tools.relation_type('c') -ok 22 - SELECT cat_tools.relation_type('f') -ok 23 - SELECT cat_tools.relation_type('m') -ok 24 - SELECT cat_tools.relation_type('r'::"char") -ok 25 - SELECT cat_tools.relation_type('i'::"char") -ok 26 - SELECT cat_tools.relation_type('S'::"char") -ok 27 - SELECT cat_tools.relation_type('t'::"char") -ok 28 - SELECT cat_tools.relation_type('v'::"char") -ok 29 - SELECT cat_tools.relation_type('c'::"char") -ok 30 - SELECT cat_tools.relation_type('f'::"char") -ok 31 - SELECT cat_tools.relation_type('m'::"char") +ok 16 - SELECT cat_tools.relation_relkind('partitioned table') +ok 17 - SELECT cat_tools.relation_relkind('partitioned index') +ok 18 - SELECT cat_tools.relation_type('r') +ok 19 - SELECT cat_tools.relation_type('i') +ok 20 - SELECT cat_tools.relation_type('S') +ok 21 - SELECT cat_tools.relation_type('t') +ok 22 - SELECT cat_tools.relation_type('v') +ok 23 - SELECT cat_tools.relation_type('c') +ok 24 - SELECT cat_tools.relation_type('f') +ok 25 - SELECT cat_tools.relation_type('m') +ok 26 - SELECT cat_tools.relation_type('p') +ok 27 - SELECT cat_tools.relation_type('I') +ok 28 - SELECT cat_tools.relation_type('r'::"char") +ok 29 - SELECT cat_tools.relation_type('i'::"char") +ok 30 - SELECT cat_tools.relation_type('S'::"char") +ok 31 - SELECT cat_tools.relation_type('t'::"char") +ok 32 - SELECT cat_tools.relation_type('v'::"char") +ok 33 - SELECT cat_tools.relation_type('c'::"char") +ok 34 - SELECT cat_tools.relation_type('f'::"char") +ok 35 - SELECT cat_tools.relation_type('m'::"char") +ok 36 - SELECT cat_tools.relation_type('p'::"char") +ok 37 - SELECT cat_tools.relation_type('I'::"char") # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/sql/object_type.sql b/test/sql/object_type.sql index e230e9e..3123c4d 100644 --- a/test/sql/object_type.sql +++ b/test/sql/object_type.sql @@ -72,7 +72,7 @@ SELECT is( ); SELECT is( (SELECT count(*)::int FROM obj_type) - , 51 + , 53 , 'sanity check size of pg_temp.obj_type' ); diff --git a/test/sql/relation_type.sql b/test/sql/relation_type.sql index 69beaf1..02999cd 100644 --- a/test/sql/relation_type.sql +++ b/test/sql/relation_type.sql @@ -27,7 +27,7 @@ SELECT plan( SELECT is( (SELECT count(*)::int FROM kinds) - , 8 + , 10 , 'Verify count from kinds' ); From 700ac3b420f2a7eeba841098de1eeca48549547e Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 29 Aug 2025 15:24:42 -0500 Subject: [PATCH 07/16] Add relation__is_temp and relation__is_catalog functions --- README.asc | 2 ++ sql/cat_tools.sql.in | 28 +++++++++++++++ test/expected/relation__is_catalog.out | 8 +++++ test/expected/relation__is_temp.out | 8 +++++ test/expected/zzz_build.out | 4 +++ test/sql/relation__is_catalog.sql | 47 ++++++++++++++++++++++++++ test/sql/relation__is_temp.sql | 47 ++++++++++++++++++++++++++ 7 files changed, 144 insertions(+) create mode 100644 test/expected/relation__is_catalog.out create mode 100644 test/expected/relation__is_temp.out create mode 100644 test/sql/relation__is_catalog.sql create mode 100644 test/sql/relation__is_temp.sql diff --git a/README.asc b/README.asc index bf35c9e..31152f9 100644 --- a/README.asc +++ b/README.asc @@ -36,6 +36,8 @@ Works on Postgres 9.3 and above. * `cat_tools.regprocedure(function_name, arguments)` - Returns regprocedure for function_name and it's full set of arguments * `cat_tools.relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `cat_tools.relation_type` * `cat_tools.relation__relkind(relation_type)` - Mapping from `cat_tools.relation_type` to a `pg_class.relkind` value +* `cat_tools.relation__is_catalog(relation regclass)` - Returns true if the relation is in the `pg_catalog` schema +* `cat_tools.relation__is_temp(relation regclass)` - Returns true if the relation is a temporary table (lives in a schema that starts with 'pg_temp') * `cat_tools.trigger__args_as_text(text)` - Converts the arguments for a trigger function (as returned by `trigger__parse()`) to text (for backwards compatibility). * `cat_tools.trigger__get_oid(trigger_table, trigger_name)` - oid of a trigger. Throws error if trigger doesn't exits. * `cat_tools.trigger__get_oid__loose(trigger_table, trigger_name)` - oid of a trigger. Does _not_ throw error if trigger doesn't exits. diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index 273374b..1212429 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -1216,6 +1216,34 @@ $body$ @generated@ +SELECT __cat_tools.create_function( + 'cat_tools.relation__is_temp' + , 'relation pg_catalog.regclass' + , $$boolean LANGUAGE sql STRICT STABLE$$ + , $body$ +SELECT relnamespace::pg_catalog.regnamespace::text ~ '^pg_temp' +FROM pg_catalog.pg_class +WHERE oid = $1 +$body$ + , 'cat_tools__usage' + , $$Returns true if the relation is a temporary table (lives in a schema that starts with 'pg_temp').$$ +); + +SELECT __cat_tools.create_function( + 'cat_tools.relation__is_catalog' + , 'relation pg_catalog.regclass' + , $$boolean LANGUAGE sql STRICT STABLE$$ + , $body$ +SELECT relnamespace::pg_catalog.regnamespace::text = 'pg_catalog' +FROM pg_catalog.pg_class +WHERE oid = $1 +$body$ + , 'cat_tools__usage' + , 'Returns true if the relation is in the pg_catalog schema.' +); + +@generated@ + SELECT __cat_tools.create_function( 'cat_tools.name__check' , 'name_to_check text' diff --git a/test/expected/relation__is_catalog.out b/test/expected/relation__is_catalog.out new file mode 100644 index 0000000..c7f0772 --- /dev/null +++ b/test/expected/relation__is_catalog.out @@ -0,0 +1,8 @@ +\set ECHO none +1..5 +ok 1 - Verify public has no perms +ok 2 - pg_catalog.pg_class is in pg_catalog schema +ok 3 - Create temp table for testing +ok 4 - temp relation is not in pg_catalog schema +ok 5 - NULL input returns NULL (STRICT function) +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/relation__is_temp.out b/test/expected/relation__is_temp.out new file mode 100644 index 0000000..55b67f7 --- /dev/null +++ b/test/expected/relation__is_temp.out @@ -0,0 +1,8 @@ +\set ECHO none +1..5 +ok 1 - Verify public has no perms +ok 2 - pg_catalog.pg_class is not a temp relation +ok 3 - Create temp table for testing +ok 4 - temp relation is correctly identified as temp +ok 5 - NULL input returns NULL (STRICT function) +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index dbe9615..215ca01 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -98,6 +98,10 @@ + + + + diff --git a/test/sql/relation__is_catalog.sql b/test/sql/relation__is_catalog.sql new file mode 100644 index 0000000..a402a4e --- /dev/null +++ b/test/sql/relation__is_catalog.sql @@ -0,0 +1,47 @@ +\set ECHO none + +\i test/setup.sql + +\set s cat_tools +\set f relation__is_catalog + +SELECT plan(5); + +SET LOCAL ROLE :no_use_role; + +SELECT throws_ok( + format( + $$SELECT %I.%I( %L )$$ + , :'s', :'f' + , 'pg_catalog.pg_class' + ) + , '42501' + , NULL + , 'Verify public has no perms' +); + +SET LOCAL ROLE :use_role; + +SELECT is( + cat_tools.relation__is_catalog('pg_catalog.pg_class'::regclass) + , true + , 'pg_catalog.pg_class is in pg_catalog schema' +); + +SELECT lives_ok($$CREATE TEMP TABLE test_temp_table()$$, 'Create temp table for testing'); + +SELECT is( + cat_tools.relation__is_catalog('test_temp_table'::regclass) + , false + , 'temp relation is not in pg_catalog schema' +); + +SELECT is( + cat_tools.relation__is_catalog(NULL) + , NULL + , 'NULL input returns NULL (STRICT function)' +); + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file diff --git a/test/sql/relation__is_temp.sql b/test/sql/relation__is_temp.sql new file mode 100644 index 0000000..d80c19e --- /dev/null +++ b/test/sql/relation__is_temp.sql @@ -0,0 +1,47 @@ +\set ECHO none + +\i test/setup.sql + +\set s cat_tools +\set f relation__is_temp + +SELECT plan(5); + +SET LOCAL ROLE :no_use_role; + +SELECT throws_ok( + format( + $$SELECT %I.%I( %L )$$ + , :'s', :'f' + , 'pg_catalog.pg_class' + ) + , '42501' + , NULL + , 'Verify public has no perms' +); + +SET LOCAL ROLE :use_role; + +SELECT is( + cat_tools.relation__is_temp('pg_catalog.pg_class'::regclass) + , false + , 'pg_catalog.pg_class is not a temp relation' +); + +SELECT lives_ok($$CREATE TEMP TABLE test_temp_table()$$, 'Create temp table for testing'); + +SELECT is( + cat_tools.relation__is_temp('test_temp_table'::regclass) + , true + , 'temp relation is correctly identified as temp' +); + +SELECT is( + cat_tools.relation__is_temp(NULL) + , NULL + , 'NULL input returns NULL (STRICT function)' +); + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file From 4addd8cd5f90df2cd2e484dbe34c7aeee4afd0a7 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Wed, 3 Sep 2025 16:31:40 -0500 Subject: [PATCH 08/16] Fix relation__column_names test to use temporary table instead of pg_catalog.pg_class --- test/sql/relation__column_names.sql | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/sql/relation__column_names.sql diff --git a/test/sql/relation__column_names.sql b/test/sql/relation__column_names.sql new file mode 100644 index 0000000..4d671c5 --- /dev/null +++ b/test/sql/relation__column_names.sql @@ -0,0 +1,57 @@ +\set ECHO none + +\i test/setup.sql + +\set s cat_tools +\set f relation__column_names + +SELECT plan(8); + +SET LOCAL ROLE :no_use_role; + +SELECT throws_ok( + format( + $$SELECT %I.%I( %L )$$ + , :'s', :'f' + , 'temp_test_table' + ) + , '42501' + , NULL + , 'Verify public has no perms' +); + +SET LOCAL ROLE :use_role; + +SELECT lives_ok($$CREATE TEMP TABLE temp_test_table(col1 int, col2 text, col3 boolean, col4 timestamp, col5 numeric)$$, 'Create temp table with multiple columns'); + +SELECT is( + cat_tools.relation__column_names('temp_test_table'::regclass) + , '{col1,col2,col3,col4,col5}'::text[] + , 'Temp table returns expected column names' +); + +SELECT lives_ok($$ALTER TABLE temp_test_table DROP COLUMN col3$$, 'Drop middle column from temp table'); + +SELECT is( + cat_tools.relation__column_names('temp_test_table'::regclass) + , '{col1,col2,col4,col5}'::text[] + , 'Temp table with dropped column returns expected column names' +); + +SELECT lives_ok($$CREATE TEMP TABLE test_table(id int, name text)$$, 'Create test table with columns'); + +SELECT is( + cat_tools.relation__column_names('test_table'::regclass) + , '{id,name}'::text[] + , 'Test table returns expected column names' +); + +SELECT is( + cat_tools.relation__column_names(NULL) + , NULL + , 'NULL input returns NULL (STRICT function)' +); + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file From b1afca2b77c616a51fd740c3a3bacd9847182709 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Wed, 3 Sep 2025 16:33:10 -0500 Subject: [PATCH 09/16] Fix missing commits --- .gitignore | 1 + README.asc | 1 + sql/cat_tools.sql.in | 15 +++++++++++++++ test/expected/relation__column_names.out | 11 +++++++++++ test/expected/table__is_temp.out | 7 +++++++ test/expected/zzz_build.out | 2 ++ 6 files changed, 37 insertions(+) create mode 100644 test/expected/relation__column_names.out create mode 100644 test/expected/table__is_temp.out diff --git a/.gitignore b/.gitignore index 3b4aafa..adffc88 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ regression.out # Misc tmp/ .DS_Store +.claude/*.local.json diff --git a/README.asc b/README.asc index 31152f9..6791c11 100644 --- a/README.asc +++ b/README.asc @@ -36,6 +36,7 @@ Works on Postgres 9.3 and above. * `cat_tools.regprocedure(function_name, arguments)` - Returns regprocedure for function_name and it's full set of arguments * `cat_tools.relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `cat_tools.relation_type` * `cat_tools.relation__relkind(relation_type)` - Mapping from `cat_tools.relation_type` to a `pg_class.relkind` value +* `cat_tools.relation__column_names(relation regclass)` - Returns an array of quoted column names for a relation in ordinal position order * `cat_tools.relation__is_catalog(relation regclass)` - Returns true if the relation is in the `pg_catalog` schema * `cat_tools.relation__is_temp(relation regclass)` - Returns true if the relation is a temporary table (lives in a schema that starts with 'pg_temp') * `cat_tools.trigger__args_as_text(text)` - Converts the arguments for a trigger function (as returned by `trigger__parse()`) to text (for backwards compatibility). diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index 1212429..6f3d79d 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -1242,6 +1242,21 @@ $body$ , 'Returns true if the relation is in the pg_catalog schema.' ); +SELECT __cat_tools.create_function( + 'cat_tools.relation__column_names' + , 'relation pg_catalog.regclass' + , $$text[] LANGUAGE sql STRICT STABLE$$ + , $body$ +SELECT array_agg(quote_ident(attname) ORDER BY attnum) +FROM pg_catalog.pg_attribute +WHERE attrelid = $1 + AND attnum > 0 + AND NOT attisdropped +$body$ + , 'cat_tools__usage' + , 'Returns an array of quoted column names for a relation in ordinal position order.' +); + @generated@ SELECT __cat_tools.create_function( diff --git a/test/expected/relation__column_names.out b/test/expected/relation__column_names.out new file mode 100644 index 0000000..3914b87 --- /dev/null +++ b/test/expected/relation__column_names.out @@ -0,0 +1,11 @@ +\set ECHO none +1..8 +ok 1 - Verify public has no perms +ok 2 - Create temp table with multiple columns +ok 3 - Temp table returns expected column names +ok 4 - Drop middle column from temp table +ok 5 - Temp table with dropped column returns expected column names +ok 6 - Create test table with columns +ok 7 - Test table returns expected column names +ok 8 - NULL input returns NULL (STRICT function) +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/table__is_temp.out b/test/expected/table__is_temp.out new file mode 100644 index 0000000..c97490e --- /dev/null +++ b/test/expected/table__is_temp.out @@ -0,0 +1,7 @@ +\set ECHO none +1..4 +ok 1 - Verify public has no perms +ok 2 - pg_catalog.pg_class is not a temp table +ok 3 - temp table is correctly identified as temp +ok 4 - NULL input returns NULL (STRICT function) +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index 215ca01..6655fea 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -104,6 +104,8 @@ + + From 3bde4a805e6a0e4a9544c462f09002e406381b8e Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 23 Sep 2025 11:57:15 -0500 Subject: [PATCH 10/16] Add function__arg_names() Also, refactor common code between it and function__arg_types() --- README.asc | 2 + sql/cat_tools.sql.in | 155 ++++++++++++++++++++++++++++++++---- test/expected/zzz_build.out | 7 +- test/setup.sql | 1 + test/sql/function.sql | 83 +++++++++++++++++++ 5 files changed, 228 insertions(+), 20 deletions(-) diff --git a/README.asc b/README.asc index 6791c11..1745299 100644 --- a/README.asc +++ b/README.asc @@ -31,6 +31,8 @@ Works on Postgres 9.3 and above. * `cat_tools.extension__schemas_unique(extension_names text/name[])` - Returns a unique array of schemas * `cat_tools.function__arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments * `cat_tools.function__arg_types_text(arguments)` - Version of `function__arg_types` that returns text +* `cat_tools.function__arg_names(arguments)` - Accepts full function argument string and returns text[] of IN/INOUT argument names +* `cat_tools.function__arg_names_text(arguments)` - Version of `function__arg_names` that returns text * `cat_tools.object__catalog(object_type)` - Returns catalog table that is used to store `object_type` objects * `cat_tools.object__reg_type(object_catalog)` - Returns the "reg" pseudotype (ie: regclass) associated with a system catalog (ie: pg_class) * `cat_tools.regprocedure(function_name, arguments)` - Returns regprocedure for function_name and it's full set of arguments diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index 6f3d79d..8088ab8 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -161,24 +161,40 @@ $body$; @generated@ -SELECT __cat_tools.create_function( - 'cat_tools.function__arg_types' - , $$arguments text$$ - , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ - , $body$ +CREATE FUNCTION _cat_tools.function__arg_to_regprocedure( + arguments text + , function_suffix text + , api_function_name text +) RETURNS pg_catalog.regprocedure LANGUAGE plpgsql AS $body$ DECLARE - input_arg_types pg_catalog.regtype[]; - - c_template CONSTANT text := $fmt$CREATE FUNCTION pg_temp.cat_tools__function__arg_types__temp_function( + /* + * Template for creating a temporary function with the user-provided argument + * signature. This allows us to leverage PostgreSQL's parser to validate and + * extract argument information without permanently creating a function. + * Using plpgsql language for the temp function to handle any return type. + */ + c_template CONSTANT text := $fmt$CREATE FUNCTION pg_temp.cat_tools__function__%s__temp_function( %s - ) RETURNS %s LANGUAGE plpgsql AS 'BEGIN NULL; END' + ) RETURNS %s LANGUAGE plpgsql AS 'BEGIN RETURN; END' $fmt$; temp_proc pg_catalog.regprocedure; sql text; BEGIN + /* + * Security check: Ensure current_user == session_user to detect SECURITY DEFINER context + * This prevents SQL injection attacks through elevated privileges. + */ + IF current_user != session_user THEN + RAISE EXCEPTION USING + ERRCODE = '28000' /* invalid_authorization_specification */ + , MESSAGE = 'potential use of SECURITY DEFINER detected' + , DETAIL = format('current_user is %s, session_user is %s', current_user, session_user) + , HINT = 'Helper functions must not be called from SECURITY DEFINER context.'; + END IF; sql := format( c_template + , function_suffix , arguments , 'void' ); @@ -191,6 +207,7 @@ BEGIN v_type := (regexp_matches( SQLERRM, 'function result type must be ([^ ]+) because of' ))[1]; sql := format( c_template + , function_suffix , arguments , v_type ); @@ -203,18 +220,59 @@ BEGIN * only one function with this name. The cast to regprocedure is for the sake * of the DROP down below. */ - EXECUTE $$SELECT 'pg_temp.cat_tools__function__arg_types__temp_function'::pg_catalog.regproc::pg_catalog.regprocedure$$ INTO temp_proc; + EXECUTE format( + $$SELECT 'pg_temp.cat_tools__function__%s__temp_function'::pg_catalog.regproc::pg_catalog.regprocedure$$ + , function_suffix + ) INTO temp_proc; + + RETURN temp_proc; +END +$body$; + +CREATE FUNCTION _cat_tools.function__drop_temp( + p_regprocedure pg_catalog.regprocedure + , api_function_name text +) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + /* + * Security check: Ensure current_user == session_user to detect SECURITY DEFINER context + * This prevents SQL injection attacks through elevated privileges. + */ + IF current_user != session_user THEN + RAISE EXCEPTION USING + ERRCODE = '28000' /* invalid_authorization_specification */ + , MESSAGE = 'potential use of SECURITY DEFINER detected' + , DETAIL = format('API function %s must not be called from a SECURITY DEFINER function', api_function_name) + , HINT = 'We detect SECURITY DEFINER context by comparing current_user and session_user, which can cause false positives if SET ROLE is used'; + END IF; + + EXECUTE 'DROP ROUTINE ' || p_regprocedure; +END +$body$; + +GRANT USAGE ON SCHEMA _cat_tools TO cat_tools__usage; +GRANT EXECUTE ON FUNCTION _cat_tools.function__arg_to_regprocedure(text, text, text) TO cat_tools__usage; +GRANT EXECUTE ON FUNCTION _cat_tools.function__drop_temp(pg_catalog.regprocedure, text) TO cat_tools__usage; + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.function__arg_types' + , $$arguments text$$ + , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ + , $body$ +DECLARE + input_arg_types pg_catalog.regtype[]; + c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_types', 'cat_tools.function__arg_types'); +BEGIN SELECT INTO STRICT input_arg_types -- This is here to re-cast the array as 1-based instead of 0 based (better solutions welcome!) string_to_array(proargtypes::text,' ')::pg_catalog.regtype[] FROM pg_proc - WHERE oid = temp_proc - ; - -- NOTE: DROP may not accept all the argument options that CREATE does, so use temp_proc - EXECUTE format( - $fmt$DROP FUNCTION %s$fmt$ - , temp_proc - ); + WHERE oid = c_temp_proc::pg_catalog.regproc; + + -- Clean up the temporary function + PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.function__arg_types'); RETURN input_arg_types; END @@ -227,6 +285,53 @@ $body$ @generated@ +SELECT __cat_tools.create_function( + 'cat_tools.function__arg_names' + , $$arguments text$$ + , $$text[] LANGUAGE plpgsql$$ + , $body$ +DECLARE + input_arg_names text[]; + c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_names', 'cat_tools.function__arg_names'); +BEGIN + -- Get argument names, filtering for IN/INOUT/VARIADIC only + SELECT INTO input_arg_names + CASE + WHEN proargmodes IS NULL THEN + -- All arguments are IN mode, return all names (converting empty strings to NULL) + array( + SELECT CASE WHEN proargnames[i] = '' THEN NULL ELSE proargnames[i] END + FROM generate_series(1, array_length(proargnames, 1)) AS s(i) + ) + ELSE + -- Filter names based on modes: 'i' (IN), 'b' (INOUT), 'v' (VARIADIC) + array( + SELECT + CASE + WHEN i <= array_length(proargnames, 1) AND proargnames[i] != '' THEN proargnames[i] + ELSE NULL + END + FROM generate_series(1, array_length(proargmodes, 1)) AS s(i) + WHERE proargmodes[i] IN ('i', 'b', 'v') + ) + END + FROM pg_proc + WHERE oid = c_temp_proc::pg_catalog.regproc; + + -- Clean up the temporary function + PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.function__arg_names'); + + RETURN input_arg_names; +END +$body$ + , 'cat_tools__usage' + , 'Returns argument names for a function argument body as an array. Only + includes IN, INOUT, and VARIADIC arguments (matching function__arg_types + behavior). Unnamed arguments appear as NULL in the result array.' +); + +@generated@ + SELECT __cat_tools.create_function( 'cat_tools.function__arg_types_text' , $$arguments text$$ @@ -243,6 +348,22 @@ $body$ @generated@ +SELECT __cat_tools.create_function( + 'cat_tools.function__arg_names_text' + , $$arguments text$$ + , $$text LANGUAGE sql$$ + , $body$ +SELECT array_to_string(cat_tools.function__arg_names($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns argument names for a function argument body as text. Only + includes IN, INOUT, and VARIADIC arguments (matching function__arg_types_text + behavior). Unnamed arguments appear as empty strings in the result.' + +); + +@generated@ + SELECT __cat_tools.create_function( 'cat_tools.regprocedure' , $$ diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index 6655fea..eb9aaed 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -103,10 +103,11 @@ - - - + + + + # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/setup.sql b/test/setup.sql index f6735ed..104205d 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -33,4 +33,5 @@ SELECT array_to_string(array( ) $body$; + -- vi: expandtab ts=2 sw=2 diff --git a/test/sql/function.sql b/test/sql/function.sql index 198ae72..6828b09 100644 --- a/test/sql/function.sql +++ b/test/sql/function.sql @@ -6,6 +6,7 @@ CREATE TEMP VIEW func_calls AS SELECT * FROM (VALUES ('function__arg_types'::name, $$'x'$$::text) + , ('function__arg_names'::name, $$'x'$$::text) , ('regprocedure'::name, $$'x', 'x'$$) ) v(fname, args) ; @@ -16,8 +17,11 @@ SELECT plan( + (SELECT count(*)::int FROM func_calls) + 4 -- function__arg_types() + + 4 -- function__arg_names() + 2 -- regprocedure() + + 6 -- security definer checks (2 helpers + 4 callers) + + 1 -- current_user != session_user test ); SET LOCAL ROLE :no_use_role; @@ -35,7 +39,24 @@ SELECT throws_ok( FROM func_calls ; +/* + * Test that the security check works when current_user != session_user + * This tests what happens when functions are called from a different role context + */ SET LOCAL ROLE :use_role; +SELECT throws_ok( + $$SELECT cat_tools.function__arg_types('int')$$, + '28000', + 'potential use of SECURITY DEFINER detected', + 'Security check should prevent execution when current_user != session_user' +); + +/* + * The helper functions now have security checks that prevent execution when + * current_user != session_user (which happens with SET LOCAL ROLE). + * Reset to session_user for testing the actual functionality. + */ +SET SESSION AUTHORIZATION :use_role; SELECT is( :s.function__arg_types($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) @@ -61,6 +82,30 @@ SELECT is( , 'Verify function__arg_types() with only inputs' ); +SELECT is( + :s.function__arg_names($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) + , '{in_int,inout_int_array,NULL,NULL}'::text[] + , 'Verify function__arg_names() with INOUT and OUT' +); + +SELECT is( + :s.function__arg_names($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) + , '{in_int,inout_int_array,NULL,NULL,NULL}'::text[] + , 'Verify function__arg_names() with just INOUT' +); + +SELECT is( + :s.function__arg_names($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) + , '{in_int,NULL,NULL,NULL}'::text[] + , 'Verify function__arg_names() with just OUT' +); + +SELECT is( + :s.function__arg_names($$anyelement, "char", pg_class, VARIADIC boolean[]$$) + , '{NULL,NULL,NULL,NULL}'::text[] + , 'Verify function__arg_names() with only inputs' +); + \set args 'anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]' SELECT lives_ok( format( @@ -76,6 +121,44 @@ SELECT is( , 'Verify regprocedure()' ); +/* + * CRITICAL SECURITY TESTS: Helper functions must NOT be SECURITY DEFINER + * If they were SECURITY DEFINER, they could be exploited for SQL injection attacks + * since they execute dynamic SQL with elevated privileges. + */ + +-- Test helper functions in _cat_tools schema +\set f function__arg_to_regprocedure +\set args_text 'text, text, text' +SELECT string_to_array(:'args_text', ', ') AS args \gset +SELECT isnt_definer('_cat_tools', :'f', :'args'::name[]); + +\set f function__drop_temp +\set args_text 'regprocedure, text' +SELECT string_to_array(:'args_text', ', ') AS args \gset +SELECT isnt_definer('_cat_tools', :'f', :'args'::name[]); + +-- Test public functions in cat_tools schema +\set f function__arg_types +\set args_text 'text' +SELECT string_to_array(:'args_text', ', ') AS args \gset +SELECT isnt_definer(:'s', :'f', :'args'::name[]); + +\set f function__arg_names +\set args_text 'text' +SELECT string_to_array(:'args_text', ', ') AS args \gset +SELECT isnt_definer(:'s', :'f', :'args'::name[]); + +\set f function__arg_types_text +\set args_text 'text' +SELECT string_to_array(:'args_text', ', ') AS args \gset +SELECT isnt_definer(:'s', :'f', :'args'::name[]); + +\set f function__arg_names_text +\set args_text 'text' +SELECT string_to_array(:'args_text', ', ') AS args \gset +SELECT isnt_definer(:'s', :'f', :'args'::name[]); + \i test/pgxntool/finish.sql -- vi: expandtab ts=2 sw=2 From c75eb40547be168e6852bdd88bd5610e13891fb1 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 23 Sep 2025 13:22:02 -0500 Subject: [PATCH 11/16] Rename function__arg_* to routine__parse_arg_* --- README.asc | 13 ++++--- sql/cat_tools.sql.in | 69 +++++++++++++++++++++++++-------- test/expected/function.out | 38 +++++++++++++++---- test/expected/zzz_build.out | 7 ++++ test/sql/function.sql | 76 +++++++++++++++++++++++++------------ 5 files changed, 151 insertions(+), 52 deletions(-) diff --git a/README.asc b/README.asc index 1745299..afa7a00 100644 --- a/README.asc +++ b/README.asc @@ -29,13 +29,16 @@ Works on Postgres 9.3 and above. * `cat_tools.pg_extension__get(extension_name name)` - Returns cat_tools.pg_extension_v row for an extension * `cat_tools.extension__schemas(extension_names text/name[])` - Returns the schemas for the requested functions * `cat_tools.extension__schemas_unique(extension_names text/name[])` - Returns a unique array of schemas -* `cat_tools.function__arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments -* `cat_tools.function__arg_types_text(arguments)` - Version of `function__arg_types` that returns text -* `cat_tools.function__arg_names(arguments)` - Accepts full function argument string and returns text[] of IN/INOUT argument names -* `cat_tools.function__arg_names_text(arguments)` - Version of `function__arg_names` that returns text +* `cat_tools.routine__parse_arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments +* `cat_tools.routine__parse_arg_types_text(arguments)` - Version of `routine__parse_arg_types` that returns text +* `cat_tools.routine__parse_arg_names(arguments)` - Accepts full function argument string and returns text[] of IN/INOUT argument names +* `cat_tools.routine__parse_arg_names_text(arguments)` - Version of `routine__parse_arg_names` that returns text +* `cat_tools.function__arg_types(arguments)` - DEPRECATED: Use `routine__parse_arg_types` instead +* `cat_tools.function__arg_types_text(arguments)` - DEPRECATED: Use `routine__parse_arg_types_text` instead * `cat_tools.object__catalog(object_type)` - Returns catalog table that is used to store `object_type` objects * `cat_tools.object__reg_type(object_catalog)` - Returns the "reg" pseudotype (ie: regclass) associated with a system catalog (ie: pg_class) -* `cat_tools.regprocedure(function_name, arguments)` - Returns regprocedure for function_name and it's full set of arguments +* `cat_tools.regprocedure(routine_name, arguments)` - Returns regprocedure for routine_name and it's full set of arguments +* `cat_tools.regprocedure(routine_name)` - Returns regprocedure for routine_name. Throws an error if the routine is overloaded * `cat_tools.relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `cat_tools.relation_type` * `cat_tools.relation__relkind(relation_type)` - Mapping from `cat_tools.relation_type` to a `pg_class.relkind` value * `cat_tools.relation__column_names(relation regclass)` - Returns an array of quoted column names for a relation in ordinal position order diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index 8088ab8..6c4e7ca 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -76,7 +76,7 @@ REVOKE ALL ON _cat_tools.pg_class_v FROM public; * function that we're about to create to create the real version of this * function. */ -CREATE FUNCTION cat_tools.function__arg_types_text(text +CREATE FUNCTION cat_tools.routine__parse_arg_types_text(text ) RETURNS text LANGUAGE sql AS 'SELECT $1'; CREATE FUNCTION __cat_tools.create_function( @@ -88,7 +88,7 @@ CREATE FUNCTION __cat_tools.create_function( , comment text DEFAULT NULL ) RETURNS void LANGUAGE plpgsql AS $body$ DECLARE - c_simple_args CONSTANT text := cat_tools.function__arg_types_text(args); + c_simple_args CONSTANT text := cat_tools.routine__parse_arg_types_text(args); create_template CONSTANT text := $template$ CREATE OR REPLACE FUNCTION %s( @@ -257,13 +257,13 @@ GRANT EXECUTE ON FUNCTION _cat_tools.function__drop_temp(pg_catalog.regprocedure @generated@ SELECT __cat_tools.create_function( - 'cat_tools.function__arg_types' + 'cat_tools.routine__parse_arg_types' , $$arguments text$$ , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ , $body$ DECLARE input_arg_types pg_catalog.regtype[]; - c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_types', 'cat_tools.function__arg_types'); + c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_types', 'cat_tools.routine__parse_arg_types'); BEGIN SELECT INTO STRICT input_arg_types -- This is here to re-cast the array as 1-based instead of 0 based (better solutions welcome!) @@ -272,7 +272,7 @@ BEGIN WHERE oid = c_temp_proc::pg_catalog.regproc; -- Clean up the temporary function - PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.function__arg_types'); + PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.routine__parse_arg_types'); RETURN input_arg_types; END @@ -286,13 +286,13 @@ $body$ @generated@ SELECT __cat_tools.create_function( - 'cat_tools.function__arg_names' + 'cat_tools.routine__parse_arg_names' , $$arguments text$$ , $$text[] LANGUAGE plpgsql$$ , $body$ DECLARE input_arg_names text[]; - c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_names', 'cat_tools.function__arg_names'); + c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_names', 'cat_tools.routine__parse_arg_names'); BEGIN -- Get argument names, filtering for IN/INOUT/VARIADIC only SELECT INTO input_arg_names @@ -319,25 +319,25 @@ BEGIN WHERE oid = c_temp_proc::pg_catalog.regproc; -- Clean up the temporary function - PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.function__arg_names'); + PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.routine__parse_arg_names'); RETURN input_arg_names; END $body$ , 'cat_tools__usage' , 'Returns argument names for a function argument body as an array. Only - includes IN, INOUT, and VARIADIC arguments (matching function__arg_types + includes IN, INOUT, and VARIADIC arguments (matching routine__parse_arg_types behavior). Unnamed arguments appear as NULL in the result array.' ); @generated@ SELECT __cat_tools.create_function( - 'cat_tools.function__arg_types_text' + 'cat_tools.routine__parse_arg_types_text' , $$arguments text$$ , $$text LANGUAGE sql$$ , $body$ -SELECT array_to_string(cat_tools.function__arg_types($1), ', ') +SELECT array_to_string(cat_tools.routine__parse_arg_types($1), ', ') $body$ , 'cat_tools__usage' , 'Returns argument types for a function argument body as text. Unlike a @@ -349,21 +349,60 @@ $body$ @generated@ SELECT __cat_tools.create_function( - 'cat_tools.function__arg_names_text' + 'cat_tools.routine__parse_arg_names_text' , $$arguments text$$ , $$text LANGUAGE sql$$ , $body$ -SELECT array_to_string(cat_tools.function__arg_names($1), ', ') +SELECT array_to_string(cat_tools.routine__parse_arg_names($1), ', ') $body$ , 'cat_tools__usage' , 'Returns argument names for a function argument body as text. Only - includes IN, INOUT, and VARIADIC arguments (matching function__arg_types_text + includes IN, INOUT, and VARIADIC arguments (matching routine__parse_arg_types_text behavior). Unnamed arguments appear as empty strings in the result.' ); @generated@ +-- Deprecated wrapper functions for backwards compatibility +SELECT __cat_tools.create_function( + 'cat_tools.function__arg_types' + , $$arguments text$$ + , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ + , $body$ +BEGIN + RAISE WARNING 'function__arg_types() is deprecated, use routine__parse_arg_types instead'; + + RETURN cat_tools.routine__parse_arg_types(arguments); +END +$body$ + , 'cat_tools__usage' + , 'DEPRECATED: Use routine__parse_arg_types instead. + Returns argument types for a function argument body as regtype[]. Only + includes IN, INOUT, and VARIADIC arguments.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.function__arg_types_text' + , $$arguments text$$ + , $$text LANGUAGE plpgsql$$ + , $body$ +BEGIN + RAISE WARNING 'function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead'; + + RETURN cat_tools.routine__parse_arg_types_text(arguments); +END +$body$ + , 'cat_tools__usage' + , 'DEPRECATED: Use routine__parse_arg_types_text instead. + Returns argument types for a function argument body as text. Only + includes IN, INOUT, and VARIADIC arguments.' +); + +@generated@ + SELECT __cat_tools.create_function( 'cat_tools.regprocedure' , $$ @@ -374,7 +413,7 @@ SELECT __cat_tools.create_function( SELECT format( '%s(%s)' , $1 - , cat_tools.function__arg_types_text($2) + , cat_tools.routine__parse_arg_types_text($2) )::pg_catalog.regprocedure $body$ , 'cat_tools__usage' diff --git a/test/expected/function.out b/test/expected/function.out index 29dade1..afa4f3a 100644 --- a/test/expected/function.out +++ b/test/expected/function.out @@ -1,11 +1,35 @@ \set ECHO none -1..8 +1..24 ok 1 - Verify public has no perms ok 2 - Verify public has no perms -ok 3 - Verify function__arg_types() with INOUT and OUT -ok 4 - Verify function__arg_types() with just INOUT -ok 5 - Verify function__arg_types() with just OUT -ok 6 - Verify function__arg_types() with only inputs -ok 7 - Create pg_temp.test_function(anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]) -ok 8 - Verify regprocedure() +ok 3 - Verify public has no perms +ok 4 - Security check should prevent execution when current_user != session_user +ok 5 - Verify routine__parse_arg_types() with INOUT and OUT +ok 6 - Verify routine__parse_arg_types() with just INOUT +ok 7 - Verify routine__parse_arg_types() with just OUT +ok 8 - Verify routine__parse_arg_types() with only inputs +ok 9 - Verify routine__parse_arg_names() with INOUT and OUT +ok 10 - Verify routine__parse_arg_names() with just INOUT +ok 11 - Verify routine__parse_arg_names() with just OUT +ok 12 - Verify routine__parse_arg_names() with only inputs +ok 13 - Create pg_temp.test_function(anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]) +ok 14 - Verify regprocedure() +WARNING: 01000: function__arg_types() is deprecated, use routine__parse_arg_types instead +LOCATION: exec_stmt_raise, pl_exec.c:3879 +ok 15 - Verify function__arg_types() with INOUT and OUT +WARNING: 01000: function__arg_types() is deprecated, use routine__parse_arg_types instead +LOCATION: exec_stmt_raise, pl_exec.c:3879 +ok 16 - Verify function__arg_types() with simple args +WARNING: 01000: function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead +LOCATION: exec_stmt_raise, pl_exec.c:3879 +ok 17 - Verify function__arg_types_text() with INOUT and OUT +WARNING: 01000: function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead +LOCATION: exec_stmt_raise, pl_exec.c:3879 +ok 18 - Verify function__arg_types_text() with simple args +ok 19 - Function _cat_tools.function__arg_to_regprocedure(text, text, text) should not be security definer +ok 20 - Function _cat_tools.function__drop_temp(regprocedure, text) should not be security definer +ok 21 - Function cat_tools.routine__parse_arg_types(text) should not be security definer +ok 22 - Function cat_tools.routine__parse_arg_names(text) should not be security definer +ok 23 - Function cat_tools.routine__parse_arg_types_text(text) should not be security definer +ok 24 - Function cat_tools.routine__parse_arg_names_text(text) should not be security definer # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index eb9aaed..7cbc064 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -103,11 +103,18 @@ + + + + + + + # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/sql/function.sql b/test/sql/function.sql index 6828b09..3627cfa 100644 --- a/test/sql/function.sql +++ b/test/sql/function.sql @@ -5,8 +5,8 @@ \set s cat_tools CREATE TEMP VIEW func_calls AS SELECT * FROM (VALUES - ('function__arg_types'::name, $$'x'$$::text) - , ('function__arg_names'::name, $$'x'$$::text) + ('routine__parse_arg_types'::name, $$'x'$$::text) + , ('routine__parse_arg_names'::name, $$'x'$$::text) , ('regprocedure'::name, $$'x', 'x'$$) ) v(fname, args) ; @@ -16,10 +16,11 @@ SELECT plan( 0 + (SELECT count(*)::int FROM func_calls) - + 4 -- function__arg_types() - + 4 -- function__arg_names() + + 4 -- routine__parse_arg_types() + + 4 -- routine__parse_arg_names() + 2 -- regprocedure() + + 4 -- deprecated function__arg_types() wrapper (2 more tests) + 6 -- security definer checks (2 helpers + 4 callers) + 1 -- current_user != session_user test ); @@ -45,7 +46,7 @@ SELECT throws_ok( */ SET LOCAL ROLE :use_role; SELECT throws_ok( - $$SELECT cat_tools.function__arg_types('int')$$, + $$SELECT cat_tools.routine__parse_arg_types('int')$$, '28000', 'potential use of SECURITY DEFINER detected', 'Security check should prevent execution when current_user != session_user' @@ -59,51 +60,51 @@ SELECT throws_ok( SET SESSION AUTHORIZATION :use_role; SELECT is( - :s.function__arg_types($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) + :s.routine__parse_arg_types($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) , '{int,int[],anyelement,boolean}'::regtype[] - , 'Verify function__arg_types() with INOUT and OUT' + , 'Verify routine__parse_arg_types() with INOUT and OUT' ); SELECT is( - :s.function__arg_types($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) + :s.routine__parse_arg_types($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) , '{int,int[],anyarray,anyelement,boolean}'::regtype[] - , 'Verify function__arg_types() with just INOUT' + , 'Verify routine__parse_arg_types() with just INOUT' ); SELECT is( - :s.function__arg_types($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) + :s.routine__parse_arg_types($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) , '{int,anyarray,anyelement,boolean}'::regtype[] - , 'Verify function__arg_types() with just OUT' + , 'Verify routine__parse_arg_types() with just OUT' ); SELECT is( - :s.function__arg_types($$anyelement, "char", pg_class, VARIADIC boolean[]$$) + :s.routine__parse_arg_types($$anyelement, "char", pg_class, VARIADIC boolean[]$$) , '{anyelement,"\"char\"",pg_class,boolean[]}'::regtype[] - , 'Verify function__arg_types() with only inputs' + , 'Verify routine__parse_arg_types() with only inputs' ); SELECT is( - :s.function__arg_names($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) + :s.routine__parse_arg_names($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) , '{in_int,inout_int_array,NULL,NULL}'::text[] - , 'Verify function__arg_names() with INOUT and OUT' + , 'Verify routine__parse_arg_names() with INOUT and OUT' ); SELECT is( - :s.function__arg_names($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) + :s.routine__parse_arg_names($$IN in_int int, INOUT inout_int_array int[], anyarray, anyelement, boolean DEFAULT false$$) , '{in_int,inout_int_array,NULL,NULL,NULL}'::text[] - , 'Verify function__arg_names() with just INOUT' + , 'Verify routine__parse_arg_names() with just INOUT' ); SELECT is( - :s.function__arg_names($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) + :s.routine__parse_arg_names($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) , '{in_int,NULL,NULL,NULL}'::text[] - , 'Verify function__arg_names() with just OUT' + , 'Verify routine__parse_arg_names() with just OUT' ); SELECT is( - :s.function__arg_names($$anyelement, "char", pg_class, VARIADIC boolean[]$$) + :s.routine__parse_arg_names($$anyelement, "char", pg_class, VARIADIC boolean[]$$) , '{NULL,NULL,NULL,NULL}'::text[] - , 'Verify function__arg_names() with only inputs' + , 'Verify routine__parse_arg_names() with only inputs' ); \set args 'anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]' @@ -121,6 +122,31 @@ SELECT is( , 'Verify regprocedure()' ); +-- Test deprecated wrapper functions still work +SELECT is( + :s.function__arg_types($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) + , '{int,int[],anyelement,boolean}'::regtype[] + , 'Verify function__arg_types() with INOUT and OUT' +); + +SELECT is( + :s.function__arg_types($$int, text$$) + , '{int,text}'::regtype[] + , 'Verify function__arg_types() with simple args' +); + +SELECT is( + :s.function__arg_types_text($$IN in_int int, INOUT inout_int_array int[], OUT out_char "char", anyelement, boolean DEFAULT false$$) + , 'integer, integer[], anyelement, boolean' + , 'Verify function__arg_types_text() with INOUT and OUT' +); + +SELECT is( + :s.function__arg_types_text($$int, text$$) + , 'integer, text' + , 'Verify function__arg_types_text() with simple args' +); + /* * CRITICAL SECURITY TESTS: Helper functions must NOT be SECURITY DEFINER * If they were SECURITY DEFINER, they could be exploited for SQL injection attacks @@ -139,22 +165,22 @@ SELECT string_to_array(:'args_text', ', ') AS args \gset SELECT isnt_definer('_cat_tools', :'f', :'args'::name[]); -- Test public functions in cat_tools schema -\set f function__arg_types +\set f routine__parse_arg_types \set args_text 'text' SELECT string_to_array(:'args_text', ', ') AS args \gset SELECT isnt_definer(:'s', :'f', :'args'::name[]); -\set f function__arg_names +\set f routine__parse_arg_names \set args_text 'text' SELECT string_to_array(:'args_text', ', ') AS args \gset SELECT isnt_definer(:'s', :'f', :'args'::name[]); -\set f function__arg_types_text +\set f routine__parse_arg_types_text \set args_text 'text' SELECT string_to_array(:'args_text', ', ') AS args \gset SELECT isnt_definer(:'s', :'f', :'args'::name[]); -\set f function__arg_names_text +\set f routine__parse_arg_names_text \set args_text 'text' SELECT string_to_array(:'args_text', ', ') AS args \gset SELECT isnt_definer(:'s', :'f', :'args'::name[]); From 262434fd43148818962dbf4eb9460107c9d56ffd Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 23 Sep 2025 14:56:31 -0500 Subject: [PATCH 12/16] Remove errant function from README --- README.asc | 1 - 1 file changed, 1 deletion(-) diff --git a/README.asc b/README.asc index afa7a00..2ecb5ee 100644 --- a/README.asc +++ b/README.asc @@ -38,7 +38,6 @@ Works on Postgres 9.3 and above. * `cat_tools.object__catalog(object_type)` - Returns catalog table that is used to store `object_type` objects * `cat_tools.object__reg_type(object_catalog)` - Returns the "reg" pseudotype (ie: regclass) associated with a system catalog (ie: pg_class) * `cat_tools.regprocedure(routine_name, arguments)` - Returns regprocedure for routine_name and it's full set of arguments -* `cat_tools.regprocedure(routine_name)` - Returns regprocedure for routine_name. Throws an error if the routine is overloaded * `cat_tools.relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `cat_tools.relation_type` * `cat_tools.relation__relkind(relation_type)` - Mapping from `cat_tools.relation_type` to a `pg_class.relkind` value * `cat_tools.relation__column_names(relation regclass)` - Returns an array of quoted column names for a relation in ordinal position order From 6ad3efb6ed9e6d94df161b7eae18c149c140164e Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Thu, 25 Sep 2025 15:37:33 -0500 Subject: [PATCH 13/16] Refactor routine__arg_names() and reorganize file structure - Eliminate duplicate NULL array creation logic by restructuring CASE statement - Use pronargs instead of parsing proargtypes text for array size - Move data type definitions and mapping functions to beginning of file - Improve code organization and readability --- README.asc | 45 +++- sql/cat_tools.sql.in | 429 ++++++++++++++++++++++---------- test/expected/function.out | 40 ++- test/expected/relation_type.out | 20 +- test/expected/zzz_build.out | 16 ++ test/sql/function.sql | 109 ++++++++ test/sql/relation_type.sql | 14 +- 7 files changed, 502 insertions(+), 171 deletions(-) diff --git a/README.asc b/README.asc index 2ecb5ee..2238bd2 100644 --- a/README.asc +++ b/README.asc @@ -18,8 +18,13 @@ Works on Postgres 9.3 and above. * `cat_tools.constraint_type` - Types of constraints (`domain constraint` or `table_constraint`) * `cat_tools.relation_type` - Types of objects stored in `pg_class` * `cat_tools.relation_relkind` - Valid values for `pg_class.relkind` +* `cat_tools.routine_type` - Types of routines stored in `pg_proc` +* `cat_tools.routine_argument_mode` - Argument modes for function/procedure parameters +* `cat_tools.routine_volatility` - Volatility levels for functions/procedures` +* `cat_tools.routine_parallel_safety` - Parallel safety levels for functions/procedures +* `cat_tools.routine_argument` - Detailed information about a single function/procedure argument -== Functions +== General Introspection Functions * `cat_tools.currval(table, column)` - Returns current value for a sequence owned by a column * `cat_tools.enum_range(regtype)` - Returns valid values for an ENUM as an array @@ -29,26 +34,46 @@ Works on Postgres 9.3 and above. * `cat_tools.pg_extension__get(extension_name name)` - Returns cat_tools.pg_extension_v row for an extension * `cat_tools.extension__schemas(extension_names text/name[])` - Returns the schemas for the requested functions * `cat_tools.extension__schemas_unique(extension_names text/name[])` - Returns a unique array of schemas -* `cat_tools.routine__parse_arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments -* `cat_tools.routine__parse_arg_types_text(arguments)` - Version of `routine__parse_arg_types` that returns text -* `cat_tools.routine__parse_arg_names(arguments)` - Accepts full function argument string and returns text[] of IN/INOUT argument names -* `cat_tools.routine__parse_arg_names_text(arguments)` - Version of `routine__parse_arg_names` that returns text -* `cat_tools.function__arg_types(arguments)` - DEPRECATED: Use `routine__parse_arg_types` instead -* `cat_tools.function__arg_types_text(arguments)` - DEPRECATED: Use `routine__parse_arg_types_text` instead * `cat_tools.object__catalog(object_type)` - Returns catalog table that is used to store `object_type` objects * `cat_tools.object__reg_type(object_catalog)` - Returns the "reg" pseudotype (ie: regclass) associated with a system catalog (ie: pg_class) -* `cat_tools.regprocedure(routine_name, arguments)` - Returns regprocedure for routine_name and it's full set of arguments -* `cat_tools.relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `cat_tools.relation_type` -* `cat_tools.relation__relkind(relation_type)` - Mapping from `cat_tools.relation_type` to a `pg_class.relkind` value * `cat_tools.relation__column_names(relation regclass)` - Returns an array of quoted column names for a relation in ordinal position order * `cat_tools.relation__is_catalog(relation regclass)` - Returns true if the relation is in the `pg_catalog` schema * `cat_tools.relation__is_temp(relation regclass)` - Returns true if the relation is a temporary table (lives in a schema that starts with 'pg_temp') + +== Routine / Function / Procedure Functions + +* `cat_tools.routine__parse_arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments +* `cat_tools.routine__parse_arg_types_text(arguments)` - Version of `routine__parse_arg_types` that returns text +* `cat_tools.routine__parse_arg_names(arguments)` - Accepts full function argument string and returns text[] of IN/INOUT argument names +* `cat_tools.routine__parse_arg_names_text(arguments)` - Version of `routine__parse_arg_names` that returns text +* `cat_tools.routine__arg_types(regprocedure)` - Returns argument types for a function as regtype[] +* `cat_tools.routine__arg_types_text(regprocedure)` - Version of `routine__arg_types` that returns text +* `cat_tools.routine__arg_names(regprocedure)` - Returns argument names for a function as text[] +* `cat_tools.routine__arg_names_text(regprocedure)` - Version of `routine__arg_names` that returns text +* `cat_tools.regprocedure(routine_name, arguments)` - Returns regprocedure for routine_name and it's full set of arguments + +== Trigger Functions + * `cat_tools.trigger__args_as_text(text)` - Converts the arguments for a trigger function (as returned by `trigger__parse()`) to text (for backwards compatibility). * `cat_tools.trigger__get_oid(trigger_table, trigger_name)` - oid of a trigger. Throws error if trigger doesn't exits. * `cat_tools.trigger__get_oid__loose(trigger_table, trigger_name)` - oid of a trigger. Does _not_ throw error if trigger doesn't exits. * `cat_tools.trigger__parse(trigger oid)` - Returns information about a trigger * `cat_tools.trigger__parse(table_name regclass, trigger_name text)` - Returns information about a trigger +== Mapping Functions + +* `cat_tools.relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `cat_tools.relation_type` +* `cat_tools.relation__relkind(relation_type)` - Mapping from `cat_tools.relation_type` to a `pg_class.relkind` value +* `cat_tools.routine__type(prokind)` - Mapping from `pg_proc.prokind` to `cat_tools.routine_type` +* `cat_tools.routine__argument_mode(mode)` - Mapping from `pg_proc.proargmodes` element to `cat_tools.routine_argument_mode` +* `cat_tools.routine__volatility(volatile)` - Mapping from `pg_proc.provolatile` to `cat_tools.routine_volatility` +* `cat_tools.routine__parallel_safety(parallel)` - Mapping from `pg_proc.proparallel` to `cat_tools.routine_parallel_safety` + +== Deprecated Functions + +* `cat_tools.function__arg_types(arguments)` - DEPRECATED: Use `routine__parse_arg_types` instead +* `cat_tools.function__arg_types_text(arguments)` - DEPRECATED: Use `routine__parse_arg_types_text` instead + == Views WARNING: These views may eventually move into a separate extension! diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index 6c4e7ca..afab1f7 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -256,25 +256,310 @@ GRANT EXECUTE ON FUNCTION _cat_tools.function__drop_temp(pg_catalog.regprocedure @generated@ +-- Data type definitions +CREATE TYPE cat_tools.constraint_type AS ENUM( + 'domain constraint', 'table constraint' +); +COMMENT ON TYPE cat_tools.constraint_type IS $$Descriptive names for every type of Postgres object (table, operator, rule, etc)$$; + +CREATE TYPE cat_tools.procedure_type AS ENUM( + 'aggregate', 'function' +); +COMMENT ON TYPE cat_tools.procedure_type IS $$Types of constraints (`domain constraint` or `table_constraint`)$$; + +CREATE TYPE cat_tools.relation_type AS ENUM( + 'table' + , 'index' + , 'sequence' + , 'toast table' + , 'view' + , 'materialized view' + , 'composite type' + , 'foreign table' + , 'partitioned table' + , 'partitioned index' +); +COMMENT ON TYPE cat_tools.relation_type IS $$Types of objects stored in `pg_class`$$; + +CREATE TYPE cat_tools.relation_relkind AS ENUM( + 'r' + , 'i' + , 'S' + , 't' + , 'v' + , 'c' + , 'f' + , 'm' + , 'p' + , 'I' +); +COMMENT ON TYPE cat_tools.relation_relkind IS $$Valid values for `pg_class.relkind`$$; + +CREATE TYPE cat_tools.routine_type AS ENUM( + 'function' + , 'procedure' + , 'aggregate' + , 'window' +); +COMMENT ON TYPE cat_tools.routine_type IS $$Types of routines stored in `pg_proc`$$; + +CREATE TYPE cat_tools.routine_argument_mode AS ENUM( + 'in' + , 'out' + , 'inout' + , 'variadic' + , 'table' +); +COMMENT ON TYPE cat_tools.routine_argument_mode IS $$Argument modes for function/procedure parameters$$; + +CREATE TYPE cat_tools.routine_volatility AS ENUM( + 'immutable' + , 'stable' + , 'volatile' +); +COMMENT ON TYPE cat_tools.routine_volatility IS $$Volatility levels for functions/procedures$$; + +CREATE TYPE cat_tools.routine_parallel_safety AS ENUM( + 'unsafe' + , 'restricted' + , 'safe' +); +COMMENT ON TYPE cat_tools.routine_parallel_safety IS $$Parallel safety levels for functions/procedures$$; + +CREATE TYPE cat_tools.routine_argument AS ( + argument_name text + , argument_type pg_catalog.regtype + , argument_mode cat_tools.routine_argument_mode + , argument_default text +); +COMMENT ON TYPE cat_tools.routine_argument IS $$Detailed information about a single function/procedure argument$$; + +-- Mapping functions +SELECT __cat_tools.create_function( + 'cat_tools.relation__kind' + , 'relkind cat_tools.relation_relkind' + , 'cat_tools.relation_type LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT CASE relkind + WHEN 'r' THEN 'table' + WHEN 'i' THEN 'index' + WHEN 'S' THEN 'sequence' + WHEN 't' THEN 'toast table' + WHEN 'v' THEN 'view' + WHEN 'c' THEN 'materialized view' + WHEN 'f' THEN 'composite type' + WHEN 'm' THEN 'foreign table' + WHEN 'p' THEN 'partitioned table' + WHEN 'I' THEN 'partitioned index' +END::cat_tools.relation_type +$body$ + , 'cat_tools__usage' + , 'Mapping from to a ' +); + +SELECT __cat_tools.create_function( + 'cat_tools.relation__relkind' + , 'kind cat_tools.relation_type' + , 'cat_tools.relation_relkind LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT CASE kind + WHEN 'table' THEN 'r' + WHEN 'index' THEN 'i' + WHEN 'sequence' THEN 'S' + WHEN 'toast table' THEN 't' + WHEN 'view' THEN 'v' + WHEN 'materialized view' THEN 'c' + WHEN 'composite type' THEN 'f' + WHEN 'foreign table' THEN 'm' + WHEN 'partitioned table' THEN 'p' + WHEN 'partitioned index' THEN 'I' +END::cat_tools.relation_relkind +$body$ + , 'cat_tools__usage' + , 'Mapping from to a value' +); + +SELECT __cat_tools.create_function( + 'cat_tools.relation__relkind' + , 'kind text' + , 'cat_tools.relation_relkind LANGUAGE sql STRICT IMMUTABLE' + , $body$SELECT cat_tools.relation__relkind(kind::cat_tools.relation_type)$body$ + , 'cat_tools__usage' + , 'Mapping from to a value' +); + +SELECT __cat_tools.create_function( + 'cat_tools.relation__kind' + , 'relkind text' + , 'cat_tools.relation_type LANGUAGE sql STRICT IMMUTABLE' + , $body$SELECT cat_tools.relation__kind(relkind::cat_tools.relation_relkind)$body$ + , 'cat_tools__usage' + , 'Mapping from to a value' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__type' + , 'prokind "char"' + , 'cat_tools.routine_type LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT CASE prokind + WHEN 'f' THEN 'function' + WHEN 'p' THEN 'procedure' + WHEN 'a' THEN 'aggregate' + WHEN 'w' THEN 'window' +END::cat_tools.routine_type +$body$ + , 'cat_tools__usage' + , 'Mapping from pg_proc.prokind to cat_tools.routine_type' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__argument_mode' + , 'proargmode "char"' + , 'cat_tools.routine_argument_mode LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT CASE proargmode + WHEN 'i' THEN 'in' + WHEN 'o' THEN 'out' + WHEN 'b' THEN 'inout' + WHEN 'v' THEN 'variadic' + WHEN 't' THEN 'table' +END::cat_tools.routine_argument_mode +$body$ + , 'cat_tools__usage' + , 'Mapping from pg_proc.proargmodes element to cat_tools.routine_argument_mode' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__volatility' + , 'provolatile "char"' + , 'cat_tools.routine_volatility LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT CASE provolatile + WHEN 'i' THEN 'immutable' + WHEN 's' THEN 'stable' + WHEN 'v' THEN 'volatile' +END::cat_tools.routine_volatility +$body$ + , 'cat_tools__usage' + , 'Mapping from pg_proc.provolatile to cat_tools.routine_volatility' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__parallel_safety' + , 'proparallel "char"' + , 'cat_tools.routine_parallel_safety LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT CASE proparallel + WHEN 'u' THEN 'unsafe' + WHEN 'r' THEN 'restricted' + WHEN 's' THEN 'safe' +END::cat_tools.routine_parallel_safety +$body$ + , 'cat_tools__usage' + , 'Mapping from pg_proc.proparallel to cat_tools.routine_parallel_safety' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_types' + , $$func pg_catalog.regprocedure$$ + , $$pg_catalog.regtype[] LANGUAGE sql STABLE$$ + , $body$ +SELECT string_to_array(proargtypes::text,' ')::pg_catalog.regtype[] +FROM pg_proc +WHERE oid = $1::pg_catalog.regproc +$body$ + , 'cat_tools__usage' + , 'Returns all argument types for a function as an array of regtype' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_names' + , $$func pg_catalog.regprocedure$$ + , $$text[] LANGUAGE sql STABLE$$ + , $body$ +SELECT + CASE + WHEN proargnames IS NULL THEN + -- No named arguments, return array of NULLs matching proargtypes length + CASE + WHEN pronargs > 0 THEN + array_fill(NULL::text, ARRAY[pronargs]) + ELSE + '{}'::text[] + END + WHEN proargmodes IS NULL THEN + -- All arguments are IN mode, proargnames and proargtypes align + array( + SELECT CASE WHEN name = '' THEN NULL ELSE name END + FROM unnest(proargnames) AS name + ) + ELSE + -- Mixed argument modes, need to filter names to match proargtypes + array( + SELECT + CASE + WHEN i <= array_length(proargnames, 1) AND proargnames[i] != '' THEN proargnames[i] + ELSE NULL + END + FROM unnest(proargmodes) WITH ORDINALITY AS t(mode, i) + WHERE mode IN ('i', 'b', 'v') + ) + END +FROM pg_proc +WHERE oid = $1::pg_catalog.regproc +$body$ + , 'cat_tools__usage' + , 'Returns all argument names for a function as an array of text. Empty strings are converted to NULL.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_types_text' + , $$func pg_catalog.regprocedure$$ + , $$text LANGUAGE sql STABLE$$ + , $body$ +SELECT array_to_string(cat_tools.routine__arg_types($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns all argument types for a function as a comma-separated text string' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_names_text' + , $$func pg_catalog.regprocedure$$ + , $$text LANGUAGE sql STABLE$$ + , $body$ +SELECT array_to_string(cat_tools.routine__arg_names($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns all argument names for a function as a comma-separated text string' +); + +@generated@ + SELECT __cat_tools.create_function( 'cat_tools.routine__parse_arg_types' , $$arguments text$$ , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ , $body$ DECLARE - input_arg_types pg_catalog.regtype[]; c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_types', 'cat_tools.routine__parse_arg_types'); + result pg_catalog.regtype[]; BEGIN - SELECT INTO STRICT input_arg_types - -- This is here to re-cast the array as 1-based instead of 0 based (better solutions welcome!) - string_to_array(proargtypes::text,' ')::pg_catalog.regtype[] - FROM pg_proc - WHERE oid = c_temp_proc::pg_catalog.regproc; + result := cat_tools.routine__arg_types(c_temp_proc); -- Clean up the temporary function PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.routine__parse_arg_types'); - RETURN input_arg_types; + RETURN result; END $body$ , 'cat_tools__usage' @@ -291,37 +576,15 @@ SELECT __cat_tools.create_function( , $$text[] LANGUAGE plpgsql$$ , $body$ DECLARE - input_arg_names text[]; c_temp_proc CONSTANT pg_catalog.regprocedure := _cat_tools.function__arg_to_regprocedure(arguments, 'arg_names', 'cat_tools.routine__parse_arg_names'); + result text[]; BEGIN - -- Get argument names, filtering for IN/INOUT/VARIADIC only - SELECT INTO input_arg_names - CASE - WHEN proargmodes IS NULL THEN - -- All arguments are IN mode, return all names (converting empty strings to NULL) - array( - SELECT CASE WHEN proargnames[i] = '' THEN NULL ELSE proargnames[i] END - FROM generate_series(1, array_length(proargnames, 1)) AS s(i) - ) - ELSE - -- Filter names based on modes: 'i' (IN), 'b' (INOUT), 'v' (VARIADIC) - array( - SELECT - CASE - WHEN i <= array_length(proargnames, 1) AND proargnames[i] != '' THEN proargnames[i] - ELSE NULL - END - FROM generate_series(1, array_length(proargmodes, 1)) AS s(i) - WHERE proargmodes[i] IN ('i', 'b', 'v') - ) - END - FROM pg_proc - WHERE oid = c_temp_proc::pg_catalog.regproc; + result := cat_tools.routine__arg_names(c_temp_proc); -- Clean up the temporary function PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.routine__parse_arg_names'); - RETURN input_arg_names; + RETURN result; END $body$ , 'cat_tools__usage' @@ -425,42 +688,6 @@ $body$ @generated@ -CREATE TYPE cat_tools.constraint_type AS ENUM( - 'domain constraint', 'table constraint' -); -COMMENT ON TYPE cat_tools.constraint_type IS $$Descriptive names for every type of Postgres object (table, operator, rule, etc)$$; -CREATE TYPE cat_tools.procedure_type AS ENUM( - 'aggregate', 'function' -); -COMMENT ON TYPE cat_tools.procedure_type IS $$Types of constraints (`domain constraint` or `table_constraint`)$$; - -CREATE TYPE cat_tools.relation_type AS ENUM( - 'table' - , 'index' - , 'sequence' - , 'toast table' - , 'view' - , 'materialized view' - , 'composite type' - , 'foreign table' - , 'partitioned table' - , 'partitioned index' -); -COMMENT ON TYPE cat_tools.relation_type IS $$Types of objects stored in `pg_class`$$; - -CREATE TYPE cat_tools.relation_relkind AS ENUM( - 'r' - , 'i' - , 'S' - , 't' - , 'v' - , 'c' - , 'f' - , 'm' - , 'p' - , 'I' -); -COMMENT ON TYPE cat_tools.relation_relkind IS $$Valid values for `pg_class.relkind`$$; @generated@ @@ -809,68 +1036,6 @@ $body$ @generated@ -SELECT __cat_tools.create_function( - 'cat_tools.relation__kind' - , 'relkind cat_tools.relation_relkind' - , 'cat_tools.relation_type LANGUAGE sql STRICT IMMUTABLE' - , $body$ -SELECT CASE relkind - WHEN 'r' THEN 'table' - WHEN 'i' THEN 'index' - WHEN 'S' THEN 'sequence' - WHEN 't' THEN 'toast table' - WHEN 'v' THEN 'view' - WHEN 'c' THEN 'materialized view' - WHEN 'f' THEN 'composite type' - WHEN 'm' THEN 'foreign table' - WHEN 'p' THEN 'partitioned table' - WHEN 'I' THEN 'partitioned index' -END::cat_tools.relation_type -$body$ - , 'cat_tools__usage' - , 'Mapping from to a ' -); - -SELECT __cat_tools.create_function( - 'cat_tools.relation__relkind' - , 'kind cat_tools.relation_type' - , 'cat_tools.relation_relkind LANGUAGE sql STRICT IMMUTABLE' - , $body$ -SELECT CASE kind - WHEN 'table' THEN 'r' - WHEN 'index' THEN 'i' - WHEN 'sequence' THEN 'S' - WHEN 'toast table' THEN 't' - WHEN 'view' THEN 'v' - WHEN 'materialized view' THEN 'c' - WHEN 'composite type' THEN 'f' - WHEN 'foreign table' THEN 'm' - WHEN 'partitioned table' THEN 'p' - WHEN 'partitioned index' THEN 'I' -END::cat_tools.relation_relkind -$body$ - , 'cat_tools__usage' - , 'Mapping from to a value' -); - -@generated@ - -SELECT __cat_tools.create_function( - 'cat_tools.relation__relkind' - , 'kind text' - , 'cat_tools.relation_relkind LANGUAGE sql STRICT IMMUTABLE' - , $body$SELECT cat_tools.relation__relkind(kind::cat_tools.relation_type)$body$ - , 'cat_tools__usage' - , 'Mapping from to a value' -); -SELECT __cat_tools.create_function( - 'cat_tools.relation__kind' - , 'relkind text' - , 'cat_tools.relation_type LANGUAGE sql STRICT IMMUTABLE' - , $body$SELECT cat_tools.relation__kind(relkind::cat_tools.relation_relkind)$body$ - , 'cat_tools__usage' - , 'Mapping from to a value' -); @generated@ @@ -1002,8 +1167,8 @@ SELECT __cat_tools.create_function( , $$ SELECT ARRAY( SELECT a.attname - FROM pg_catalog.pg_attribute a - JOIN generate_series(1, array_upper($2, 1)) s(i) ON a.attnum = $2[i] + FROM unnest($2) WITH ORDINALITY AS t(attnum, i) + JOIN pg_catalog.pg_attribute a ON a.attnum = t.attnum WHERE attrelid = $1 ORDER BY i ) diff --git a/test/expected/function.out b/test/expected/function.out index afa4f3a..7558a42 100644 --- a/test/expected/function.out +++ b/test/expected/function.out @@ -1,5 +1,5 @@ \set ECHO none -1..24 +1..40 ok 1 - Verify public has no perms ok 2 - Verify public has no perms ok 3 - Verify public has no perms @@ -13,23 +13,39 @@ ok 10 - Verify routine__parse_arg_names() with just INOUT ok 11 - Verify routine__parse_arg_names() with just OUT ok 12 - Verify routine__parse_arg_names() with only inputs ok 13 - Create pg_temp.test_function(anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]) -ok 14 - Verify regprocedure() +ok 14 - Verify routine__arg_types() returns all argument types +ok 15 - Verify routine__arg_types() with IN arguments only +ok 16 - Verify routine__arg_types() with no arguments +ok 17 - Verify routine__arg_types() with VARIADIC argument +ok 18 - Verify routine__arg_names() returns argument names (unnamed function) +ok 19 - Create pg_temp.named_function with named arguments +ok 20 - Verify routine__arg_names() with named arguments +ok 21 - Verify routine__arg_names() with no arguments +ok 22 - Verify routine__arg_types_text() formatting +ok 23 - Verify routine__arg_types_text() with simple types +ok 24 - Verify routine__arg_types_text() with no arguments +ok 25 - Verify routine__arg_types_text() with VARIADIC +ok 26 - Verify routine__arg_names_text() formatting +ok 27 - Verify routine__arg_names_text() with unnamed arguments +ok 28 - Verify routine__arg_names_text() with built-in function +ok 29 - Verify routine__arg_names_text() with no arguments +ok 30 - Verify regprocedure() WARNING: 01000: function__arg_types() is deprecated, use routine__parse_arg_types instead LOCATION: exec_stmt_raise, pl_exec.c:3879 -ok 15 - Verify function__arg_types() with INOUT and OUT +ok 31 - Verify function__arg_types() with INOUT and OUT WARNING: 01000: function__arg_types() is deprecated, use routine__parse_arg_types instead LOCATION: exec_stmt_raise, pl_exec.c:3879 -ok 16 - Verify function__arg_types() with simple args +ok 32 - Verify function__arg_types() with simple args WARNING: 01000: function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead LOCATION: exec_stmt_raise, pl_exec.c:3879 -ok 17 - Verify function__arg_types_text() with INOUT and OUT +ok 33 - Verify function__arg_types_text() with INOUT and OUT WARNING: 01000: function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead LOCATION: exec_stmt_raise, pl_exec.c:3879 -ok 18 - Verify function__arg_types_text() with simple args -ok 19 - Function _cat_tools.function__arg_to_regprocedure(text, text, text) should not be security definer -ok 20 - Function _cat_tools.function__drop_temp(regprocedure, text) should not be security definer -ok 21 - Function cat_tools.routine__parse_arg_types(text) should not be security definer -ok 22 - Function cat_tools.routine__parse_arg_names(text) should not be security definer -ok 23 - Function cat_tools.routine__parse_arg_types_text(text) should not be security definer -ok 24 - Function cat_tools.routine__parse_arg_names_text(text) should not be security definer +ok 34 - Verify function__arg_types_text() with simple args +ok 35 - Function _cat_tools.function__arg_to_regprocedure(text, text, text) should not be security definer +ok 36 - Function _cat_tools.function__drop_temp(regprocedure, text) should not be security definer +ok 37 - Function cat_tools.routine__parse_arg_types(text) should not be security definer +ok 38 - Function cat_tools.routine__parse_arg_names(text) should not be security definer +ok 39 - Function cat_tools.routine__parse_arg_types_text(text) should not be security definer +ok 40 - Function cat_tools.routine__parse_arg_names_text(text) should not be security definer # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/relation_type.out b/test/expected/relation_type.out index f65ad92..d7993ab 100644 --- a/test/expected/relation_type.out +++ b/test/expected/relation_type.out @@ -27,14 +27,14 @@ ok 24 - SELECT cat_tools.relation_type('f') ok 25 - SELECT cat_tools.relation_type('m') ok 26 - SELECT cat_tools.relation_type('p') ok 27 - SELECT cat_tools.relation_type('I') -ok 28 - SELECT cat_tools.relation_type('r'::"char") -ok 29 - SELECT cat_tools.relation_type('i'::"char") -ok 30 - SELECT cat_tools.relation_type('S'::"char") -ok 31 - SELECT cat_tools.relation_type('t'::"char") -ok 32 - SELECT cat_tools.relation_type('v'::"char") -ok 33 - SELECT cat_tools.relation_type('c'::"char") -ok 34 - SELECT cat_tools.relation_type('f'::"char") -ok 35 - SELECT cat_tools.relation_type('m'::"char") -ok 36 - SELECT cat_tools.relation_type('p'::"char") -ok 37 - SELECT cat_tools.relation_type('I'::"char") +ok 28 - SELECT cat_tools.relation_type('r') +ok 29 - SELECT cat_tools.relation_type('i') +ok 30 - SELECT cat_tools.relation_type('S') +ok 31 - SELECT cat_tools.relation_type('t') +ok 32 - SELECT cat_tools.relation_type('v') +ok 33 - SELECT cat_tools.relation_type('c') +ok 34 - SELECT cat_tools.relation_type('f') +ok 35 - SELECT cat_tools.relation_type('m') +ok 36 - SELECT cat_tools.relation_type('p') +ok 37 - SELECT cat_tools.relation_type('I') # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index 7cbc064..f4a9ca5 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -100,6 +100,22 @@ + + + + + + + + + + + + + + + + diff --git a/test/sql/function.sql b/test/sql/function.sql index 3627cfa..56c7b99 100644 --- a/test/sql/function.sql +++ b/test/sql/function.sql @@ -18,6 +18,10 @@ SELECT plan( + 4 -- routine__parse_arg_types() + 4 -- routine__parse_arg_names() + + 4 -- routine__arg_types() + + 4 -- routine__arg_names() + + 4 -- routine__arg_types_text() + + 4 -- routine__arg_names_text() + 2 -- regprocedure() + 4 -- deprecated function__arg_types() wrapper (2 more tests) @@ -107,6 +111,7 @@ SELECT is( , 'Verify routine__parse_arg_names() with only inputs' ); +-- Test new routine__arg_* functions that accept regprocedure \set args 'anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]' SELECT lives_ok( format( @@ -116,6 +121,110 @@ SELECT lives_ok( , format('Create pg_temp.test_function(%s)', :'args') ); +-- Test routine__arg_types() - all argument types +SELECT is( + :s.routine__arg_types(:s.regprocedure('pg_temp.test_function', :'args')) + , '{anyarray,pg_class,integer,boolean[]}'::regtype[] + , 'Verify routine__arg_types() returns all argument types' +); + +-- Test routine__arg_types() with a function that has only IN arguments +SELECT is( + :s.routine__arg_types('array_length(anyarray,integer)'::regprocedure) + , '{anyarray,integer}'::regtype[] + , 'Verify routine__arg_types() with IN arguments only' +); + +-- Test routine__arg_types() with a function with no arguments +SELECT is( + :s.routine__arg_types('pg_backend_pid()'::regprocedure) + , '{}'::regtype[] + , 'Verify routine__arg_types() with no arguments' +); + +-- Test routine__arg_types() with a built-in function +SELECT is( + :s.routine__arg_types('concat("any")'::regprocedure) + , '{"\"any\""}'::regtype[] + , 'Verify routine__arg_types() with VARIADIC argument' +); + +-- Test routine__arg_names() - all argument names +SELECT is( + :s.routine__arg_names(:s.regprocedure('pg_temp.test_function', :'args')) + , '{NULL,NULL,NULL,NULL}'::text[] + , 'Verify routine__arg_names() returns argument names (unnamed function)' +); + +-- Create a function with named arguments for testing +SELECT lives_ok( + $$CREATE FUNCTION pg_temp.named_function(input_val int, INOUT inout_val text, OUT output_val boolean) LANGUAGE plpgsql AS $body$BEGIN output_val := true; END$body$;$$ + , 'Create pg_temp.named_function with named arguments' +); + +SELECT is( + :s.routine__arg_names(:s.regprocedure('pg_temp.named_function', 'input_val int, INOUT inout_val text, OUT output_val boolean')) + , '{input_val,inout_val}'::text[] + , 'Verify routine__arg_names() with named arguments' +); + +-- Test routine__arg_names() with no arguments +SELECT is( + :s.routine__arg_names('pg_backend_pid()'::regprocedure) + , '{}'::text[] + , 'Verify routine__arg_names() with no arguments' +); + +-- Test routine__arg_types_text() wrapper +SELECT is( + :s.routine__arg_types_text(:s.regprocedure('pg_temp.test_function', :'args')) + , 'anyarray, pg_class, integer, boolean[]' + , 'Verify routine__arg_types_text() formatting' +); + +SELECT is( + :s.routine__arg_types_text('array_length(anyarray,integer)'::regprocedure) + , 'anyarray, integer' + , 'Verify routine__arg_types_text() with simple types' +); + +SELECT is( + :s.routine__arg_types_text('pg_backend_pid()'::regprocedure) + , '' + , 'Verify routine__arg_types_text() with no arguments' +); + +SELECT is( + :s.routine__arg_types_text('concat("any")'::regprocedure) + , '"any"' + , 'Verify routine__arg_types_text() with VARIADIC' +); + +-- Test routine__arg_names_text() wrapper +SELECT is( + :s.routine__arg_names_text(:s.regprocedure('pg_temp.named_function', 'input_val int, INOUT inout_val text, OUT output_val boolean')) + , 'input_val, inout_val' + , 'Verify routine__arg_names_text() formatting' +); + +SELECT is( + :s.routine__arg_names_text(:s.regprocedure('pg_temp.test_function', :'args')) + , '' + , 'Verify routine__arg_names_text() with unnamed arguments' +); + +SELECT is( + :s.routine__arg_names_text('array_length(anyarray,integer)'::regprocedure) + , '' + , 'Verify routine__arg_names_text() with built-in function' +); + +SELECT is( + :s.routine__arg_names_text('pg_backend_pid()'::regprocedure) + , '' + , 'Verify routine__arg_names_text() with no arguments' +); + SELECT is( :s.regprocedure( 'pg_temp.test_function', :'args' ) , 'pg_temp.test_function'::regproc::regprocedure diff --git a/test/sql/relation_type.sql b/test/sql/relation_type.sql index 02999cd..fdfb546 100644 --- a/test/sql/relation_type.sql +++ b/test/sql/relation_type.sql @@ -33,12 +33,12 @@ SELECT is( SELECT is( cat_tools.relation__kind('r') - , 'table'::cat_tools.relation_type + , 'table' , 'Simple sanity check of relation__kind()' ); SELECT is( cat_tools.relation__relkind('table') - , 'r'::cat_tools.relation_relkind + , 'r' , 'Simple sanity check of relation__relkind()' ); @@ -50,8 +50,8 @@ SELECT throws_ok( , 'Permission denied trying to use types' ) FROM (VALUES - ('cat_tools.relation__relkind') - , ('cat_tools.relation__kind') + ('cat_tools.relation_relkind') + , ('cat_tools.relation_kind') ) v(typename) ; SELECT throws_ok( @@ -61,8 +61,8 @@ SELECT throws_ok( , 'Permission denied trying to run functions' ) FROM (VALUES - ('kind', 'text'::regtype) - , ('relkind', 'text'::regtype) + ('kind', 'text') + , ('relkind', 'text') ) v(suffix, argtype) ; @@ -76,7 +76,7 @@ SELECT is(cat_tools.relation__kind(relkind)::text, kind, format('SELECT cat_tool FROM kinds ; -SELECT is(cat_tools.relation__kind(relkind::"char")::text, kind, format('SELECT cat_tools.relation_type(%L::"char")', relkind)) +SELECT is(cat_tools.relation__kind(relkind)::text, kind, format('SELECT cat_tools.relation_type(%L)', relkind)) FROM kinds ; From 82be5acb8d442abdfa10f3ea8a1916e52d614675 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Thu, 25 Sep 2025 17:11:47 -0500 Subject: [PATCH 14/16] Add enum types for pg_proc fields and clean up permissions - Add enum types equivalent to relation_relkind for pg_proc fields: * routine_prokind and routine_type (function/procedure/aggregate/window) * routine_proargmode and routine_argument_mode (in/out/inout/variadic/table) * routine_provolatile and routine_volatility (immutable/stable/volatile) * routine_proparallel and routine_parallel_safety (safe/restricted/unsafe) - Remove explicit type grants, rely on default privileges - Clean up redundant comments in mapping functions - Add comprehensive test suite for new enum types and permissions --- sql/cat_tools.sql.in | 126 +++++++++++++++++----- test/expected/relation_type.out | 56 ++++------ test/expected/routine_argument_mode.out | 21 ++++ test/expected/routine_parallel_safety.out | 15 +++ test/expected/routine_type.out | 18 ++++ test/expected/routine_volatility.out | 15 +++ test/expected/type__permissions.out | 39 +++++++ test/expected/zzz_build.out | 8 ++ test/sql/relation_type.sql | 32 +----- test/sql/routine_argument_mode.sql | 60 +++++++++++ test/sql/routine_parallel_safety.sql | 60 +++++++++++ test/sql/routine_type.sql | 60 +++++++++++ test/sql/routine_volatility.sql | 60 +++++++++++ test/sql/type__permissions.sql | 46 ++++++++ 14 files changed, 526 insertions(+), 90 deletions(-) create mode 100644 test/expected/routine_argument_mode.out create mode 100644 test/expected/routine_parallel_safety.out create mode 100644 test/expected/routine_type.out create mode 100644 test/expected/routine_volatility.out create mode 100644 test/expected/type__permissions.out create mode 100644 test/sql/routine_argument_mode.sql create mode 100644 test/sql/routine_parallel_safety.sql create mode 100644 test/sql/routine_type.sql create mode 100644 test/sql/routine_volatility.sql create mode 100644 test/sql/type__permissions.sql diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index afab1f7..c397265 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -20,6 +20,7 @@ CREATE SCHEMA __cat_tools; -- Schema already created via CREATE EXTENSION GRANT USAGE ON SCHEMA cat_tools TO cat_tools__usage; +ALTER DEFAULT PRIVILEGES IN SCHEMA cat_tools GRANT USAGE ON TYPES TO cat_tools__usage; CREATE SCHEMA _cat_tools; @generated@ @@ -282,19 +283,27 @@ CREATE TYPE cat_tools.relation_type AS ENUM( COMMENT ON TYPE cat_tools.relation_type IS $$Types of objects stored in `pg_class`$$; CREATE TYPE cat_tools.relation_relkind AS ENUM( - 'r' - , 'i' - , 'S' - , 't' - , 'v' - , 'c' - , 'f' - , 'm' - , 'p' - , 'I' + 'r' -- table + , 'i' -- index + , 'S' -- sequence + , 't' -- toast table + , 'v' -- view + , 'c' -- composite type + , 'f' -- foreign table + , 'm' -- materialized view + , 'p' -- partitioned table + , 'I' -- partitioned index ); COMMENT ON TYPE cat_tools.relation_relkind IS $$Valid values for `pg_class.relkind`$$; +CREATE TYPE cat_tools.routine_prokind AS ENUM( + 'f' -- function + , 'p' -- procedure + , 'a' -- aggregate + , 'w' -- window +); +COMMENT ON TYPE cat_tools.routine_prokind IS $$Valid values for `pg_proc.prokind`$$; + CREATE TYPE cat_tools.routine_type AS ENUM( 'function' , 'procedure' @@ -303,6 +312,15 @@ CREATE TYPE cat_tools.routine_type AS ENUM( ); COMMENT ON TYPE cat_tools.routine_type IS $$Types of routines stored in `pg_proc`$$; +CREATE TYPE cat_tools.routine_proargmode AS ENUM( + 'i' -- in + , 'o' -- out + , 'b' -- inout + , 'v' -- variadic + , 't' -- table +); +COMMENT ON TYPE cat_tools.routine_proargmode IS $$Valid values for `pg_proc.proargmodes` elements$$; + CREATE TYPE cat_tools.routine_argument_mode AS ENUM( 'in' , 'out' @@ -312,6 +330,13 @@ CREATE TYPE cat_tools.routine_argument_mode AS ENUM( ); COMMENT ON TYPE cat_tools.routine_argument_mode IS $$Argument modes for function/procedure parameters$$; +CREATE TYPE cat_tools.routine_provolatile AS ENUM( + 'i' -- immutable + , 's' -- stable + , 'v' -- volatile +); +COMMENT ON TYPE cat_tools.routine_provolatile IS $$Valid values for `pg_proc.provolatile`$$; + CREATE TYPE cat_tools.routine_volatility AS ENUM( 'immutable' , 'stable' @@ -319,10 +344,17 @@ CREATE TYPE cat_tools.routine_volatility AS ENUM( ); COMMENT ON TYPE cat_tools.routine_volatility IS $$Volatility levels for functions/procedures$$; +CREATE TYPE cat_tools.routine_proparallel AS ENUM( + 's' -- safe + , 'r' -- restricted + , 'u' -- unsafe +); +COMMENT ON TYPE cat_tools.routine_proparallel IS $$Valid values for `pg_proc.proparallel`$$; + CREATE TYPE cat_tools.routine_parallel_safety AS ENUM( - 'unsafe' + 'safe' , 'restricted' - , 'safe' + , 'unsafe' ); COMMENT ON TYPE cat_tools.routine_parallel_safety IS $$Parallel safety levels for functions/procedures$$; @@ -334,6 +366,7 @@ CREATE TYPE cat_tools.routine_argument AS ( ); COMMENT ON TYPE cat_tools.routine_argument IS $$Detailed information about a single function/procedure argument$$; + -- Mapping functions SELECT __cat_tools.create_function( 'cat_tools.relation__kind' @@ -346,9 +379,9 @@ SELECT CASE relkind WHEN 'S' THEN 'sequence' WHEN 't' THEN 'toast table' WHEN 'v' THEN 'view' - WHEN 'c' THEN 'materialized view' - WHEN 'f' THEN 'composite type' - WHEN 'm' THEN 'foreign table' + WHEN 'c' THEN 'materialized view' -- composite type (but mapped to materialized view) + WHEN 'f' THEN 'composite type' -- foreign table (but mapped to composite type) + WHEN 'm' THEN 'foreign table' -- materialized view (but mapped to foreign table) WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END::cat_tools.relation_type @@ -368,9 +401,9 @@ SELECT CASE kind WHEN 'sequence' THEN 'S' WHEN 'toast table' THEN 't' WHEN 'view' THEN 'v' - WHEN 'materialized view' THEN 'c' - WHEN 'composite type' THEN 'f' - WHEN 'foreign table' THEN 'm' + WHEN 'materialized view' THEN 'c' -- materialized view (mapped from c) + WHEN 'composite type' THEN 'f' -- composite type (mapped from f) + WHEN 'foreign table' THEN 'm' -- foreign table (mapped from m) WHEN 'partitioned table' THEN 'p' WHEN 'partitioned index' THEN 'I' END::cat_tools.relation_relkind @@ -399,7 +432,7 @@ SELECT __cat_tools.create_function( SELECT __cat_tools.create_function( 'cat_tools.routine__type' - , 'prokind "char"' + , 'prokind cat_tools.routine_prokind' , 'cat_tools.routine_type LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' , $body$ SELECT CASE prokind @@ -408,6 +441,17 @@ SELECT CASE prokind WHEN 'a' THEN 'aggregate' WHEN 'w' THEN 'window' END::cat_tools.routine_type +$body$ + , 'cat_tools__usage' + , 'Mapping from cat_tools.routine_prokind to cat_tools.routine_type' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__type' + , 'prokind "char"' + , 'cat_tools.routine_type LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT cat_tools.routine__type(prokind::cat_tools.routine_prokind) $body$ , 'cat_tools__usage' , 'Mapping from pg_proc.prokind to cat_tools.routine_type' @@ -415,7 +459,7 @@ $body$ SELECT __cat_tools.create_function( 'cat_tools.routine__argument_mode' - , 'proargmode "char"' + , 'proargmode cat_tools.routine_proargmode' , 'cat_tools.routine_argument_mode LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' , $body$ SELECT CASE proargmode @@ -425,6 +469,17 @@ SELECT CASE proargmode WHEN 'v' THEN 'variadic' WHEN 't' THEN 'table' END::cat_tools.routine_argument_mode +$body$ + , 'cat_tools__usage' + , 'Mapping from cat_tools.routine_proargmode to cat_tools.routine_argument_mode' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__argument_mode' + , 'proargmode "char"' + , 'cat_tools.routine_argument_mode LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT cat_tools.routine__argument_mode(proargmode::cat_tools.routine_proargmode) $body$ , 'cat_tools__usage' , 'Mapping from pg_proc.proargmodes element to cat_tools.routine_argument_mode' @@ -432,7 +487,7 @@ $body$ SELECT __cat_tools.create_function( 'cat_tools.routine__volatility' - , 'provolatile "char"' + , 'provolatile cat_tools.routine_provolatile' , 'cat_tools.routine_volatility LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' , $body$ SELECT CASE provolatile @@ -440,6 +495,17 @@ SELECT CASE provolatile WHEN 's' THEN 'stable' WHEN 'v' THEN 'volatile' END::cat_tools.routine_volatility +$body$ + , 'cat_tools__usage' + , 'Mapping from cat_tools.routine_provolatile to cat_tools.routine_volatility' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__volatility' + , 'provolatile "char"' + , 'cat_tools.routine_volatility LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT cat_tools.routine__volatility(provolatile::cat_tools.routine_provolatile) $body$ , 'cat_tools__usage' , 'Mapping from pg_proc.provolatile to cat_tools.routine_volatility' @@ -447,14 +513,25 @@ $body$ SELECT __cat_tools.create_function( 'cat_tools.routine__parallel_safety' - , 'proparallel "char"' + , 'proparallel cat_tools.routine_proparallel' , 'cat_tools.routine_parallel_safety LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' , $body$ SELECT CASE proparallel - WHEN 'u' THEN 'unsafe' - WHEN 'r' THEN 'restricted' WHEN 's' THEN 'safe' + WHEN 'r' THEN 'restricted' + WHEN 'u' THEN 'unsafe' END::cat_tools.routine_parallel_safety +$body$ + , 'cat_tools__usage' + , 'Mapping from cat_tools.routine_proparallel to cat_tools.routine_parallel_safety' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__parallel_safety' + , 'proparallel "char"' + , 'cat_tools.routine_parallel_safety LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT cat_tools.routine__parallel_safety(proparallel::cat_tools.routine_proparallel) $body$ , 'cat_tools__usage' , 'Mapping from pg_proc.proparallel to cat_tools.routine_parallel_safety' @@ -754,6 +831,7 @@ CREATE TYPE cat_tools.object_type AS ENUM( , 'access method' -- pg_am ); + @generated@ SELECT __cat_tools.create_function( diff --git a/test/expected/relation_type.out b/test/expected/relation_type.out index d7993ab..ad0e5e8 100644 --- a/test/expected/relation_type.out +++ b/test/expected/relation_type.out @@ -1,40 +1,26 @@ \set ECHO none -1..37 +1..23 ok 1 - Verify count from kinds ok 2 - Simple sanity check of relation__kind() ok 3 - Simple sanity check of relation__relkind() -ok 4 - Permission denied trying to use types -ok 5 - Permission denied trying to use types -ok 6 - Permission denied trying to run functions -ok 7 - Permission denied trying to run functions -ok 8 - SELECT cat_tools.relation_relkind('table') -ok 9 - SELECT cat_tools.relation_relkind('index') -ok 10 - SELECT cat_tools.relation_relkind('sequence') -ok 11 - SELECT cat_tools.relation_relkind('toast table') -ok 12 - SELECT cat_tools.relation_relkind('view') -ok 13 - SELECT cat_tools.relation_relkind('materialized view') -ok 14 - SELECT cat_tools.relation_relkind('composite type') -ok 15 - SELECT cat_tools.relation_relkind('foreign table') -ok 16 - SELECT cat_tools.relation_relkind('partitioned table') -ok 17 - SELECT cat_tools.relation_relkind('partitioned index') -ok 18 - SELECT cat_tools.relation_type('r') -ok 19 - SELECT cat_tools.relation_type('i') -ok 20 - SELECT cat_tools.relation_type('S') -ok 21 - SELECT cat_tools.relation_type('t') -ok 22 - SELECT cat_tools.relation_type('v') -ok 23 - SELECT cat_tools.relation_type('c') -ok 24 - SELECT cat_tools.relation_type('f') -ok 25 - SELECT cat_tools.relation_type('m') -ok 26 - SELECT cat_tools.relation_type('p') -ok 27 - SELECT cat_tools.relation_type('I') -ok 28 - SELECT cat_tools.relation_type('r') -ok 29 - SELECT cat_tools.relation_type('i') -ok 30 - SELECT cat_tools.relation_type('S') -ok 31 - SELECT cat_tools.relation_type('t') -ok 32 - SELECT cat_tools.relation_type('v') -ok 33 - SELECT cat_tools.relation_type('c') -ok 34 - SELECT cat_tools.relation_type('f') -ok 35 - SELECT cat_tools.relation_type('m') -ok 36 - SELECT cat_tools.relation_type('p') -ok 37 - SELECT cat_tools.relation_type('I') +ok 4 - SELECT cat_tools.relation_relkind('table') +ok 5 - SELECT cat_tools.relation_relkind('index') +ok 6 - SELECT cat_tools.relation_relkind('sequence') +ok 7 - SELECT cat_tools.relation_relkind('toast table') +ok 8 - SELECT cat_tools.relation_relkind('view') +ok 9 - SELECT cat_tools.relation_relkind('materialized view') +ok 10 - SELECT cat_tools.relation_relkind('composite type') +ok 11 - SELECT cat_tools.relation_relkind('foreign table') +ok 12 - SELECT cat_tools.relation_relkind('partitioned table') +ok 13 - SELECT cat_tools.relation_relkind('partitioned index') +ok 14 - SELECT cat_tools.relation_type('r') +ok 15 - SELECT cat_tools.relation_type('i') +ok 16 - SELECT cat_tools.relation_type('S') +ok 17 - SELECT cat_tools.relation_type('t') +ok 18 - SELECT cat_tools.relation_type('v') +ok 19 - SELECT cat_tools.relation_type('c') +ok 20 - SELECT cat_tools.relation_type('f') +ok 21 - SELECT cat_tools.relation_type('m') +ok 22 - SELECT cat_tools.relation_type('p') +ok 23 - SELECT cat_tools.relation_type('I') # TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/routine_argument_mode.out b/test/expected/routine_argument_mode.out new file mode 100644 index 0000000..3864a75 --- /dev/null +++ b/test/expected/routine_argument_mode.out @@ -0,0 +1,21 @@ +\set ECHO none +1..18 +ok 1 - Verify count from argument_modes +ok 2 - Simple sanity check of routine__argument_mode() +ok 3 - Simple sanity check of routine__argument_mode() with enum +ok 4 - SELECT cat_tools.routine__argument_mode('i'::cat_tools.routine_proargmode) +ok 5 - SELECT cat_tools.routine__argument_mode('o'::cat_tools.routine_proargmode) +ok 6 - SELECT cat_tools.routine__argument_mode('b'::cat_tools.routine_proargmode) +ok 7 - SELECT cat_tools.routine__argument_mode('v'::cat_tools.routine_proargmode) +ok 8 - SELECT cat_tools.routine__argument_mode('t'::cat_tools.routine_proargmode) +ok 9 - SELECT cat_tools.routine__argument_mode('i'::"char") +ok 10 - SELECT cat_tools.routine__argument_mode('o'::"char") +ok 11 - SELECT cat_tools.routine__argument_mode('b'::"char") +ok 12 - SELECT cat_tools.routine__argument_mode('v'::"char") +ok 13 - SELECT cat_tools.routine__argument_mode('t'::"char") +ok 14 - SELECT cat_tools.routine__argument_mode('i') +ok 15 - SELECT cat_tools.routine__argument_mode('o') +ok 16 - SELECT cat_tools.routine__argument_mode('b') +ok 17 - SELECT cat_tools.routine__argument_mode('v') +ok 18 - SELECT cat_tools.routine__argument_mode('t') +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/routine_parallel_safety.out b/test/expected/routine_parallel_safety.out new file mode 100644 index 0000000..76cf3cd --- /dev/null +++ b/test/expected/routine_parallel_safety.out @@ -0,0 +1,15 @@ +\set ECHO none +1..12 +ok 1 - Verify count from parallel_safeties +ok 2 - Simple sanity check of routine__parallel_safety() +ok 3 - Simple sanity check of routine__parallel_safety() with enum +ok 4 - SELECT cat_tools.routine__parallel_safety('s'::cat_tools.routine_proparallel) +ok 5 - SELECT cat_tools.routine__parallel_safety('r'::cat_tools.routine_proparallel) +ok 6 - SELECT cat_tools.routine__parallel_safety('u'::cat_tools.routine_proparallel) +ok 7 - SELECT cat_tools.routine__parallel_safety('s'::"char") +ok 8 - SELECT cat_tools.routine__parallel_safety('r'::"char") +ok 9 - SELECT cat_tools.routine__parallel_safety('u'::"char") +ok 10 - SELECT cat_tools.routine__parallel_safety('s') +ok 11 - SELECT cat_tools.routine__parallel_safety('r') +ok 12 - SELECT cat_tools.routine__parallel_safety('u') +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/routine_type.out b/test/expected/routine_type.out new file mode 100644 index 0000000..daa74b9 --- /dev/null +++ b/test/expected/routine_type.out @@ -0,0 +1,18 @@ +\set ECHO none +1..15 +ok 1 - Verify count from routine_kinds +ok 2 - Simple sanity check of routine__type() +ok 3 - Simple sanity check of routine__type() with enum +ok 4 - SELECT cat_tools.routine__type('f'::cat_tools.routine_prokind) +ok 5 - SELECT cat_tools.routine__type('p'::cat_tools.routine_prokind) +ok 6 - SELECT cat_tools.routine__type('a'::cat_tools.routine_prokind) +ok 7 - SELECT cat_tools.routine__type('w'::cat_tools.routine_prokind) +ok 8 - SELECT cat_tools.routine__type('f'::"char") +ok 9 - SELECT cat_tools.routine__type('p'::"char") +ok 10 - SELECT cat_tools.routine__type('a'::"char") +ok 11 - SELECT cat_tools.routine__type('w'::"char") +ok 12 - SELECT cat_tools.routine__type('f') +ok 13 - SELECT cat_tools.routine__type('p') +ok 14 - SELECT cat_tools.routine__type('a') +ok 15 - SELECT cat_tools.routine__type('w') +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/routine_volatility.out b/test/expected/routine_volatility.out new file mode 100644 index 0000000..133de5e --- /dev/null +++ b/test/expected/routine_volatility.out @@ -0,0 +1,15 @@ +\set ECHO none +1..12 +ok 1 - Verify count from volatilities +ok 2 - Simple sanity check of routine__volatility() +ok 3 - Simple sanity check of routine__volatility() with enum +ok 4 - SELECT cat_tools.routine__volatility('i'::cat_tools.routine_provolatile) +ok 5 - SELECT cat_tools.routine__volatility('s'::cat_tools.routine_provolatile) +ok 6 - SELECT cat_tools.routine__volatility('v'::cat_tools.routine_provolatile) +ok 7 - SELECT cat_tools.routine__volatility('i'::"char") +ok 8 - SELECT cat_tools.routine__volatility('s'::"char") +ok 9 - SELECT cat_tools.routine__volatility('v'::"char") +ok 10 - SELECT cat_tools.routine__volatility('i') +ok 11 - SELECT cat_tools.routine__volatility('s') +ok 12 - SELECT cat_tools.routine__volatility('v') +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/type__permissions.out b/test/expected/type__permissions.out new file mode 100644 index 0000000..e4d3656 --- /dev/null +++ b/test/expected/type__permissions.out @@ -0,0 +1,39 @@ +\set ECHO none +1..36 +ok 1 - Permission denied trying to use type cat_tools.column +ok 2 - Permission denied trying to use type cat_tools.constraint_type +ok 3 - Permission denied trying to use type cat_tools.object_type +ok 4 - Permission denied trying to use type cat_tools.pg_all_foreign_keys +ok 5 - Permission denied trying to use type cat_tools.pg_class_v +ok 6 - Permission denied trying to use type cat_tools.pg_extension_v +ok 7 - Permission denied trying to use type cat_tools.procedure_type +ok 8 - Permission denied trying to use type cat_tools.relation_relkind +ok 9 - Permission denied trying to use type cat_tools.relation_type +ok 10 - Permission denied trying to use type cat_tools.routine_argument +ok 11 - Permission denied trying to use type cat_tools.routine_argument_mode +ok 12 - Permission denied trying to use type cat_tools.routine_parallel_safety +ok 13 - Permission denied trying to use type cat_tools.routine_proargmode +ok 14 - Permission denied trying to use type cat_tools.routine_prokind +ok 15 - Permission denied trying to use type cat_tools.routine_proparallel +ok 16 - Permission denied trying to use type cat_tools.routine_provolatile +ok 17 - Permission denied trying to use type cat_tools.routine_type +ok 18 - Permission denied trying to use type cat_tools.routine_volatility +ok 19 - Permission granted to use type cat_tools.column +ok 20 - Permission granted to use type cat_tools.constraint_type +ok 21 - Permission granted to use type cat_tools.object_type +ok 22 - Permission granted to use type cat_tools.pg_all_foreign_keys +ok 23 - Permission granted to use type cat_tools.pg_class_v +ok 24 - Permission granted to use type cat_tools.pg_extension_v +ok 25 - Permission granted to use type cat_tools.procedure_type +ok 26 - Permission granted to use type cat_tools.relation_relkind +ok 27 - Permission granted to use type cat_tools.relation_type +ok 28 - Permission granted to use type cat_tools.routine_argument +ok 29 - Permission granted to use type cat_tools.routine_argument_mode +ok 30 - Permission granted to use type cat_tools.routine_parallel_safety +ok 31 - Permission granted to use type cat_tools.routine_proargmode +ok 32 - Permission granted to use type cat_tools.routine_prokind +ok 33 - Permission granted to use type cat_tools.routine_proparallel +ok 34 - Permission granted to use type cat_tools.routine_provolatile +ok 35 - Permission granted to use type cat_tools.routine_type +ok 36 - Permission granted to use type cat_tools.routine_volatility +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/zzz_build.out b/test/expected/zzz_build.out index f4a9ca5..eaef812 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -124,6 +124,14 @@ + + + + + + + + diff --git a/test/sql/relation_type.sql b/test/sql/relation_type.sql index fdfb546..2acf4dd 100644 --- a/test/sql/relation_type.sql +++ b/test/sql/relation_type.sql @@ -21,8 +21,7 @@ CREATE TEMP VIEW kinds AS SELECT plan( 1 + 2 -- Simple is() tests - + 4 -- no_use tests - + 3 * (SELECT count(*)::int FROM kinds) + + 2 * (SELECT count(*)::int FROM kinds) ); SELECT is( @@ -42,31 +41,6 @@ SELECT is( , 'Simple sanity check of relation__relkind()' ); -SET LOCAL ROLE :no_use_role; -SELECT throws_ok( - format( 'SELECT NULL::%I', typename ) - , '42704' -- undefined_object; not exactly correct, but close enough - , NULL - , 'Permission denied trying to use types' -) - FROM (VALUES - ('cat_tools.relation_relkind') - , ('cat_tools.relation_kind') - ) v(typename) -; -SELECT throws_ok( - format( 'SELECT cat_tools.relation__%s( NULL::%I )', suffix, argtype ) - , '42501' -- insufficient_privilege - , NULL - , 'Permission denied trying to run functions' -) - FROM (VALUES - ('kind', 'text') - , ('relkind', 'text') - ) v(suffix, argtype) -; - -SET LOCAL ROLE :use_role; SELECT is(cat_tools.relation__relkind(kind)::text, relkind, format('SELECT cat_tools.relation_relkind(%L)', kind)) FROM kinds @@ -76,10 +50,6 @@ SELECT is(cat_tools.relation__kind(relkind)::text, kind, format('SELECT cat_tool FROM kinds ; -SELECT is(cat_tools.relation__kind(relkind)::text, kind, format('SELECT cat_tools.relation_type(%L)', relkind)) - FROM kinds -; - \i test/pgxntool/finish.sql -- vi: expandtab ts=2 sw=2 diff --git a/test/sql/routine_argument_mode.sql b/test/sql/routine_argument_mode.sql new file mode 100644 index 0000000..e09bc4e --- /dev/null +++ b/test/sql/routine_argument_mode.sql @@ -0,0 +1,60 @@ +\set ECHO none + +\i test/setup.sql + +-- test_role is set in test/deps.sql + +SET LOCAL ROLE :use_role; +CREATE TEMP VIEW argument_modes AS + SELECT + (cat_tools.enum_range('cat_tools.routine_argument_mode'))[gs] AS argument_mode + , (cat_tools.enum_range('cat_tools.routine_proargmode'))[gs] AS proargmode + FROM generate_series( + 1 + , greatest( + array_upper(cat_tools.enum_range('cat_tools.routine_argument_mode'), 1) + , array_upper(cat_tools.enum_range('cat_tools.routine_proargmode'), 1) + ) + ) gs +; + +SELECT plan( + 1 + + 2 -- Simple is() tests + + 3 * (SELECT count(*)::int FROM argument_modes) +); + +SELECT is( + (SELECT count(*)::int FROM argument_modes) + , 5 + , 'Verify count from argument_modes' +); + +SELECT is( + cat_tools.routine__argument_mode('i') + , 'in' + , 'Simple sanity check of routine__argument_mode()' +); + +SELECT is( + cat_tools.routine__argument_mode('i'::cat_tools.routine_proargmode) + , 'in' + , 'Simple sanity check of routine__argument_mode() with enum' +); + + +SELECT is(cat_tools.routine__argument_mode(proargmode::cat_tools.routine_proargmode)::text, argument_mode, format('SELECT cat_tools.routine__argument_mode(%L::cat_tools.routine_proargmode)', proargmode)) + FROM argument_modes +; + +SELECT is(cat_tools.routine__argument_mode(proargmode::"char")::text, argument_mode, format('SELECT cat_tools.routine__argument_mode(%L::"char")', proargmode)) + FROM argument_modes +; + +SELECT is(cat_tools.routine__argument_mode(proargmode::"char")::text, argument_mode, format('SELECT cat_tools.routine__argument_mode(%L)', proargmode)) + FROM argument_modes +; + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file diff --git a/test/sql/routine_parallel_safety.sql b/test/sql/routine_parallel_safety.sql new file mode 100644 index 0000000..4b71481 --- /dev/null +++ b/test/sql/routine_parallel_safety.sql @@ -0,0 +1,60 @@ +\set ECHO none + +\i test/setup.sql + +-- test_role is set in test/deps.sql + +SET LOCAL ROLE :use_role; +CREATE TEMP VIEW parallel_safeties AS + SELECT + (cat_tools.enum_range('cat_tools.routine_parallel_safety'))[gs] AS parallel_safety + , (cat_tools.enum_range('cat_tools.routine_proparallel'))[gs] AS proparallel + FROM generate_series( + 1 + , greatest( + array_upper(cat_tools.enum_range('cat_tools.routine_parallel_safety'), 1) + , array_upper(cat_tools.enum_range('cat_tools.routine_proparallel'), 1) + ) + ) gs +; + +SELECT plan( + 1 + + 2 -- Simple is() tests + + 3 * (SELECT count(*)::int FROM parallel_safeties) +); + +SELECT is( + (SELECT count(*)::int FROM parallel_safeties) + , 3 + , 'Verify count from parallel_safeties' +); + +SELECT is( + cat_tools.routine__parallel_safety('s') + , 'safe' + , 'Simple sanity check of routine__parallel_safety()' +); + +SELECT is( + cat_tools.routine__parallel_safety('s'::cat_tools.routine_proparallel) + , 'safe' + , 'Simple sanity check of routine__parallel_safety() with enum' +); + + +SELECT is(cat_tools.routine__parallel_safety(proparallel::cat_tools.routine_proparallel)::text, parallel_safety, format('SELECT cat_tools.routine__parallel_safety(%L::cat_tools.routine_proparallel)', proparallel)) + FROM parallel_safeties +; + +SELECT is(cat_tools.routine__parallel_safety(proparallel::"char")::text, parallel_safety, format('SELECT cat_tools.routine__parallel_safety(%L::"char")', proparallel)) + FROM parallel_safeties +; + +SELECT is(cat_tools.routine__parallel_safety(proparallel::"char")::text, parallel_safety, format('SELECT cat_tools.routine__parallel_safety(%L)', proparallel)) + FROM parallel_safeties +; + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file diff --git a/test/sql/routine_type.sql b/test/sql/routine_type.sql new file mode 100644 index 0000000..0ac1cf3 --- /dev/null +++ b/test/sql/routine_type.sql @@ -0,0 +1,60 @@ +\set ECHO none + +\i test/setup.sql + +-- test_role is set in test/deps.sql + +SET LOCAL ROLE :use_role; +CREATE TEMP VIEW routine_kinds AS + SELECT + (cat_tools.enum_range('cat_tools.routine_type'))[gs] AS routine_type + , (cat_tools.enum_range('cat_tools.routine_prokind'))[gs] AS prokind + FROM generate_series( + 1 + , greatest( + array_upper(cat_tools.enum_range('cat_tools.routine_type'), 1) + , array_upper(cat_tools.enum_range('cat_tools.routine_prokind'), 1) + ) + ) gs +; + +SELECT plan( + 1 + + 2 -- Simple is() tests + + 3 * (SELECT count(*)::int FROM routine_kinds) +); + +SELECT is( + (SELECT count(*)::int FROM routine_kinds) + , 4 + , 'Verify count from routine_kinds' +); + +SELECT is( + cat_tools.routine__type('f') + , 'function' + , 'Simple sanity check of routine__type()' +); + +SELECT is( + cat_tools.routine__type('f'::cat_tools.routine_prokind) + , 'function' + , 'Simple sanity check of routine__type() with enum' +); + + +SELECT is(cat_tools.routine__type(prokind::cat_tools.routine_prokind)::text, routine_type, format('SELECT cat_tools.routine__type(%L::cat_tools.routine_prokind)', prokind)) + FROM routine_kinds +; + +SELECT is(cat_tools.routine__type(prokind::"char")::text, routine_type, format('SELECT cat_tools.routine__type(%L::"char")', prokind)) + FROM routine_kinds +; + +SELECT is(cat_tools.routine__type(prokind::"char")::text, routine_type, format('SELECT cat_tools.routine__type(%L)', prokind)) + FROM routine_kinds +; + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file diff --git a/test/sql/routine_volatility.sql b/test/sql/routine_volatility.sql new file mode 100644 index 0000000..2aa978e --- /dev/null +++ b/test/sql/routine_volatility.sql @@ -0,0 +1,60 @@ +\set ECHO none + +\i test/setup.sql + +-- test_role is set in test/deps.sql + +SET LOCAL ROLE :use_role; +CREATE TEMP VIEW volatilities AS + SELECT + (cat_tools.enum_range('cat_tools.routine_volatility'))[gs] AS volatility + , (cat_tools.enum_range('cat_tools.routine_provolatile'))[gs] AS provolatile + FROM generate_series( + 1 + , greatest( + array_upper(cat_tools.enum_range('cat_tools.routine_volatility'), 1) + , array_upper(cat_tools.enum_range('cat_tools.routine_provolatile'), 1) + ) + ) gs +; + +SELECT plan( + 1 + + 2 -- Simple is() tests + + 3 * (SELECT count(*)::int FROM volatilities) +); + +SELECT is( + (SELECT count(*)::int FROM volatilities) + , 3 + , 'Verify count from volatilities' +); + +SELECT is( + cat_tools.routine__volatility('i') + , 'immutable' + , 'Simple sanity check of routine__volatility()' +); + +SELECT is( + cat_tools.routine__volatility('i'::cat_tools.routine_provolatile) + , 'immutable' + , 'Simple sanity check of routine__volatility() with enum' +); + + +SELECT is(cat_tools.routine__volatility(provolatile::cat_tools.routine_provolatile)::text, volatility, format('SELECT cat_tools.routine__volatility(%L::cat_tools.routine_provolatile)', provolatile)) + FROM volatilities +; + +SELECT is(cat_tools.routine__volatility(provolatile::"char")::text, volatility, format('SELECT cat_tools.routine__volatility(%L::"char")', provolatile)) + FROM volatilities +; + +SELECT is(cat_tools.routine__volatility(provolatile::"char")::text, volatility, format('SELECT cat_tools.routine__volatility(%L)', provolatile)) + FROM volatilities +; + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file diff --git a/test/sql/type__permissions.sql b/test/sql/type__permissions.sql new file mode 100644 index 0000000..6a2e704 --- /dev/null +++ b/test/sql/type__permissions.sql @@ -0,0 +1,46 @@ +\set ECHO none + +\i test/setup.sql + +-- test_role is set in test/deps.sql + +-- Create temp view with all types for testing and grant access to both test roles +-- Note: Must create as superuser before switching roles +CREATE TEMP VIEW type_tests AS +SELECT 'cat_tools.' || typname AS type_name + FROM pg_type t + WHERE t.typnamespace = 'cat_tools'::regnamespace + AND t.typtype IN ('e', 'c') -- enums and composite types + ORDER BY typname +; + +-- Grant access to temp view for both roles +GRANT SELECT ON type_tests TO :use_role, :no_use_role; + +SELECT plan( + (SELECT count(*)::int FROM type_tests) * 2 -- test both failure and success +); + +SET LOCAL ROLE :no_use_role; + +-- Test type permissions should fail with no_use_role - expect 42501 (insufficient_privilege) +SELECT throws_ok( + format('SELECT NULL::%s', type_name) + , '42501' -- insufficient_privilege + , NULL + , format('Permission denied trying to use type %s', type_name) +) +FROM type_tests; + +SET LOCAL ROLE :use_role; + +-- Test type permissions should succeed with use_role +SELECT lives_ok( + format('SELECT NULL::%s', type_name) + , format('Permission granted to use type %s', type_name) +) +FROM type_tests; + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 \ No newline at end of file From 7a5fe71893022359d04681d075f59cc61e47830e Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 11 Nov 2025 15:46:44 -0600 Subject: [PATCH 15/16] Add missing test file --- test/expected/permissions.out | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/expected/permissions.out diff --git a/test/expected/permissions.out b/test/expected/permissions.out new file mode 100644 index 0000000..f2f23df --- /dev/null +++ b/test/expected/permissions.out @@ -0,0 +1,23 @@ +\set ECHO none +1..20 +ok 1 - Permission denied trying to use type cat_tools.column +ok 2 - Permission denied trying to use type cat_tools.constraint_type +ok 3 - Permission denied trying to use type cat_tools.object_type +ok 4 - Permission denied trying to use type cat_tools.pg_all_foreign_keys +ok 5 - Permission denied trying to use type cat_tools.pg_class_v +ok 6 - Permission denied trying to use type cat_tools.pg_extension_v +ok 7 - Permission denied trying to use type cat_tools.procedure_type +ok 8 - Permission denied trying to use type cat_tools.relation_relkind +ok 9 - Permission denied trying to use type cat_tools.relation_type +ok 10 - Permission denied trying to use type cat_tools.routine_argument +ok 11 - Permission denied trying to use type cat_tools.routine_argument_mode +ok 12 - Permission denied trying to use type cat_tools.routine_parallel_safety +ok 13 - Permission denied trying to use type cat_tools.routine_proargmode +ok 14 - Permission denied trying to use type cat_tools.routine_prokind +ok 15 - Permission denied trying to use type cat_tools.routine_proparallel +ok 16 - Permission denied trying to use type cat_tools.routine_provolatile +ok 17 - Permission denied trying to use type cat_tools.routine_type +ok 18 - Permission denied trying to use type cat_tools.routine_volatility +ok 19 - Permission denied trying to run function relation__relkind +ok 20 - Permission denied trying to run function relation_type +# TRANSACTION INTENTIONALLY LEFT OPEN! From 21ec71b66bb07f69e53b5a55d59b1678fa3d9f7f Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Tue, 11 Nov 2025 15:47:09 -0600 Subject: [PATCH 16/16] Add automated test workflow --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0946c32..c89c1b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - pg: [17, 16, 15, 14, 13, 12, 11, 10, 9.6, 9.5, 9.4, 9.3, 9.2] + pg: [18, 17, 16, 15, 14, 13, 12, 11, 10] name: 🐘 PostgreSQL ${{ matrix.pg }} runs-on: ubuntu-latest container: pgxn/pgxn-tools @@ -14,4 +14,4 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 - name: Test on PostgreSQL ${{ matrix.pg }} - run: pg-build-test + run: make test PGUSER=postgres