diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c89c1b2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +name: CI +on: [push, pull_request] +jobs: + test: + strategy: + matrix: + pg: [18, 17, 16, 15, 14, 13, 12, 11, 10] + 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: make test PGUSER=postgres 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/.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 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..2238bd2 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 @@ -21,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 @@ -32,19 +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.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.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.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! @@ -58,4 +87,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/pgxntool/HISTORY.asc b/pgxntool/HISTORY.asc index b134482..9cb793b 100644 --- a/pgxntool/HISTORY.asc +++ b/pgxntool/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/pgxntool/base.mk b/pgxntool/base.mk index 0634f2e..a976ebb 100644 --- a/pgxntool/base.mk +++ b/pgxntool/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) 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 dda82d8..c397265 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -20,16 +20,9 @@ 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; --- 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,14 +34,50 @@ 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 * 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( @@ -60,7 +89,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( @@ -133,24 +162,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' ); @@ -163,6 +208,7 @@ BEGIN v_type := (regexp_matches( SQLERRM, 'function result type must be ([^ ]+) because of' ))[1]; sql := format( c_template + , function_suffix , arguments , v_type ); @@ -175,20 +221,422 @@ 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; - 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 - ); + $$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@ + +-- 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' -- 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' + , 'aggregate' + , 'window' +); +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' + , 'inout' + , 'variadic' + , 'table' +); +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' + , 'volatile' +); +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( + 'safe' + , 'restricted' + , 'unsafe' +); +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' -- 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 +$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' -- 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 +$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 cat_tools.routine_prokind' + , '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 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' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__argument_mode' + , 'proargmode cat_tools.routine_proargmode' + , '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 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' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__volatility' + , 'provolatile cat_tools.routine_provolatile' + , '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 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' +); + +SELECT __cat_tools.create_function( + 'cat_tools.routine__parallel_safety' + , 'proparallel cat_tools.routine_proparallel' + , 'cat_tools.routine_parallel_safety LANGUAGE sql STRICT IMMUTABLE PARALLEL SAFE' + , $body$ +SELECT CASE proparallel + 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' +); + +@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' +); - RETURN input_arg_types; +@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 + 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 + 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 result; END $body$ , 'cat_tools__usage' @@ -200,11 +648,36 @@ $body$ @generated@ SELECT __cat_tools.create_function( - 'cat_tools.function__arg_types_text' + 'cat_tools.routine__parse_arg_names' + , $$arguments text$$ + , $$text[] LANGUAGE plpgsql$$ + , $body$ +DECLARE + 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 + 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 result; +END +$body$ + , 'cat_tools__usage' + , 'Returns argument names for a function argument body as an array. Only + 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.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 @@ -215,6 +688,61 @@ $body$ @generated@ +SELECT __cat_tools.create_function( + 'cat_tools.routine__parse_arg_names_text' + , $$arguments text$$ + , $$text LANGUAGE sql$$ + , $body$ +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 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' , $$ @@ -225,7 +753,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' @@ -237,38 +765,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' -); -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' -); -COMMENT ON TYPE cat_tools.relation_relkind IS $$Valid values for `pg_class.relkind`$$; @generated@ @@ -282,6 +778,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! @@ -333,6 +831,7 @@ CREATE TYPE cat_tools.object_type AS ENUM( , 'access method' -- pg_am ); + @generated@ SELECT __cat_tools.create_function( @@ -443,6 +942,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[] ) @@ -613,64 +1114,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' -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' -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@ @@ -717,15 +1160,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 +1204,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 +1216,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 @@ -792,8 +1245,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 ) @@ -1166,6 +1619,49 @@ $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.' +); + +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( 'cat_tools.name__check' , 'name_to_check text' @@ -1318,7 +1814,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 +1954,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/function.out b/test/expected/function.out index 29dade1..7558a42 100644 --- a/test/expected/function.out +++ b/test/expected/function.out @@ -1,11 +1,51 @@ \set ECHO none -1..8 +1..40 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 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 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 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 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 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/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/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/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! 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/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/relation_type.out b/test/expected/relation_type.out index c8d4172..ad0e5e8 100644 --- a/test/expected/relation_type.out +++ b/test/expected/relation_type.out @@ -1,34 +1,26 @@ \set ECHO none -1..31 +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_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 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/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/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 754863c..eaef812 100644 --- a/test/expected/zzz_build.out +++ b/test/expected/zzz_build.out @@ -78,6 +78,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/setup.sql b/test/setup.sql index ae7b502..104205d 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -16,4 +16,22 @@ 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' diff --git a/test/sql/function.sql b/test/sql/function.sql index 198ae72..56c7b99 100644 --- a/test/sql/function.sql +++ b/test/sql/function.sql @@ -5,7 +5,8 @@ \set s cat_tools CREATE TEMP VIEW func_calls AS SELECT * FROM (VALUES - ('function__arg_types'::name, $$'x'$$::text) + ('routine__parse_arg_types'::name, $$'x'$$::text) + , ('routine__parse_arg_names'::name, $$'x'$$::text) , ('regprocedure'::name, $$'x', 'x'$$) ) v(fname, args) ; @@ -15,9 +16,17 @@ SELECT plan( 0 + (SELECT count(*)::int FROM func_calls) - + 4 -- function__arg_types() + + 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) + + 6 -- security definer checks (2 helpers + 4 callers) + + 1 -- current_user != session_user test ); SET LOCAL ROLE :no_use_role; @@ -35,32 +44,74 @@ 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.routine__parse_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$$) + :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.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 routine__parse_arg_names() with INOUT and OUT' +); + +SELECT is( + :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 routine__parse_arg_names() with just INOUT' ); +SELECT is( + :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 routine__parse_arg_names() with just OUT' +); + +SELECT is( + :s.routine__parse_arg_names($$anyelement, "char", pg_class, VARIADIC boolean[]$$) + , '{NULL,NULL,NULL,NULL}'::text[] + , '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( @@ -70,12 +121,179 @@ 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 , '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 + * 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 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 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 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 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[]); + \i test/pgxntool/finish.sql -- vi: expandtab ts=2 sw=2 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__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 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 diff --git a/test/sql/relation_type.sql b/test/sql/relation_type.sql index 69beaf1..2acf4dd 100644 --- a/test/sql/relation_type.sql +++ b/test/sql/relation_type.sql @@ -21,52 +21,26 @@ 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( (SELECT count(*)::int FROM kinds) - , 8 + , 10 , 'Verify count from kinds' ); 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()' ); -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'::regtype) - , ('relkind', 'text'::regtype) - ) 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::"char")::text, kind, format('SELECT cat_tools.relation_type(%L::"char")', 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