diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec1f57d..5b4391c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: test: strategy: matrix: - pg: [18, 17, 16, 15, 14, 13, 12, 11, 10] + pg: [18, 17, 16, 15, 14, 13, 12] name: 🐘 PostgreSQL ${{ matrix.pg }} runs-on: ubuntu-latest container: pgxn/pgxn-tools @@ -34,14 +34,6 @@ jobs: strategy: matrix: include: - - old_pg: "10" - new_pg: "11" - - old_pg: "10" - new_pg: "18" - - old_pg: "11" - new_pg: "12" - - old_pg: "11" - new_pg: "18" - old_pg: "12" new_pg: "13" - old_pg: "12" @@ -68,6 +60,9 @@ jobs: pg_isready -t 30 - name: Check out the repo uses: actions/checkout@v6 + - name: Set expected extension version + run: | + echo "EXPECTED_VERSION=$(sed -n "s/^default_version = '\(.*\)'$/\1/p" cat_tools.control)" >> $GITHUB_ENV - name: Install rsync run: apt-get install -y rsync - name: Install cat_tools into old cluster @@ -104,7 +99,7 @@ jobs: run: | VERSION=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") echo "Extension version: ${VERSION:-}" - echo "$VERSION" | grep -q "0.2.2" + echo "$VERSION" | grep -qF "$EXPECTED_VERSION" - name: Run test suite on upgraded cluster run: make test # TODO: also test ALTER EXTENSION cat_tools UPDATE here, once the @@ -115,17 +110,12 @@ jobs: extension-update-test: strategy: matrix: - # Restricted to PG10 only because the pre-0.2.2 install scripts use - # unqualified SELECT * in views over pg_attribute/pg_constraint: - # - PG11 added attmissingval (pseudo-type anyarray) to pg_attribute, - # so cat_tools--0.2.0.sql and cat_tools--0.2.1.sql fail on PG11+ - # with "column attmissingval has pseudo-type anyarray". - # - PG12 made the `oid` system column visible in SELECT *, causing - # "column oid specified more than once" on PG12+. - # PG10 is the only version where both old scripts install cleanly. - # TODO: when a future version's install script works on PG11+, expand - # this matrix and add tests for the upgrade path from 0.2.2 onwards. - pg: [10] + # PG12 is the oldest version where the 0.2.2β†’0.3.0 update works. + # PG11 (and PG10) cannot run `ALTER TYPE ... ADD VALUE` in extension + # update scripts; this restriction was lifted in PG12. + # (PG10 is dropped; the 0.2.0/0.2.1 update paths required PG10 and + # can no longer be tested.) + pg: [12] name: ⬆️ Extension update test on PostgreSQL ${{ matrix.pg }} runs-on: ubuntu-latest container: pgxn/pgxn-tools @@ -136,23 +126,19 @@ jobs: uses: actions/checkout@v6 - name: Install rsync run: apt-get install -y rsync + - name: Set expected extension version + run: | + echo "EXPECTED_VERSION=$(sed -n "s/^default_version = '\(.*\)'$/\1/p" cat_tools.control)" >> $GITHUB_ENV - name: Install cat_tools (all versions) run: make install - - name: Test upgrade from 0.2.0 (direct 0.2.0β†’0.2.2 path) - run: | - psql -c "CREATE EXTENSION cat_tools VERSION '0.2.0'" - psql -c "ALTER EXTENSION cat_tools UPDATE" - VERSION=$(psql -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") - echo "Version after 0.2.0 upgrade: ${VERSION:-}" - echo "$VERSION" | grep -q "0.2.2" - - name: Test upgrade from 0.2.1 (0.2.1β†’0.2.2 path) + - name: Test update from 0.2.2 (0.2.2β†’current path) run: | - createdb cat_tools_from_021 - psql -d cat_tools_from_021 -c "CREATE EXTENSION cat_tools VERSION '0.2.1'" - psql -d cat_tools_from_021 -c "ALTER EXTENSION cat_tools UPDATE" - VERSION=$(psql -d cat_tools_from_021 -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") - echo "Version after 0.2.1 upgrade: ${VERSION:-}" - echo "$VERSION" | grep -q "0.2.2" + createdb cat_tools_from_022 + psql -d cat_tools_from_022 -c "CREATE EXTENSION cat_tools VERSION '0.2.2'" + psql -d cat_tools_from_022 -c "ALTER EXTENSION cat_tools UPDATE" + VERSION=$(psql -d cat_tools_from_022 -tAc "SELECT extversion FROM pg_extension WHERE extname = 'cat_tools'") + echo "Version after 0.2.2 update: ${VERSION:-}" + echo "$VERSION" | grep -qF "$EXPECTED_VERSION" - name: Run test suite on updated extension run: make test diff --git a/.gitignore b/.gitignore index 6fc6141..8dfbc48 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ control.mk # built targets # Note: Version-specific files (sql/*--*.sql) are now tracked in git and should be committed -# test targets -/test/.build/ - # Test artifacts results/ regression.diffs diff --git a/CLAUDE.md b/CLAUDE.md index 44f3caa..04d53d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,13 @@ When fixing a bug, add a comment at the fix site explaining what the bug was and **Always open PRs against the main repo** (`Postgres-Extensions/cat_tools`), not a fork. +## Terminology + +- **Extension update**: moving from one cat_tools version to another (e.g. `ALTER EXTENSION cat_tools UPDATE`). Always say "update" for this. +- **PostgreSQL upgrade**: upgrading a PostgreSQL cluster to a newer major version (e.g. `pg_upgrade`, `pg_upgradecluster`). Always say "upgrade" for this. + +Never use "upgrade" to describe an extension version change, and never use "update" to describe a PostgreSQL cluster version change. + ## SQL file conventions Rules for what to track in git: @@ -21,26 +28,28 @@ Rules for what to track in git: 0. If a `.sql.in` file exists, track the `.sql.in` and **not** the corresponding `.sql`. 1. If no `.sql.in` exists, track the `.sql` directly (e.g. historical pre-0.2.0 files). 2. Version-specific install scripts (e.g. `sql/cat_tools--0.2.2.sql.in`) MUST be tracked. -3. Upgrade scripts (e.g. `sql/cat_tools--0.2.1--0.2.2.sql.in`) MUST be tracked. +3. Update scripts (e.g. `sql/cat_tools--0.2.1--0.2.2.sql.in`) MUST be tracked. 4. The current version'''s install script (e.g. `sql/cat_tools--0.2.2.sql.in`) is generated by `make` from `sql/cat_tools.sql.in`, but MUST still be tracked (rule 2 applies). 5. Version-specific files MUST NEVER be edited manually β€” always edit `sql/cat_tools.sql.in` and regenerate. -## CI: extension-update-test matrix +## CI: PostgreSQL version support + +**Policy:** Never support a fresh install on any PostgreSQL version where the extension +update path is known to be broken β€” a version that cannot be updated to is not truly +supported. -The `extension-update-test` job in `.github/workflows/ci.yml` is currently restricted to -`pg: [10]` because that is the only PostgreSQL version where the pre-0.2.2 install scripts -install cleanly: -- PG 11 added `attmissingval` (pseudo-type `anyarray`) to `pg_attribute`; the old `SELECT *` - in `0.2.0`/`0.2.1` tries to include it directly, failing with "column attmissingval has - pseudo-type anyarray". -- PG 12+ exposed the `oid` system column in `SELECT *`, breaking `0.2.0`/`0.2.1` with - "column oid specified more than once". +Both PG10 and PG11 are dropped as of 0.3.0. The `ALTER TYPE ... ADD VALUE` statements in +the update script cannot run inside an extension update script on PG11 or earlier +(PROCESS_UTILITY_QUERY context); this restriction was lifted in PG12. Because a version +that cannot be updated to is not truly supported, PG10 and PG11 support is dropped +entirely. cat_tools 0.3.0 supports PG12+. -**When working on a new version:** review and expand this matrix. The new version's install -script may support more PG versions, enabling testing of the upgrade path from older -cat_tools versions on newer PostgreSQL. +The `extension-update-test` job tests `pg: [12]` β€” PG12 is the oldest version where the +0.2.2β†’0.3.0 update works. PG11 (and PG10) cannot run `ALTER TYPE ... ADD VALUE` in +extension update scripts; this restriction was lifted in PG12. The 0.2.0/0.2.1 update +paths required PG10 and can no longer be tested. ## Code Style diff --git a/HISTORY.asc b/HISTORY.asc index b25e350..e080e21 100644 --- a/HISTORY.asc +++ b/HISTORY.asc @@ -1,7 +1,25 @@ +0.3.0 +----- +New functions and types for working with routines and partitioned relations. + +### PostgreSQL Version Support + +**PostgreSQL 10 and 11 are no longer supported** as of this release. The 0.2.2β†’0.3.0 +update script adds values to existing enum types (`ALTER TYPE ... ADD VALUE`), which +cannot run inside an extension update script on PG11 or earlier (a restriction lifted +in PG12). Because a version that cannot be updated to is not truly supported, PG10 and +PG11 support is dropped entirely. + +cat_tools 0.3.0 supports **PostgreSQL 12 through 18+**. + +### Changes + +// TODO + 0.2.2 ----- Compatibility release: fixes broken installs on PostgreSQL 11 and 12+, and -provides an upgrade path from 0.2.0 and 0.2.1. +provides an update path from 0.2.0 and 0.2.1. ### PostgreSQL Version Support @@ -15,24 +33,24 @@ It fails on newer versions due to catalog schema changes: * **PG 12**: System catalog `oid` columns became visible as regular columns. Views using `SELECT c.*` alongside an explicit `c.oid` alias produce duplicate column names. -cat_tools 0.2.2 installs and upgrades correctly on **PostgreSQL 9.2 through 18+** +cat_tools 0.2.2 installs and updates correctly on **PostgreSQL 9.2 through 18+** (all currently supported versions). -### Upgrade Path +### Update Path -**You must upgrade cat_tools to 0.2.2 before upgrading PostgreSQL to version 11 or -later.** Upgrade via `ALTER EXTENSION cat_tools UPDATE`. +**You must update cat_tools to 0.2.2 before upgrading PostgreSQL to version 11 or +later.** Update via `ALTER EXTENSION cat_tools UPDATE`. -### Upgrade Warning: Objects Depending on `cat_tools.column` +### Update Warning: Objects Depending on `cat_tools.column` `cat_tools.column` in 0.2.0 and 0.2.1 had extra columns: an unqualified `SELECT *` across a `LEFT JOIN pg_constraint` accidentally pulled in all `pg_constraint` columns -(`conname`, `contype`, etc.). The 0.2.2 upgrade fixes the column list by dropping and +(`conname`, `contype`, etc.). The 0.2.2 update fixes the column list by dropping and recreating `cat_tools.column` (and its underlying `_cat_tools.column`). **If you have created any views, functions, or other objects that depend on -`cat_tools.column`, you must drop them before upgrading and recreate them -afterward.** The upgrade will fail with an error if any such dependent objects +`cat_tools.column`, you must drop them before updating and recreate them +afterward.** The update will fail with an error if any such dependent objects exist β€” this is intentional, to avoid silently breaking user-defined objects. After dropping your dependent objects, run `ALTER EXTENSION cat_tools UPDATE` again. @@ -56,17 +74,17 @@ to PostgreSQL 12 or later.** ### Changes -* `sql/cat_tools--0.1.4--0.1.5.sql` was empty; added `-- empty upgrade` placeholder so - PostgreSQL accepts it as a valid (no-op) upgrade script. +* `sql/cat_tools--0.1.4--0.1.5.sql` was empty; added `-- empty update` placeholder so + PostgreSQL accepts it as a valid (no-op) update script. * `cat_tools.column` now exposes `attmissingval` as `text[]` (cast from `anyarray` on PG 11+, or `NULL::text[]` on older versions). Any `SELECT *` on `cat_tools.column` will now include this column. * Views rebuilt using `omit_column()` to enumerate columns explicitly, avoiding both the `anyarray` and duplicate-`oid` problems. -* A direct `0.2.0 β†’ 0.2.2` upgrade path is provided (`cat_tools--0.2.0--0.2.2.sql`), +* A direct `0.2.0 β†’ 0.2.2` update path is provided (`cat_tools--0.2.0--0.2.2.sql`), which also applies all 0.2.1 function additions in a single step. * `GRANT SELECT ON cat_tools.pg_extension_v TO cat_tools__usage` is now applied on the - upgrade path from 0.2.0 (it was absent in 0.2.0 and only added via the 0.2.1 upgrade). + update path from 0.2.0 (it was absent in 0.2.0 and only added via the 0.2.1 update). * (Re-release) `_cat_tools.pg_class_v` now explicitly omits `relhasoids` (removed in PG12) and `relhaspkey` (removed in PG17) to prevent `pg_upgrade` failures. * (Re-release) `_cat_tools.pg_attribute_v` now explicitly omits `attcacheoff` (removed in @@ -74,7 +92,7 @@ to PostgreSQL 12 or later.** 0.2.1 ----- -Fix significant problems with a previous upgrade script. +Fix significant problems with a previous update script. Add pg_extension_v and related functions. @@ -114,7 +132,7 @@ objects would cascede to the extension itself. 0.1.2 ----- -=## BUGFIX: Install upgrade script +=## BUGFIX: Install update script 0.1.1 ----- diff --git a/LICENSE b/LICENSE index de25054..f1e6077 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Jim Nasby +Copyright (c) 2016-2026 Jim Nasby Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/META.in.json b/META.in.json index 6f07609..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.2", + "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.2", + "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 2bd6367..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.2", + "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.2", + "version": "0.3.0", "X_comment": "Optional: \"abstract\": Description of the extension.", "abstract": "Tools for interfacing with the catalog", diff --git a/Makefile b/Makefile index 2a551ee..59edfdd 100644 --- a/Makefile +++ b/Makefile @@ -17,16 +17,14 @@ upgrade_scripts_out = $(subst sql/,$B/,$(subst .sql.in,.sql,$(wildcard sql/*--*- # Pre-built historical install scripts (no .sql.in source available) DATA += sql/cat_tools--0.1.0.sql sql/cat_tools--0.1.3.sql sql/cat_tools--0.1.4.sql sql/cat_tools--0.1.5.sql -# Generated historical install scripts (built from .sql.in source). +# Generated install scripts (built from .sql.in source). # Exclude EXTENSION_VERSION_FILES (managed by control.mk) and upgrade scripts # ($(upgrade_scripts_out), already handled by base.mk) to avoid duplicates. DATA += $(filter-out $(EXTENSION_VERSION_FILES) $(upgrade_scripts_out), $(versioned_out)) all: $B/cat_tools.sql $(versioned_out) installcheck: $B/cat_tools.sql $(versioned_out) -EXTRA_CLEAN += $B/cat_tools.sql $(filter-out $(EXTENSION_VERSION_FILES), $(versioned_out)) -# Also clean the generated .sql.in for the current version -EXTRA_CLEAN += $(EXTENSION_VERSION_FILES:.sql=.sql.in) +EXTRA_CLEAN += $B/cat_tools.sql $(versioned_out) # Temporary ugly hack for 9.x β€” remove these two blocks when 9.x support is dropped. # $@ is deferred via = and expands to the target name at recipe time. @@ -57,6 +55,7 @@ define _apply_version_seds {print}' $@.tmp > $@.tmp2 && mv $@.tmp2 $@.tmp endef + # TODO: refactor the version stuff into a function # # This initially creates $@.tmp before moving it into place atomically. That's @@ -88,42 +87,6 @@ $(EXTENSION_VERSION_FILES): $(EXTENSION_VERSION_FILES:.sql=.sql.in) pgxntool/saf $(_apply_version_seds) mv $@.tmp $@ -# Support for upgrade test -# -# TODO: Instead of all of this stuff figure out how to pass something to -# pg_regress that will alter the behavior of the test instead. -TEST_BUILD_DIR = test/.build -testdeps: $(TEST_BUILD_DIR)/dep.mk $(TEST_BUILD_DIR)/active.sql --include $(TEST_BUILD_DIR)/dep.mk - -# Ensure dep.mk exists. -$(TEST_BUILD_DIR)/dep.mk: $(TEST_BUILD_DIR) - echo 'TEST_LOAD_SOURCE = new' > $(TEST_BUILD_DIR)/dep.mk - -.PHONY: set-test-new -set-test-new: $(TEST_BUILD_DIR) - echo 'TEST_LOAD_SOURCE = new' > $(TEST_BUILD_DIR)/dep.mk - -.PHONY: test-upgrade -set-test-upgrade: $(TEST_BUILD_DIR) - echo 'TEST_LOAD_SOURCE = upgrade' > $(TEST_BUILD_DIR)/dep.mk - - -$(TEST_BUILD_DIR)/active.sql: $(TEST_BUILD_DIR)/dep.mk $(TEST_BUILD_DIR)/$(TEST_LOAD_SOURCE).sql - ln -sf $(TEST_LOAD_SOURCE).sql $@ - -$(TEST_BUILD_DIR)/upgrade.sql: test/load_upgrade.sql $(TEST_BUILD_DIR) old_version - (echo @generated@ && cat $< && echo @generated@) | sed -e 's#@generated@#-- GENERATED FILE! DO NOT EDIT! See $<#' > $@.tmp - mv $@.tmp $@ - -$(TEST_BUILD_DIR)/new.sql: test/load_new.sql $(TEST_BUILD_DIR) - (echo @generated@ && cat $< && echo @generated@) | sed -e 's#@generated@#-- GENERATED FILE! DO NOT EDIT! See $<#' > $@.tmp - mv $@.tmp $@ - -# TODO: figure out vpath -EXTRA_CLEAN += $(TEST_BUILD_DIR)/ -$(TEST_BUILD_DIR): - [ -d $@ ] || mkdir -p $@ .PHONY: old_version old_version: $(DESTDIR)$(datadir)/extension/cat_tools--0.2.0.sql diff --git a/README.asc b/README.asc index 6f67413..e18299a 100644 --- a/README.asc +++ b/README.asc @@ -16,9 +16,6 @@ it may break when you upgrade PostgreSQL. == 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 @@ -31,10 +28,14 @@ Works on Postgres 9.3 and above. * `constraint_type` - Types of constraints (`domain constraint` or `table_constraint`) * `relation_type` - Types of objects stored in `pg_class` * `relation_relkind` - Valid values for `pg_class.relkind` +* `routine_type` - Types of routines stored in `pg_proc` +* `routine_argument_mode` - Argument modes for function/procedure parameters +* `routine_volatility` - Volatility levels for functions/procedures` +* `routine_parallel_safety` - Parallel safety levels for functions/procedures +* `routine_argument` - Detailed information about a single function/procedure argument -== Functions +== General Introspection Functions -* `currval(table, column)` - Returns current value for a sequence owned by a column * `enum_range(regtype)` - Returns valid values for an ENUM as an array * `enum_range_srf(regtype)` - Returns valid values for an ENUM as a recordset * `name__check(text)` - Throws an error if input would be truncated when cast to name @@ -42,19 +43,71 @@ Works on Postgres 9.3 and above. * `pg_extension__get(extension_name name)` - Returns pg_extension_v row for an extension * `extension__schemas(extension_names text/name[])` - Returns the schemas for the requested functions * `extension__schemas_unique(extension_names text/name[])` - Returns a unique array of schemas -* `function__arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments -* `function__arg_types_text(arguments)` - Version of `function__arg_types` that returns text +* `pg_attribute__get(relation regclass, column_name name)` - Returns `pg_attribute` row for a column; throws error if column doesn't exist +* `relation__column_names(relation regclass)` - Returns an array of quoted column names for a relation in ordinal position order +* `relation__is_catalog(relation regclass)` - Returns true if the relation is in the `pg_catalog` schema +* `relation__is_temp(relation regclass)` - Returns true if the relation is a temporary table (lives in a schema that starts with 'pg_temp') + +== Object Type Query Functions + +Functions for working with `object_type` values β€” the enum of descriptive names for every type of Postgres object (table, index, role, etc). + * `object__catalog(object_type)` - Returns catalog table that is used to store `object_type` objects * `object__reg_type(object_catalog)` - Returns the "reg" pseudotype (ie: regclass) associated with a system catalog (ie: pg_class) -* `regprocedure(function_name, arguments)` - Returns regprocedure for function_name and it's full set of arguments -* `relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `relation_type` -* `relation__relkind(relation_type)` - Mapping from `relation_type` to a `pg_class.relkind` value +* `object__reg_type_catalog(object_identifier_type regtype)` - Returns the system catalog that stores a particular object identifier type (inverse of `object__reg_type`) +* `object__address_classid(object_type)` - Returns the classid used by the `pg_*_object*()` functions for an object_type +* `objects__shared()` - Returns an array of object types that are shared catalog objects (as opposed to per-database) +* `objects__shared_srf()` - Set returning version of `objects__shared` +* `object__is_shared(object_type)` - Returns true if object_type is a shared object +* `objects__address_unsupported()` - Returns array of object types not supported by `pg_get_object_address()` +* `objects__address_unsupported_srf()` - Set returning version of `objects__address_unsupported` +* `object__is_address_unsupported(object_type)` - Returns true if object type is not supported by `pg_get_object_address()` + +== Sequence Functions + +* `get_serial_sequence(table_name text, column_name text)` - Returns sequence associated with a column; unlike `pg_get_serial_sequence`, throws an exception if no sequence exists +* `currval(table_name text, column_name text)` - Alias for `sequence__last` +* `nextval(table_name text, column_name text)` - Alias for `sequence__next` +* `setval(table_name text, column_name text, new_value bigint, has_been_used boolean DEFAULT true)` - Sets sequence value for a column; if `has_been_used` is true, sequence will return `new_value + 1` next +* `sequence__last(table_name text, column_name text)` - Returns the last value assigned to a column's sequence +* `sequence__next(table_name text, column_name text)` - Returns the next sequence value and advances the sequence +* `sequence__set_last(table_name text, column_name text, last_value bigint)` - Sets last used value; sequence will return `last_value + 1` next +* `sequence__set_next(table_name text, column_name text, next_value bigint)` - Sets the next value the sequence will return + +== Routine / Function / Procedure Functions + +* `routine__parse_arg_types(arguments)` - Accepts full function argument string and returns regtype[] of IN/INOUT arguments +* `routine__parse_arg_types_text(arguments)` - Version of `routine__parse_arg_types` that returns text +* `routine__parse_arg_names(arguments)` - Accepts full function argument string and returns text[] of IN/INOUT argument names +* `routine__parse_arg_names_text(arguments)` - Version of `routine__parse_arg_names` that returns text +* `routine__arg_types(regprocedure)` - Returns argument types for a function as regtype[] +* `routine__arg_types_text(regprocedure)` - Version of `routine__arg_types` that returns text +* `routine__arg_names(regprocedure)` - Returns argument names for a function as text[] +* `routine__arg_names_text(regprocedure)` - Version of `routine__arg_names` that returns text +* `regprocedure(routine_name, arguments)` - Returns regprocedure for routine_name and it's full set of arguments + +== Trigger Functions + * `trigger__args_as_text(text)` - Converts the arguments for a trigger function (as returned by `trigger__parse()`) to text (for backwards compatibility). * `trigger__get_oid(trigger_table, trigger_name)` - oid of a trigger. Throws error if trigger doesn't exits. * `trigger__get_oid__loose(trigger_table, trigger_name)` - oid of a trigger. Does _not_ throw error if trigger doesn't exits. * `trigger__parse(trigger oid)` - Returns information about a trigger * `trigger__parse(table_name regclass, trigger_name text)` - Returns information about a trigger +== Mapping Functions + +* `relation__kind(relkind)` - Mapping from `pg_class.relkind` to a `relation_type` +* `relation__relkind(relation_type)` - Mapping from `relation_type` to a `pg_class.relkind` value +* `routine__type(prokind)` - Mapping from `pg_proc.prokind` to `routine_type` +* `routine__argument_mode(mode)` - Mapping from `pg_proc.proargmodes` element to `routine_argument_mode` +* `routine__volatility(volatile)` - Mapping from `pg_proc.provolatile` to `routine_volatility` +* `routine__parallel_safety(parallel)` - Mapping from `pg_proc.proparallel` to `routine_parallel_safety` + +== Deprecated Functions + +* `function__arg_types(arguments)` - DEPRECATED: Use `routine__parse_arg_types` instead +* `function__arg_types_text(arguments)` - DEPRECATED: Use `routine__parse_arg_types_text` instead + == Views WARNING: These views may eventually move into a separate extension! @@ -68,4 +121,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) 2026 Jim Nasby . diff --git a/cat_tools.control b/cat_tools.control index 6a6297a..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.2' +default_version = '0.3.0' relocatable = false schema = 'cat_tools' diff --git a/control.mk b/control.mk index 9ac83c2..26db2f6 100644 --- a/control.mk +++ b/control.mk @@ -1,6 +1,6 @@ EXTENSIONS += cat_tools EXTENSION_SQL_FILES += sql/cat_tools.sql -EXTENSION_cat_tools_VERSION := 0.2.2 +EXTENSION_cat_tools_VERSION := 0.3.0 EXTENSION_cat_tools_VERSION_FILE = sql/cat_tools--$(EXTENSION_cat_tools_VERSION).sql EXTENSION_VERSION_FILES += $(EXTENSION_cat_tools_VERSION_FILE) $(EXTENSION_cat_tools_VERSION_FILE): sql/cat_tools.sql cat_tools.control diff --git a/sql/cat_tools--0.2.2--0.3.0.sql.in b/sql/cat_tools--0.2.2--0.3.0.sql.in new file mode 100644 index 0000000..a735d19 --- /dev/null +++ b/sql/cat_tools--0.2.2--0.3.0.sql.in @@ -0,0 +1,848 @@ +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$; + + +-- NOTE: Changes already applied in 0.2.2: +-- ALTER DEFAULT PRIVILEGES, pg_class_v fix, pg_attribute_v fix, pg_extension_v fix, +-- pg_extension__get recreation, cat_tools.column recreation are all skipped here. +-- They are part of the 0.2.1β†’0.2.2 upgrade. + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L165 +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 + c_template CONSTANT text := $fmt$CREATE FUNCTION pg_temp.cat_tools__function__%s__temp_function( + %s + ) RETURNS %s LANGUAGE plpgsql AS 'BEGIN RETURN; END' + $fmt$; + + temp_proc pg_catalog.regprocedure; + sql text; +BEGIN + IF current_user != session_user THEN + RAISE EXCEPTION USING + ERRCODE = '28000' + , 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' + ); + DECLARE + v_type pg_catalog.regtype; + BEGIN + EXECUTE sql; + EXCEPTION WHEN invalid_function_definition THEN + v_type := (regexp_matches( SQLERRM, 'function result type must be ([^ ]+) because of' ))[1]; + sql := format( + c_template + , function_suffix + , arguments + , v_type + ); + EXECUTE sql; + END; + + EXECUTE format( + $$SELECT 'pg_temp.cat_tools__function__%s__temp_function'::pg_catalog.regproc::pg_catalog.regprocedure$$ + , function_suffix + ) INTO temp_proc; + + RETURN temp_proc; +END +$body$; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L233 +CREATE FUNCTION _cat_tools.function__drop_temp( + p_regprocedure pg_catalog.regprocedure + , api_function_name text +) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + IF current_user != session_user THEN + RAISE EXCEPTION USING + ERRCODE = '28000' + , 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$; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L254 +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; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L280 +ALTER TYPE cat_tools.relation_type ADD VALUE 'partitioned table'; +ALTER TYPE cat_tools.relation_type ADD VALUE 'partitioned index'; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L294 +ALTER TYPE cat_tools.relation_relkind ADD VALUE 'p'; +ALTER TYPE cat_tools.relation_relkind ADD VALUE 'I'; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L299 +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$$; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L372 +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 ' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L394 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L416 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L425 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L434 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L459 +CREATE CAST ("char" AS cat_tools.routine_prokind) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_proargmode) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_provolatile) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_proparallel) WITH INOUT AS IMPLICIT; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L463 +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__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__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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L502 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L519 +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 + CASE + WHEN pronargs > 0 THEN + array_fill(NULL::text, ARRAY[pronargs]) + ELSE + '{}'::text[] + END + WHEN proargmodes IS NULL THEN + array( + SELECT CASE WHEN name = '' THEN NULL ELSE name END + FROM unnest(proargnames) AS name + ) + ELSE + 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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L561 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L574 +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' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L587 +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); + PERFORM _cat_tools.function__drop_temp(c_temp_proc, 'cat_tools.routine__parse_arg_types'); + RETURN result; +END +$body$ + , 'cat_tools__usage' + , 'Returns argument types for a function argument body as an array. Unlike a + normal regprocedure cast, this function accepts anything that is valid when + defining a function.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L612 +SELECT __cat_tools.create_function( + '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); + 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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L637 +SELECT __cat_tools.create_function( + 'cat_tools.routine__parse_arg_types_text' + , $$arguments text$$ + , $$text LANGUAGE sql$$ + , $body$ +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 + normal regprocedure cast, this function accepts anything that is valid when + defining a function.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L653 +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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L670 +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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L689 +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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L708 +SELECT __cat_tools.create_function( + 'cat_tools.regprocedure' + , $$ + function_name text + , arguments text$$ + , $$pg_catalog.regprocedure LANGUAGE sql$$ + , $body$ +SELECT format( + '%s(%s)' + , $1 + , cat_tools.routine__parse_arg_types_text($2) +)::pg_catalog.regprocedure +$body$ + , 'cat_tools__usage' + , 'Returns a regprocedure for a given function name and arguments. Unlike a + normal regprocedure cast, arguments can contain anything that is valid when + defining a function.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L742 +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'; + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L890 +SELECT __cat_tools.create_function( + 'cat_tools.object__catalog' + , 'object_type cat_tools.object_type' + , 'pg_catalog.regclass LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT ( + 'pg_catalog.' + || CASE + WHEN object_type = ANY( array[ + 'table' + , 'index' + , 'sequence' + , 'toast table' + , 'view' + , '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[] ) + THEN 'pg_constraint' + WHEN object_type = ANY( '{aggregate,function}'::cat_tools.object_type[] ) + THEN 'pg_proc' + WHEN object_type::text LIKE '% column' + THEN 'pg_attribute' + ELSE CASE object_type + WHEN 'default value' THEN 'pg_attrdef' + WHEN 'large object' THEN 'pg_largeobject' + WHEN 'operator class' THEN 'pg_opclass' + WHEN 'operator family' THEN 'pg_opfamily' + WHEN 'operator of access method' THEN 'pg_amop' + WHEN 'function of access method' THEN 'pg_amproc' + WHEN 'rule' THEN 'pg_rewrite' + WHEN 'schema' THEN 'pg_namespace' + WHEN 'text search parser' THEN 'pg_ts_parser' + WHEN 'text search dictionary' THEN 'pg_ts_dict' + WHEN 'text search template' THEN 'pg_ts_template' + WHEN 'text search configuration' THEN 'pg_ts_config' + WHEN 'role' THEN 'pg_authid' + WHEN 'foreign-data wrapper' THEN 'pg_foreign_data_wrapper' + WHEN 'server' THEN 'pg_foreign_server' + WHEN 'user mapping' THEN 'pg_user_mapping' + WHEN 'default acl' THEN 'pg_default_acl' + WHEN 'event trigger' THEN 'pg_event_trigger' + WHEN 'access method' THEN 'pg_am' + ELSE 'pg_' || object_type::text + END + END + )::pg_catalog.regclass +$body$ + , 'cat_tools__usage' + , 'Returns catalog table that is used to store objects' +); + + + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L1202 +SELECT __cat_tools.create_function( + '_cat_tools._pg_sv_column_array' + , 'OID, SMALLINT[]' + , 'NAME[] LANGUAGE sql STABLE' + , $$ + SELECT ARRAY( + SELECT a.attname + 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 + ) +$$ +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L1582 +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').$$ +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L1597 +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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L1610 +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.' +); + +-- https://github.com/jnasbyupgrade/cat_tools/blob/new_functions/sql/cat_tools.sql.in#L1702 +SELECT __cat_tools.create_function( + 'cat_tools.trigger__parse' + , $$ + trigger_oid oid + , OUT trigger_table regclass + , OUT timing text + , OUT events text[] + , OUT defer text + , OUT row_statement text + , OUT when_clause text + , OUT trigger_function regprocedure + , OUT function_arguments text[] +$$ + , $$record STABLE LANGUAGE plpgsql$$ + , $body$ +DECLARE + r_trigger pg_catalog.pg_trigger; + v_triggerdef text; + v_create_stanza text; + v_on_clause text; + v_execute_clause text; + + v_work text; + v_array text[]; +BEGIN + /* + * Do this first to make sure trigger exists. + * + * TODO: After we no longer support < 9.6, test v_triggerdef for NULL instead + * using the extra block here. + */ + BEGIN + SELECT * INTO STRICT r_trigger FROM pg_catalog.pg_trigger WHERE oid = trigger_oid; + EXCEPTION WHEN no_data_found THEN + RAISE EXCEPTION 'trigger with OID % does not exist', trigger_oid + USING errcode = 'undefined_object' -- 42704 + ; + END; + trigger_table := r_trigger.tgrelid; + trigger_function := r_trigger.tgfoid; + + v_triggerdef := pg_catalog.pg_get_triggerdef(trigger_oid, true); + + v_create_stanza := format( + 'CREATE %sTRIGGER %I ' + , CASE WHEN r_trigger.tgconstraint=0 THEN '' ELSE 'CONSTRAINT ' END + , r_trigger.tgname + ); + -- Strip CREATE [CONSTRAINT] TRIGGER ... off + v_work := replace( v_triggerdef, v_create_stanza, '' ); + + -- Get BEFORE | AFTER | INSTEAD OF + timing := split_part( v_work, ' ', 1 ); + timing := timing || CASE timing WHEN 'INSTEAD' THEN ' OF' ELSE '' END; + + -- Strip off timing clause + v_work := replace( v_work, timing || ' ', '' ); + + -- Get array of events (INSERT, UPDATE [OF column, column], DELETE, TRUNCATE) + v_on_clause := ' ON ' || r_trigger.tgrelid::pg_catalog.regclass || ' '; + v_array := regexp_split_to_array( v_work, v_on_clause ); + events := string_to_array( v_array[1], ' OR ' ); + -- Get everything after ON table_name + v_work := v_array[2]; + RAISE DEBUG 'v_work "%"', v_work; + + -- Strip off FROM referenced_table if we have it + IF r_trigger.tgconstrrelid<>0 THEN + v_work := replace( + v_work + , 'FROM ' || r_trigger.tgconstrrelid::pg_catalog.regclass || ' ' + , '' + ); + END IF; + RAISE DEBUG 'v_work "%"', v_work; + + -- Get function arguments + -- Use a generic pattern rather than the regproc name, since pg_get_triggerdef + -- may render temp functions as "pg_temp.f" while ::regproc gives "pg_temp_N.f". + v_execute_clause := E' EXECUTE (FUNCTION|PROCEDURE) \\S+\\('; + v_array := regexp_split_to_array( v_work, v_execute_clause ); + EXECUTE format( + CASE WHEN coalesce( rtrim( v_array[2], ')' ), '' ) = '' + THEN 'SELECT ARRAY[]::text[]' + ELSE 'SELECT array[ %s ]' + END + , rtrim( v_array[2], ')' ) -- Yank trailing ) + ) + INTO function_arguments + ; + RAISE DEBUG 'v_array[2] "%"', v_array[2]; + -- Get everything prior to EXECUTE PROCEDURE ... + v_work := v_array[1]; + RAISE DEBUG 'v_work "%"', v_work; + + row_statement := (regexp_matches( v_work, 'FOR EACH (ROW|STATEMENT)' ))[1]; + + -- Get [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ] + v_array := regexp_split_to_array( v_work, 'FOR EACH (ROW|STATEMENT)' ); + RAISE DEBUG 'v_work = "%", v_array = "%"', v_work, v_array; + defer := rtrim(v_array[1]); + + IF r_trigger.tgqual IS NOT NULL THEN + when_clause := rtrim( + (regexp_split_to_array( v_array[2], E' WHEN \\(' ))[2] + , ')' + ); + END IF; + + RAISE DEBUG +$$v_create_stanza = "%" + v_on_clause = "%" + v_execute_clause = "%"$$ + , v_create_stanza + , v_on_clause + , v_execute_clause + ; + + RETURN; +END +$body$ + , 'cat_tools__usage' + , 'Provide details about a trigger.' +); + + +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--0.3.0.sql.in b/sql/cat_tools--0.3.0.sql.in new file mode 100644 index 0000000..ca8f4a9 --- /dev/null +++ b/sql/cat_tools--0.3.0.sql.in @@ -0,0 +1,1958 @@ +@generated@ + +SET LOCAL client_min_messages = WARNING; + +DO $$ +BEGIN + CREATE ROLE cat_tools__usage NOLOGIN; +EXCEPTION WHEN duplicate_object THEN + NULL; +END +$$; + +/* + * NOTE: All pg_temp objects must be dropped at the end of the script! + * Otherwise the eventual DROP CASCADE of pg_temp when the session ends will + * also drop the extension! Instead of risking problems, create our own + * "temporary" schema instead. + */ +CREATE SCHEMA __cat_tools; + +-- Schema already created via CREATE EXTENSION +GRANT USAGE ON SCHEMA cat_tools TO cat_tools__usage; +ALTER DEFAULT PRIVILEGES IN SCHEMA cat_tools GRANT USAGE ON TYPES TO cat_tools__usage; +CREATE SCHEMA _cat_tools; + +@generated@ + +CREATE FUNCTION __cat_tools.exec( + sql text +) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + RAISE DEBUG 'sql = %', sql; + EXECUTE sql; +END +$body$; + +-- See also test/setup.sql +CREATE FUNCTION __cat_tools.omit_column( + rel text + , omit name[] DEFAULT array['oid'] +) RETURNS text LANGUAGE sql STABLE 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 != ALL( omit ) + ORDER BY attnum + ) + , ', ' +) +$body$; + +@generated@ + +/* + * Starting in PG12 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', array['oid', 'relhasoids', 'relhaspkey']) +)); +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.routine__parse_arg_types_text(text +) RETURNS text LANGUAGE sql AS 'SELECT $1'; + +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.routine__parse_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$ + ; + +@generated@ + +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$; + +@generated@ + +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 + /* + * 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 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' + ); + --RAISE DEBUG 'Executing SQL %', sql; + DECLARE + v_type pg_catalog.regtype; + BEGIN + EXECUTE sql; + EXCEPTION WHEN invalid_function_definition THEN + v_type := (regexp_matches( SQLERRM, 'function result type must be ([^ ]+) because of' ))[1]; + sql := format( + c_template + , function_suffix + , arguments + , v_type + ); + EXECUTE sql; + END; + + /* + * Get new OID. *This must be done dynamically!* Otherwise we get stuck + * with a CONST oid after first compilation. The regproc cast ensures there's + * only one function with this name. The cast to regprocedure is for the sake + * of the DROP down below. + */ + EXECUTE format( + $$SELECT 'pg_temp.cat_tools__function__%s__temp_function'::pg_catalog.regproc::pg_catalog.regprocedure$$ + , function_suffix + ) INTO temp_proc; + + RETURN temp_proc; +END +$body$; + +CREATE FUNCTION _cat_tools.function__drop_temp( + p_regprocedure pg_catalog.regprocedure + , api_function_name text +) RETURNS void LANGUAGE plpgsql AS $body$ +BEGIN + /* + * Security check: Ensure current_user == session_user to detect SECURITY DEFINER context + * This prevents SQL injection attacks through elevated privileges. + */ + IF current_user != session_user THEN + RAISE EXCEPTION USING + ERRCODE = '28000' /* invalid_authorization_specification */ + , MESSAGE = 'potential use of SECURITY DEFINER detected' + , DETAIL = format('API function %s must not be called from a SECURITY DEFINER function', api_function_name) + , HINT = 'We detect SECURITY DEFINER context by comparing current_user and session_user, which can cause false positives if SET ROLE is used'; + END IF; + + EXECUTE 'DROP ROUTINE ' || p_regprocedure; +END +$body$; + +GRANT USAGE ON SCHEMA _cat_tools TO cat_tools__usage; +GRANT EXECUTE ON FUNCTION _cat_tools.function__arg_to_regprocedure(text, text, text) TO cat_tools__usage; +GRANT EXECUTE ON FUNCTION _cat_tools.function__drop_temp(pg_catalog.regprocedure, text) TO cat_tools__usage; + +@generated@ + +-- 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' +); + +CREATE CAST ("char" AS cat_tools.routine_prokind) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_proargmode) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_provolatile) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_proparallel) WITH INOUT AS IMPLICIT; + +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__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__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' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_types' + , $$func pg_catalog.regprocedure$$ + , $$pg_catalog.regtype[] LANGUAGE sql STABLE$$ + , $body$ +SELECT string_to_array(proargtypes::text,' ')::pg_catalog.regtype[] +FROM pg_proc +WHERE oid = $1::pg_catalog.regproc +$body$ + , 'cat_tools__usage' + , 'Returns all argument types for a function as an array of regtype' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_names' + , $$func pg_catalog.regprocedure$$ + , $$text[] LANGUAGE sql STABLE$$ + , $body$ +SELECT + CASE + WHEN proargnames IS NULL THEN + -- No named arguments, return array of NULLs matching proargtypes length + CASE + WHEN pronargs > 0 THEN + array_fill(NULL::text, ARRAY[pronargs]) + ELSE + '{}'::text[] + END + WHEN proargmodes IS NULL THEN + -- All arguments are IN mode, proargnames and proargtypes align + array( + SELECT CASE WHEN name = '' THEN NULL ELSE name END + FROM unnest(proargnames) AS name + ) + ELSE + -- Mixed argument modes, need to filter names to match proargtypes + array( + SELECT + CASE + WHEN i <= array_length(proargnames, 1) AND proargnames[i] != '' THEN proargnames[i] + ELSE NULL + END + FROM unnest(proargmodes) WITH ORDINALITY AS t(mode, i) + WHERE mode IN ('i', 'b', 'v') + ) + END +FROM pg_proc +WHERE oid = $1::pg_catalog.regproc +$body$ + , 'cat_tools__usage' + , 'Returns all argument names for a function as an array of text. Empty strings are converted to NULL.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_types_text' + , $$func pg_catalog.regprocedure$$ + , $$text LANGUAGE sql STABLE$$ + , $body$ +SELECT array_to_string(cat_tools.routine__arg_types($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns all argument types for a function as a comma-separated text string' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_names_text' + , $$func pg_catalog.regprocedure$$ + , $$text LANGUAGE sql STABLE$$ + , $body$ +SELECT array_to_string(cat_tools.routine__arg_names($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns all argument names for a function as a comma-separated text string' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__parse_arg_types' + , $$arguments text$$ + , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ + , $body$ +DECLARE + 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' + , 'Returns argument types for a function argument body as an array. Unlike a + normal regprocedure cast, this function accepts anything that is valid when + defining a function.' +); + +@generated@ + +SELECT __cat_tools.create_function( + '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.routine__parse_arg_types($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns argument types for a function argument body as text. Unlike a + normal regprocedure cast, this function accepts anything that is valid when + defining a function.' + +); + +@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' + , $$ + function_name text + , arguments text$$ + , $$pg_catalog.regprocedure LANGUAGE sql$$ + , $body$ +SELECT format( + '%s(%s)' + , $1 + , cat_tools.routine__parse_arg_types_text($2) +)::pg_catalog.regprocedure +$body$ + , 'cat_tools__usage' + , 'Returns a regprocedure for a given function name and arguments. Unlike a + normal regprocedure cast, arguments can contain anything that is valid when + defining a function.' +); + + +@generated@ + + +@generated@ + +CREATE TYPE cat_tools.object_type AS ENUM( + -- pg_class + 'table' + , 'index' + , 'sequence' + , 'toast table' + , 'view' + , '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! + */ + , 'table column' + , 'index column' + , 'sequence column' + , 'toast table column' + , 'view column' + , 'materialized view column' + , 'composite type column' + , 'foreign table column' + -- pg_constraint + -- NOTE: a domain itself is considered to be a type + , 'domain constraint', 'table constraint' + -- pg_proc + , 'aggregate', 'function' + -- This is taken from getObjectTypeDescription() in objectaddress.c in the Postgres source code + , 'type' + , 'cast' + , 'collation' + , 'conversion' + , 'default value' -- pg_attrdef + , 'language' + , 'large object' -- pg_largeobject + , 'operator' + , 'operator class' -- pg_opclass + , 'operator family' -- pg_opfamily + , 'operator of access method' -- pg_amop + , 'function of access method' -- pg_amproc + , 'rule' -- pg_rewrite + , 'trigger' + , 'schema' -- pg_namespace + , 'text search parser' -- pg_ts_parser + , 'text search dictionary' -- pg_ts_dict + , 'text search template' -- pg_ts_template + , 'text search configuration' -- pg_ts_config + , 'role' -- pg_authid + , 'database' + , 'tablespace' + , 'foreign-data wrapper' -- pg_foreign_data_wrapper + , 'server' -- pg_foreign_server + , 'user mapping' -- pg_user_mapping + , 'default acl' -- pg_default_acl + , 'extension' + , 'event trigger' -- pg_event_trigger -- SED: REQUIRES 9.3! + , 'policy' -- SED: REQUIRES 9.5! + , 'transform' -- SED: REQUIRES 9.5! + , 'access method' -- pg_am +); + + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.objects__shared' + , '' + , 'cat_tools.object_type[] LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT '{role,database,tablespace}'::cat_tools.object_type[] +$body$ + , 'cat_tools__usage' + , 'Returns array of object types for shared objects.' +); +SELECT __cat_tools.create_function( + 'cat_tools.objects__shared_srf' + , '' + , 'SETOF cat_tools.object_type LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT * FROM pg_catalog.unnest(cat_tools.objects__shared()) +$body$ + , 'cat_tools__usage' + , 'Returns set of object types for shared objects.' +); +SELECT __cat_tools.create_function( + 'cat_tools.object__is_shared' + , 'object_type cat_tools.object_type' + , 'boolean LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT object_type = ANY(cat_tools.objects__shared()) +$body$ + , 'cat_tools__usage' + , 'Returns true if object_type is a shared object.' +); +SELECT __cat_tools.create_function( + 'cat_tools.object__is_shared' + , 'object_type text' + , 'boolean LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT cat_tools.object__is_shared(object_type::cat_tools.object_type) +$body$ + , 'cat_tools__usage' + , 'Returns true if object_type is a shared object.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.objects__address_unsupported' + , '' + , 'cat_tools.object_type[] LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT array[ + 'toast table'::cat_tools.object_type, 'composite type' + , 'index column' , 'sequence column', 'toast table column', 'view column' + , 'materialized view column', 'composite type column' +] +$body$ + , 'cat_tools__usage' + , 'Returns array of object types not supported by pg_get_object_address().' +); +SELECT __cat_tools.create_function( + 'cat_tools.objects__address_unsupported_srf' + , '' + , 'SETOF cat_tools.object_type LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT * FROM pg_catalog.unnest(cat_tools.objects__address_unsupported()) +$body$ + , 'cat_tools__usage' + , 'Returns set of object types not supported by pg_get_object_address().' +); +@generated@ +SELECT __cat_tools.create_function( + 'cat_tools.object__is_address_unsupported' + , 'object_type cat_tools.object_type' + , 'boolean LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT object_type = ANY(cat_tools.objects__address_unsupported()) +$body$ + , 'cat_tools__usage' + , 'Returns true if object type is not supported by pg_get_object_address().' +); +SELECT __cat_tools.create_function( + 'cat_tools.object__is_address_unsupported' + , 'object_type text' + , 'boolean LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT cat_tools.object__is_address_unsupported(object_type::cat_tools.object_type) +$body$ + , 'cat_tools__usage' + , 'Returns true if object type is not supported by pg_get_object_address().' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.object__catalog' + , 'object_type cat_tools.object_type' + , 'pg_catalog.regclass LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT ( + 'pg_catalog.' + || CASE + WHEN object_type = ANY( array[ + 'table' + , 'index' + , 'sequence' + , 'toast table' + , 'view' + , '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[] ) + THEN 'pg_constraint' + WHEN object_type = ANY( '{aggregate,function}'::cat_tools.object_type[] ) + THEN 'pg_proc' + WHEN object_type::text LIKE '% column' + THEN 'pg_attribute' + ELSE CASE object_type + -- Unusual cases + -- s/, \(.\{-}\) -- \(.*\)/ WHEN \1 THEN '\2'/ + WHEN 'default value' THEN 'pg_attrdef' + WHEN 'large object' THEN 'pg_largeobject' + WHEN 'operator class' THEN 'pg_opclass' + WHEN 'operator family' THEN 'pg_opfamily' + WHEN 'operator of access method' THEN 'pg_amop' + WHEN 'function of access method' THEN 'pg_amproc' + WHEN 'rule' THEN 'pg_rewrite' + WHEN 'schema' THEN 'pg_namespace' + WHEN 'text search parser' THEN 'pg_ts_parser' + WHEN 'text search dictionary' THEN 'pg_ts_dict' + WHEN 'text search template' THEN 'pg_ts_template' + WHEN 'text search configuration' THEN 'pg_ts_config' + WHEN 'role' THEN 'pg_authid' + WHEN 'foreign-data wrapper' THEN 'pg_foreign_data_wrapper' + WHEN 'server' THEN 'pg_foreign_server' + WHEN 'user mapping' THEN 'pg_user_mapping' + WHEN 'default acl' THEN 'pg_default_acl' + WHEN 'event trigger' THEN 'pg_event_trigger' -- SED: REQUIRES 9.3! + WHEN 'access method' THEN 'pg_am' + ELSE 'pg_' || object_type::text + END + END + )::pg_catalog.regclass +$body$ + , 'cat_tools__usage' + , 'Returns catalog table that is used to store objects' +); +@generated@ +SELECT __cat_tools.create_function( + 'cat_tools.object__catalog' + , 'object_type text' + , 'pg_catalog.regclass LANGUAGE sql STRICT IMMUTABLE' + , $body$SELECT cat_tools.object__catalog(object_type::cat_tools.object_type)$body$ + , 'cat_tools__usage' + , 'Returns catalog table that is used to store objects' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.object__address_classid' + , 'object_type cat_tools.object_type' + , 'pg_catalog.regclass LANGUAGE sql STRICT IMMUTABLE' + , $body$ +SELECT CASE + WHEN c = 'pg_catalog.pg_attribute'::regclass THEN 'pg_catalog.pg_class'::regclass + ELSE c + END + FROM cat_tools.object__catalog(object_type::cat_tools.object_type) c +$body$ + , 'cat_tools__usage' + , 'Returns the classid used by the pg_*_object*() functions for an object_type' +); +SELECT __cat_tools.create_function( + 'cat_tools.object__address_classid' + , 'object_type text' + , 'pg_catalog.regclass LANGUAGE sql STRICT IMMUTABLE' + , $body$SELECT cat_tools.object__address_classid(object_type::cat_tools.object_type)$body$ + , 'cat_tools__usage' + , 'Returns the classid used by the pg_*_object*() functions for an object_type' +); + +@generated@ + +CREATE TABLE _cat_tools.catalog_metadata( + object_catalog pg_catalog.regclass + CONSTRAINT catalog_metadata__pk_object_catalog PRIMARY KEY + , namespace_field name + , reg_type pg_catalog.regtype + , simple_reg_type pg_catalog.regtype +); +-- Table is populated later, after enum_range_srf is created +SELECT __cat_tools.create_function( + '_cat_tools.catalog_metadata__get' + , 'object_catalog _cat_tools.catalog_metadata.object_catalog%TYPE' + , '_cat_tools.catalog_metadata LANGUAGE plpgsql IMMUTABLE' -- Technically should be STABLE + , $body$ +DECLARE + o _cat_tools.catalog_metadata; +BEGIN + SELECT INTO STRICT o + * + FROM _cat_tools.catalog_metadata m + WHERE m.object_catalog = catalog_metadata__get.object_catalog + ; + RETURN o; +END +$body$ + , 'cat_tools__usage' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.object__reg_type' + , 'object_catalog pg_catalog.regclass' + , 'pg_catalog.regtype LANGUAGE sql SECURITY DEFINER STRICT IMMUTABLE' + , $body$ +SELECT (_cat_tools.catalog_metadata__get(object_catalog)).reg_type +$body$ + , 'cat_tools__usage' + , 'Returns the object identifier type (ie: regclass) associated with a system catalog (ie: pg_class).' +); +SELECT __cat_tools.create_function( + 'cat_tools.object__reg_type' + , 'object_type cat_tools.object_type' + , 'pg_catalog.regtype LANGUAGE sql STRICT IMMUTABLE' + , $body$SELECT cat_tools.object__reg_type(cat_tools.object__catalog(object_type))$body$ + , 'cat_tools__usage' + , 'Returns the object identifier type (ie: regclass) associated with a system catalog (ie: pg_class).' +); +SELECT __cat_tools.create_function( + 'cat_tools.object__reg_type' + , 'object_type text' + , 'pg_catalog.regtype LANGUAGE sql STRICT IMMUTABLE' + , $body$SELECT cat_tools.object__reg_type(object_type::cat_tools.object_type)$body$ + , 'cat_tools__usage' + , 'Returns the object identifier type (ie: regclass) associated with a system catalog (ie: pg_class).' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.object__reg_type_catalog' + , 'object_identifier_type regtype' + , 'pg_catalog.regclass LANGUAGE plpgsql SECURITY DEFINER STRICT IMMUTABLE' + , $body$ +DECLARE + cat pg_catalog.regclass; +BEGIN + SELECT INTO STRICT cat + object_catalog + FROM _cat_tools.catalog_metadata m + WHERE m.reg_type = object_identifier_type + OR m.simple_reg_type = object_identifier_type + ; + RETURN cat; +EXCEPTION WHEN no_data_found THEN + IF object_identifier_type::text LIKE 'reg%' THEN + RAISE 'object identifier type % is not supported', object_identifier_type + USING + HINT = format( 'If %I is a valid object identifier type please open an issue at https://github.com/decibel/cat_tools/issues.', object_identifier_type ) + , ERRCODE = 'feature_not_supported' + ; + ELSE + RAISE '% is not a object identifier type', object_identifier_type + USING + HINT = 'See https://www.postgresql.org/docs/current/static/datatype-oid.html' + , ERRCODE = 'wrong_object_type' + ; + END IF; +END +$body$ + , 'cat_tools__usage' + , 'Returns the system catalog that stores a particular object identifier type.' +); + +@generated@ + + +@generated@ + +CREATE OR REPLACE VIEW _cat_tools.pg_depend_identity_v AS -- SED: REQUIRES 9.3! + SELECT o.type AS object_type -- SED: REQUIRES 9.3! + , o.schema AS object_schema -- SED: REQUIRES 9.3! + , o.name AS object_name -- SED: REQUIRES 9.3! + , o.identity AS object_identity -- SED: REQUIRES 9.3! + , r.type AS reference_type -- SED: REQUIRES 9.3! + , r.schema AS reference_schema -- SED: REQUIRES 9.3! + , r.name AS reference_name -- SED: REQUIRES 9.3! + , r.identity AS reference_identity -- SED: REQUIRES 9.3! + , d.* -- SED: REQUIRES 9.3! + FROM pg_catalog.pg_depend d -- SED: REQUIRES 9.3! + , pg_catalog.pg_identify_object(classid, objid, objsubid) o -- SED: REQUIRES 9.3! + , pg_catalog.pg_identify_object(refclassid, refobjid, refobjsubid) r -- SED: REQUIRES 9.3! + WHERE classid <> 0 -- SED: REQUIRES 9.3! + UNION ALL -- SED: REQUIRES 9.3! + SELECT NULL, NULL, NULL, NULL -- SED: REQUIRES 9.3! + , r.type AS reference_type -- SED: REQUIRES 9.3! + , r.schema AS reference_schema -- SED: REQUIRES 9.3! + , r.name AS reference_name -- SED: REQUIRES 9.3! + , r.identity AS reference_identity -- SED: REQUIRES 9.3! + , d.* -- SED: REQUIRES 9.3! + FROM pg_catalog.pg_depend d -- SED: REQUIRES 9.3! + , pg_catalog.pg_identify_object(refclassid, refobjid, refobjsubid) r -- SED: REQUIRES 9.3! + WHERE classid = 0 -- SED: REQUIRES 9.3! +; -- SED: REQUIRES 9.3! + +@generated@ + +CREATE OR REPLACE VIEW cat_tools.pg_class_v AS + SELECT * + FROM _cat_tools.pg_class_v + + /* + * Oddly, there's no security associated with schema or table visibility. + * Be a bit paranoid though. + */ + WHERE NOT pg_is_other_temp_schema(relnamespace) + AND relkind IN( 'r', 'v', 'f' ) +; +GRANT SELECT ON cat_tools.pg_class_v TO cat_tools__usage; + +@generated@ + +/* + * On PG11+, pg_attribute gained attmissingval (pseudo-type anyarray, not usable in views). + * Include it cast to text[] on PG11+; expose as NULL::text[] on older versions. + */ +SELECT __cat_tools.exec(format($fmt$ +CREATE OR REPLACE VIEW _cat_tools.pg_attribute_v AS + SELECT %s + , c.* + , t.oid AS typoid + , %s + , a.attmissingval::text::text[] AS attmissingval -- SED: REQUIRES 11! + , NULL::text[] AS attmissingval -- SED: PRIOR TO 11! + 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$ + /* + * attmissingval is explicitly included above (cast to text[] via SED markers). + * Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+. + */ + , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff']) + , __cat_tools.omit_column('pg_catalog.pg_type') +)); +REVOKE ALL ON _cat_tools.pg_attribute_v FROM public; + +/* + * Computed columns come first so that future CREATE OR REPLACE VIEW upgrades + * can extend the pg_attribute_v column list (via %s) at the end without + * breaking existing column positions. TODO: once issue 13 is resolved, + * switch back to logical ordering (raw pg_attribute_v columns first, computed + * columns after). + */ +SELECT __cat_tools.exec(format($fmt$ +CREATE OR REPLACE VIEW _cat_tools.column AS + SELECT pg_catalog.format_type(typoid, atttypmod) AS column_type + , CASE typtype + -- domain + WHEN 'd' THEN pg_catalog.format_type(typbasetype, typtypmod) + -- enum + WHEN 'e' THEN 'text' + ELSE pg_catalog.format_type(typoid, atttypmod) + END AS base_type + , pk.conkey AS pk_columns + , ARRAY[attnum] <@ pk.conkey AS is_pk_member + , (SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) + FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) AS column_default + , %s + FROM _cat_tools.pg_attribute_v a + LEFT JOIN pg_constraint pk + ON ( reloid = pk.conrelid ) + AND pk.contype = 'p' +; +$fmt$ + , __cat_tools.omit_column('_cat_tools.pg_attribute_v') +)); +REVOKE ALL ON _cat_tools.column FROM public; + +@generated@ + +/* + * Starting in PG12, oid became a visible column in system catalogs. + * Use omit_column to avoid duplicate oid columns. + */ +SELECT __cat_tools.exec(format($fmt$ +CREATE OR REPLACE VIEW cat_tools.pg_extension_v AS + SELECT e.oid + , %s + + , extnamespace::regnamespace AS extschema -- SED: REQUIRES 9.5! + , nspname AS extschema -- SED: PRIOR TO 9.5! + + , extconfig::pg_catalog.regclass[] AS ext_config_tables + 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 + SELECT * + FROM _cat_tools.column + -- SECURITY + WHERE NOT pg_is_other_temp_schema(relnamespace) + AND attnum > 0 + AND NOT attisdropped + AND relkind IN( 'r', 'v', 'f' ) + AND ( + pg_has_role(SESSION_USER, relowner, 'USAGE'::text) + OR has_column_privilege(SESSION_USER, reloid, attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text) + ) + ORDER BY relschema, relname, attnum +; +GRANT SELECT ON cat_tools.column TO cat_tools__usage; + +-- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ +SELECT __cat_tools.create_function( + '_cat_tools._pg_sv_column_array' + , 'OID, SMALLINT[]' + , 'NAME[] LANGUAGE sql STABLE' + , $$ + SELECT ARRAY( + SELECT a.attname + 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 + ) +$$ +); + +@generated@ + +-- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ +SELECT __cat_tools.create_function( + '_cat_tools._pg_sv_table_accessible' + , 'OID, OID' + , 'boolean LANGUAGE sql STABLE' + , $$ + SELECT CASE WHEN has_schema_privilege($1, 'USAGE') THEN ( + has_table_privilege($2, 'SELECT') + OR has_table_privilege($2, 'INSERT') + or has_table_privilege($2, 'UPDATE') + OR has_table_privilege($2, 'DELETE') + OR has_table_privilege($2, 'RULE') + OR has_table_privilege($2, 'REFERENCES') + OR has_table_privilege($2, 'TRIGGER') + ) ELSE FALSE + END; +$$ +); + +@generated@ + +-- Borrowed from newsysviews: http://pgfoundry.org/projects/newsysviews/ +CREATE OR REPLACE VIEW cat_tools.pg_all_foreign_keys +AS + SELECT n1.nspname AS fk_schema_name, + c1.relname AS fk_table_name, + k1.conname AS fk_constraint_name, + c1.oid AS fk_table_oid, + _cat_tools._pg_sv_column_array(k1.conrelid,k1.conkey) AS fk_columns, + n2.nspname AS pk_schema_name, + c2.relname AS pk_table_name, + k2.conname AS pk_constraint_name, + c2.oid AS pk_table_oid, + ci.relname AS pk_index_name, + _cat_tools._pg_sv_column_array(k1.confrelid,k1.confkey) AS pk_columns, + CASE k1.confmatchtype WHEN 'f' THEN 'FULL' + WHEN 'p' THEN 'PARTIAL' + WHEN 'u' THEN 'NONE' + else null + END AS match_type, + CASE k1.confdeltype WHEN 'a' THEN 'NO ACTION' -- @generated@ + WHEN 'c' THEN 'CASCADE' + WHEN 'd' THEN 'SET DEFAULT' + WHEN 'n' THEN 'SET NULL' + WHEN 'r' THEN 'RESTRICT' + else null + END AS on_delete, + CASE k1.confupdtype WHEN 'a' THEN 'NO ACTION' + WHEN 'c' THEN 'CASCADE' + WHEN 'd' THEN 'SET DEFAULT' + WHEN 'n' THEN 'SET NULL' + WHEN 'r' THEN 'RESTRICT' + ELSE NULL + END AS on_update, + k1.condeferrable AS is_deferrable, -- @generated@ + k1.condeferred AS is_deferred + FROM pg_catalog.pg_constraint k1 + JOIN pg_catalog.pg_namespace n1 ON (n1.oid = k1.connamespace) + JOIN pg_catalog.pg_class c1 ON (c1.oid = k1.conrelid) + JOIN pg_catalog.pg_class c2 ON (c2.oid = k1.confrelid) + JOIN pg_catalog.pg_namespace n2 ON (n2.oid = c2.relnamespace) + JOIN pg_catalog.pg_depend d ON ( + d.classid = 'pg_constraint'::pg_catalog.regclass -- @generated@ + AND d.objid = k1.oid + AND d.objsubid = 0 + AND d.deptype = 'n' + AND d.refclassid = 'pg_class'::pg_catalog.regclass + AND d.refobjsubid=0 + ) + JOIN pg_catalog.pg_class ci ON (ci.oid = d.refobjid AND ci.relkind = 'i') + LEFT JOIN pg_depend d2 ON ( + d2.classid = 'pg_class'::pg_catalog.regclass -- @generated@ + AND d2.objid = ci.oid + AND d2.objsubid = 0 + AND d2.deptype = 'i' + AND d2.refclassid = 'pg_constraint'::pg_catalog.regclass + AND d2.refobjsubid = 0 + ) + LEFT JOIN pg_catalog.pg_constraint k2 ON ( -- @generated@ + k2.oid = d2.refobjid + AND k2.contype IN ('p', 'u') + ) + WHERE k1.conrelid != 0 + AND k1.confrelid != 0 + AND k1.contype = 'f' + AND _cat_tools._pg_sv_table_accessible(n1.oid, c1.oid) +; +GRANT SELECT ON cat_tools.pg_all_foreign_keys TO cat_tools__usage; + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.pg_attribute__get' + , $$ + relation pg_catalog.regclass + , column_name name +$$ + , $$pg_catalog.pg_attribute LANGUAGE plpgsql$$ + , $body$ +DECLARE + r pg_catalog.pg_attribute; +BEGIN + SELECT INTO STRICT r + * + FROM pg_catalog.pg_attribute + WHERE attrelid = relation + AND attname = column_name + ; + RETURN r; +EXCEPTION WHEN no_data_found THEN + RAISE 'column "%" of relation "%" does not exist', column_name, relation + USING ERRCODE = 'undefined_column' + ; +END +$body$ + , 'cat_tools__usage' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.pg_extension__get' + , 'extension_name name' + , $$cat_tools.pg_extension_v LANGUAGE plpgsql$$ + , $body$ +DECLARE + r cat_tools.pg_extension_v; +BEGIN + SELECT INTO STRICT r + * + FROM cat_tools.pg_extension_v + WHERE extname = extension_name + ; + RETURN r; +EXCEPTION WHEN no_data_found THEN + RAISE 'extension "%" does not exist', extension_name + USING ERRCODE = 'undefined_object' + ; +END +$body$ + , 'cat_tools__usage' +); +SELECT __cat_tools.create_function( + 'cat_tools.extension__schemas' + , 'extension_names name[]' + , $$pg_catalog.regnamespace[] LANGUAGE sql$$ -- SED: REQUIRES 9.5! + , $$pg_catalog.name[] LANGUAGE sql$$ -- SED: PRIOR TO 9.5! + , $body$ +SELECT array( + SELECT (cat_tools.pg_extension__get(en)).extschema + FROM unnest(extension_names) en +) +$body$ + , 'cat_tools__usage' +); +SELECT __cat_tools.create_function( + 'cat_tools.extension__schemas_unique' + , 'extension_names name[]' + , $$pg_catalog.regnamespace[] LANGUAGE sql$$ -- SED: REQUIRES 9.5! + , $$pg_catalog.name[] LANGUAGE sql$$ -- SED: PRIOR TO 9.5! + , $body$ +SELECT array( + SELECT DISTINCT (cat_tools.pg_extension__get(en)).extschema + FROM unnest(extension_names) en +) +$body$ + , 'cat_tools__usage' +); + +@generated@ + +-- Text versions +SELECT __cat_tools.create_function( + 'cat_tools.extension__schemas' + , 'extension_names text' + , $$pg_catalog.regnamespace[] LANGUAGE sql$$ -- SED: REQUIRES 9.5! + , $$pg_catalog.name[] LANGUAGE sql$$ -- SED: PRIOR TO 9.5! + , $body$ +SELECT cat_tools.extension__schemas( + CASE WHEN extension_names LIKE '{%}' THEN extension_names + ELSE '{' || extension_names || '}' + END::name[] +) +$body$ + , 'cat_tools__usage' +); +SELECT __cat_tools.create_function( + 'cat_tools.extension__schemas_unique' + , 'extension_names text' + , $$pg_catalog.regnamespace[] LANGUAGE sql$$ -- SED: REQUIRES 9.5! + , $$pg_catalog.name[] LANGUAGE sql$$ -- SED: PRIOR TO 9.5! + , $body$ +SELECT cat_tools.extension__schemas_unique( + CASE WHEN extension_names LIKE '{%}' THEN extension_names + ELSE '{' || extension_names || '}' + END::name[] +) +$body$ + , 'cat_tools__usage' +); + + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.get_serial_sequence' + , $$ + table_name text + , column_name text +$$ + , $$pg_catalog.regclass LANGUAGE plpgsql$$ + , $body$ +DECLARE + seq pg_catalog.regclass; +BEGIN + -- Note: the function will throw an error if table or column doesn't exist + seq := pg_get_serial_sequence( table_name, column_name ); + + IF seq IS NULL THEN + RAISE EXCEPTION '"%" is not a serial column', column_name + USING ERRCODE = 'wrong_object_type' + -- TODO: SCHEMA and COLUMN + , COLUMN = column_name -- SED: REQUIRES 9.3! + ; + END IF; + + RETURN seq; +END +$body$ + , 'cat_tools__usage' + , 'Return sequence that is associated with a column. Unlike the pg_get_serial_sequence, throw an exception if there is no sequence associated with the column.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.sequence__last' + , $$ + table_name text + , column_name text +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT pg_catalog.currval(cat_tools.get_serial_sequence($1,$2))' + , 'cat_tools__usage' + , 'Return the last value assigned to a column with an associated sequence.' +); +SELECT __cat_tools.create_function( + 'cat_tools.currval' + , $$ + table_name text + , column_name text +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT cat_tools.sequence__last($1,$2)' + , 'cat_tools__usage' + , 'Return the last value assigned to a column with an associated sequence.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.sequence__next' + , $$ + table_name text + , column_name text +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT pg_catalog.nextval(cat_tools.get_serial_sequence($1,$2))' + , 'cat_tools__usage' + , 'Return the next value to assign to a column with an associated sequence. THIS ADVANCES THE SEQUENCE.' +); +SELECT __cat_tools.create_function( + 'cat_tools.nextval' + , $$ + table_name text + , column_name text +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT cat_tools.sequence__next($1,$2)' + , 'cat_tools__usage' + , 'Return the next value to assign to a column with an associated sequence. THIS ADVANCES THE SEQUENCE.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.setval' + , $$ + table_name text + , column_name text + , new_value bigint + , has_been_used boolean DEFAULT true +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT pg_catalog.setval(cat_tools.get_serial_sequence($1,$2), $3, $4)' + , 'cat_tools__usage' + , 'Changes the value for a sequence associated with a column. If has_been_used is true, the sequence will be set to new_value + 1. Returns new_value. See also sequence__set_last() and sequence__set_next().' +); +SELECT __cat_tools.create_function( + 'cat_tools.sequence__set_last' + , $$ + table_name text + , column_name text + , last_value bigint +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT cat_tools.setval($1,$2,$3,true)' + , 'cat_tools__usage' + , 'Changes the value for a sequence associated with a column. last_value is the last value used, so sequence is set to last_value+1. See also sequence__set_next().' +); +SELECT __cat_tools.create_function( + 'cat_tools.sequence__set_next' + , $$ + table_name text + , column_name text + , next_value bigint +$$ + , $$bigint LANGUAGE sql$$ + , 'SELECT cat_tools.setval($1,$2,$3,false)' + , 'cat_tools__usage' + , 'Changes the value for a sequence associated with a column. next_value is the next value the sequence will assign. See also sequence__last_value.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.enum_range' + , 'enum pg_catalog.regtype' + , $$text[] LANGUAGE plpgsql STABLE$$ + , $body$ +DECLARE + ret text[]; +BEGIN + EXECUTE format('SELECT pg_catalog.enum_range( NULL::%s )', enum) INTO ret; + RETURN ret; +END +$body$ + , 'cat_tools__usage' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.enum_range_srf' + , 'enum pg_catalog.regtype' + , $$SETOF text LANGUAGE sql$$ + , $body$ +SELECT * FROM unnest( cat_tools.enum_range($1) ) AS r(enum_label) +$body$ + , 'cat_tools__usage' +); + +SELECT __cat_tools.create_function( + 'cat_tools.pg_class' + , 'rel pg_catalog.regclass' + , $$cat_tools.pg_class_v LANGUAGE sql STABLE$$ + , $body$ +SELECT * FROM cat_tools.pg_class_v WHERE reloid = $1 +$body$ + , 'cat_tools__usage' +); + +@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' + , $$void LANGUAGE plpgsql$$ + , $body$ +BEGIN + IF name_to_check IS DISTINCT FROM name_to_check::name THEN + RAISE '"%" becomes "%" when cast to name', name_to_check, name_to_check::name; + END IF; +END +$body$ + , 'cat_tools__usage' +); + +@generated@ + +/* + * Trigger functions + */ +SELECT __cat_tools.create_function( + 'cat_tools.trigger__get_oid__loose' + , $$ + trigger_table pg_catalog.regclass + , trigger_name text +$$ + , $$oid LANGUAGE sql$$ + , $body$ + SELECT oid + FROM pg_trigger + WHERE tgrelid = $1 --trigger_table + AND tgname = CASE + /* + * tgname isn't quoted, so strip quotes, but only if the string both + * starts and ends with quotes + */ + WHEN $2 LIKE '"%"' THEN btrim($2, '"') + ELSE $2 + END --trigger_name + ; +$body$ + , 'cat_tools__usage' + , 'Return the OID for a trigger. Returns NULL if trigger does not exist.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.trigger__get_oid' + , $$ + trigger_table pg_catalog.regclass + , trigger_name text +$$ + , $$oid LANGUAGE plpgsql$$ + , $body$ +DECLARE + v_oid oid; +BEGIN + -- Note that because __loose isn't an SRF it'll always return a value + v_oid := cat_tools.trigger__get_oid__loose( trigger_table, trigger_name ) ; + + IF v_oid IS NULL THEN + RAISE EXCEPTION 'trigger % on table % does not exist', trigger_name, trigger_table + USING errcode = 'undefined_object' -- 42704 + ; + END IF; + + RETURN v_oid; +END +$body$ + , 'cat_tools__usage' + , 'Return the OID for a trigger. Throws an undefined_object error if the trigger does not exist.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.trigger__parse' + , $$ + trigger_oid oid + , OUT trigger_table regclass + , OUT timing text + , OUT events text[] + , OUT defer text + , OUT row_statement text + , OUT when_clause text + , OUT trigger_function regprocedure + , OUT function_arguments text[] +$$ + , $$record STABLE LANGUAGE plpgsql$$ + , $body$ +DECLARE + r_trigger pg_catalog.pg_trigger; + v_triggerdef text; + v_create_stanza text; + v_on_clause text; + v_execute_clause text; + + v_work text; + v_array text[]; +BEGIN + /* + * Do this first to make sure trigger exists. + * + * TODO: After we no longer support < 9.6, test v_triggerdef for NULL instead + * using the extra block here. + */ + BEGIN + SELECT * INTO STRICT r_trigger FROM pg_catalog.pg_trigger WHERE oid = trigger_oid; + EXCEPTION WHEN no_data_found THEN + RAISE EXCEPTION 'trigger with OID % does not exist', trigger_oid + USING errcode = 'undefined_object' -- 42704 + ; + END; + trigger_table := r_trigger.tgrelid; + trigger_function := r_trigger.tgfoid; + + v_triggerdef := pg_catalog.pg_get_triggerdef(trigger_oid, true); + + v_create_stanza := format( + 'CREATE %sTRIGGER %I ' + , CASE WHEN r_trigger.tgconstraint=0 THEN '' ELSE 'CONSTRAINT ' END + , r_trigger.tgname + ); + -- Strip CREATE [CONSTRAINT] TRIGGER ... off + v_work := replace( v_triggerdef, v_create_stanza, '' ); + + -- Get BEFORE | AFTER | INSTEAD OF + timing := split_part( v_work, ' ', 1 ); + timing := timing || CASE timing WHEN 'INSTEAD' THEN ' OF' ELSE '' END; + + -- Strip off timing clause + v_work := replace( v_work, timing || ' ', '' ); + + -- Get array of events (INSERT, UPDATE [OF column, column], DELETE, TRUNCATE) + v_on_clause := ' ON ' || r_trigger.tgrelid::pg_catalog.regclass || ' '; + v_array := regexp_split_to_array( v_work, v_on_clause ); + events := string_to_array( v_array[1], ' OR ' ); + -- Get everything after ON table_name + v_work := v_array[2]; + RAISE DEBUG 'v_work "%"', v_work; + + -- Strip off FROM referenced_table if we have it + IF r_trigger.tgconstrrelid<>0 THEN + v_work := replace( + v_work + , 'FROM ' || r_trigger.tgconstrrelid::pg_catalog.regclass || ' ' + , '' + ); + END IF; + RAISE DEBUG 'v_work "%"', v_work; + + -- Get function arguments + -- PG11+ uses "EXECUTE FUNCTION"; older versions use "EXECUTE PROCEDURE". + -- Note: ::regproc returns the internal pg_temp_N schema name while pg_get_triggerdef + -- uses the pg_temp alias, so we match on EXECUTE PROCEDURE/FUNCTION + any non-space + -- chars (the function name) rather than the specific function name. + v_execute_clause := E' EXECUTE (?:PROCEDURE|FUNCTION) \\S+\\('; + v_array := regexp_split_to_array( v_work, v_execute_clause ); + EXECUTE CASE + WHEN trim(rtrim(v_array[2], ')')) = '' THEN 'SELECT ARRAY[]::text[]' + ELSE format('SELECT array[ %s ]', rtrim( v_array[2], ')' )) + END + INTO function_arguments + ; + RAISE DEBUG 'v_array[2] "%"', v_array[2]; + -- Get everything prior to EXECUTE PROCEDURE ... + v_work := v_array[1]; + RAISE DEBUG 'v_work "%"', v_work; + + row_statement := (regexp_matches( v_work, 'FOR EACH (ROW|STATEMENT)' ))[1]; + + -- Get [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ] + v_array := regexp_split_to_array( v_work, 'FOR EACH (ROW|STATEMENT)' ); + RAISE DEBUG 'v_work = "%", v_array = "%"', v_work, v_array; + defer := rtrim(v_array[1]); + + IF r_trigger.tgqual IS NOT NULL THEN + when_clause := rtrim( + (regexp_split_to_array( v_array[2], E' WHEN \\(' ))[2] + , ')' + ); + END IF; + + RAISE DEBUG +$$v_create_stanza = "%" + v_on_clause = "%" + v_execute_clause = "%"$$ + , v_create_stanza + , v_on_clause + , v_execute_clause + ; + + RETURN; +END +$body$ + , 'cat_tools__usage' + , 'Provide details about a trigger.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.trigger__parse' + , $$ + trigger_table pg_catalog.regclass + , trigger_name text + , OUT timing text + , OUT events text[] + , OUT defer text + , OUT row_statement text + , OUT when_clause text + , OUT trigger_function regprocedure + , OUT function_arguments text[] +$$ + , $$record STABLE LANGUAGE sql$$ + -- s/, OUT \(\w*\).*/ , \1/ + , $body$ +SELECT + timing + , events + , "defer" + , row_statement + , when_clause + , trigger_function + , function_arguments + FROM cat_tools.trigger__parse( + cat_tools.trigger__get_oid(trigger_table, trigger_name) + ) +$body$ + , 'cat_tools__usage' + , 'Provide details about a trigger.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.trigger__args_as_text' + , $$function_arguments text[]$$ + , $$text IMMUTABLE STRICT LANGUAGE sql$$ + , $body$ + SELECT format( + $$'%s'$$ + , array_to_string( + function_arguments + , $$', '$$ + ) + ) +$body$ + , 'cat_tools__usage' + , 'Convert function_arguments as returned by trigger__parse() to text (for backwards compatibility).' +); + +@generated@ + +INSERT INTO _cat_tools.catalog_metadata(object_catalog, reg_type, namespace_field) +SELECT object__catalog + , CASE object__catalog + WHEN 'pg_catalog.pg_class'::pg_catalog.regclass THEN 'pg_catalog.regclass' + WHEN 'pg_catalog.pg_ts_config'::pg_catalog.regclass THEN 'pg_catalog.regconfig' + WHEN 'pg_catalog.pg_ts_dict'::pg_catalog.regclass THEN 'pg_catalog.regdictionary' + WHEN 'pg_catalog.pg_namespace'::pg_catalog.regclass THEN 'pg_catalog.regnamespace' -- SED: REQUIRES 9.5! + WHEN 'pg_catalog.pg_operator'::pg_catalog.regclass THEN 'pg_catalog.regoperator' + WHEN 'pg_catalog.pg_proc'::pg_catalog.regclass THEN 'pg_catalog.regprocedure' + WHEN 'pg_catalog.pg_authid'::pg_catalog.regclass THEN 'pg_catalog.regrole' -- SED: REQUIRES 9.5! + WHEN 'pg_catalog.pg_type'::pg_catalog.regclass THEN 'pg_catalog.regtype' + END::pg_catalog.regtype + , n.attname + FROM ( + SELECT DISTINCT cat_tools.object__catalog(object_type) + FROM cat_tools.enum_range_srf('cat_tools.object_type') r(object_type) + ) d + LEFT JOIN cat_tools.column n + ON n.attrelid = object__catalog + AND n.attname ~ 'namespace$' + AND atttypid = 'oid'::pg_catalog.regtype +; +UPDATE _cat_tools.catalog_metadata + SET simple_reg_type = 'pg_catalog.regproc' + WHERE object_catalog = 'pg_catalog.pg_proc'::pg_catalog.regclass +; +UPDATE _cat_tools.catalog_metadata + SET simple_reg_type = 'pg_catalog.regoper' + WHERE object_catalog = 'pg_catalog.pg_operator'::pg_catalog.regclass +; +-- Cluster to get rid of dead rows +CLUSTER _cat_tools.catalog_metadata USING catalog_metadata__pk_object_catalog; + +@generated@ + +/* + * Drop "temporary" objects + */ +DROP FUNCTION __cat_tools.omit_column( + rel text + , omit name[] +); +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; + +-- vi: expandtab ts=2 sw=2 diff --git a/sql/cat_tools.sql.in b/sql/cat_tools.sql.in index bf119eb..ca8f4a9 100644 --- a/sql/cat_tools.sql.in +++ b/sql/cat_tools.sql.in @@ -67,7 +67,7 @@ CREATE OR REPLACE VIEW _cat_tools.pg_class_v AS LEFT JOIN pg_namespace n ON( n.oid = c.relnamespace ) ; $fmt$ - , __cat_tools.omit_column('pg_catalog.pg_class') + , __cat_tools.omit_column('pg_catalog.pg_class', array['oid', 'relhasoids', 'relhaspkey']) )); REVOKE ALL ON _cat_tools.pg_class_v FROM public; @@ -76,7 +76,7 @@ REVOKE ALL ON _cat_tools.pg_class_v FROM public; * function that we're about to create to create the real version of this * function. */ -CREATE FUNCTION cat_tools.function__arg_types_text(text +CREATE FUNCTION cat_tools.routine__parse_arg_types_text(text ) RETURNS text LANGUAGE sql AS 'SELECT $1'; CREATE FUNCTION __cat_tools.create_function( @@ -88,7 +88,7 @@ CREATE FUNCTION __cat_tools.create_function( , comment text DEFAULT NULL ) RETURNS void LANGUAGE plpgsql AS $body$ DECLARE - c_simple_args CONSTANT text := cat_tools.function__arg_types_text(args); + c_simple_args CONSTANT text := cat_tools.routine__parse_arg_types_text(args); create_template CONSTANT text := $template$ CREATE OR REPLACE FUNCTION %s( @@ -161,24 +161,40 @@ $body$; @generated@ -SELECT __cat_tools.create_function( - 'cat_tools.function__arg_types' - , $$arguments text$$ - , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ - , $body$ +CREATE FUNCTION _cat_tools.function__arg_to_regprocedure( + arguments text + , function_suffix text + , api_function_name text +) RETURNS pg_catalog.regprocedure LANGUAGE plpgsql AS $body$ DECLARE - input_arg_types pg_catalog.regtype[]; - - c_template CONSTANT text := $fmt$CREATE FUNCTION pg_temp.cat_tools__function__arg_types__temp_function( + /* + * Template for creating a temporary function with the user-provided argument + * signature. This allows us to leverage PostgreSQL's parser to validate and + * extract argument information without permanently creating a function. + * Using plpgsql language for the temp function to handle any return type. + */ + c_template CONSTANT text := $fmt$CREATE FUNCTION pg_temp.cat_tools__function__%s__temp_function( %s - ) RETURNS %s LANGUAGE plpgsql AS 'BEGIN NULL; END' + ) RETURNS %s LANGUAGE plpgsql AS 'BEGIN RETURN; END' $fmt$; temp_proc pg_catalog.regprocedure; sql text; BEGIN + /* + * Security check: Ensure current_user == session_user to detect SECURITY DEFINER context + * This prevents SQL injection attacks through elevated privileges. + */ + IF current_user != session_user THEN + RAISE EXCEPTION USING + ERRCODE = '28000' /* invalid_authorization_specification */ + , MESSAGE = 'potential use of SECURITY DEFINER detected' + , DETAIL = format('current_user is %s, session_user is %s', current_user, session_user) + , HINT = 'Helper functions must not be called from SECURITY DEFINER context.'; + END IF; sql := format( c_template + , function_suffix , arguments , 'void' ); @@ -191,6 +207,7 @@ BEGIN v_type := (regexp_matches( SQLERRM, 'function result type must be ([^ ]+) because of' ))[1]; sql := format( c_template + , function_suffix , arguments , v_type ); @@ -203,20 +220,383 @@ 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; - RETURN input_arg_types; + 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' +); + +CREATE CAST ("char" AS cat_tools.routine_prokind) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_proargmode) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_provolatile) WITH INOUT AS IMPLICIT; +CREATE CAST ("char" AS cat_tools.routine_proparallel) WITH INOUT AS IMPLICIT; + +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__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__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' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_types' + , $$func pg_catalog.regprocedure$$ + , $$pg_catalog.regtype[] LANGUAGE sql STABLE$$ + , $body$ +SELECT string_to_array(proargtypes::text,' ')::pg_catalog.regtype[] +FROM pg_proc +WHERE oid = $1::pg_catalog.regproc +$body$ + , 'cat_tools__usage' + , 'Returns all argument types for a function as an array of regtype' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_names' + , $$func pg_catalog.regprocedure$$ + , $$text[] LANGUAGE sql STABLE$$ + , $body$ +SELECT + CASE + WHEN proargnames IS NULL THEN + -- No named arguments, return array of NULLs matching proargtypes length + CASE + WHEN pronargs > 0 THEN + array_fill(NULL::text, ARRAY[pronargs]) + ELSE + '{}'::text[] + END + WHEN proargmodes IS NULL THEN + -- All arguments are IN mode, proargnames and proargtypes align + array( + SELECT CASE WHEN name = '' THEN NULL ELSE name END + FROM unnest(proargnames) AS name + ) + ELSE + -- Mixed argument modes, need to filter names to match proargtypes + array( + SELECT + CASE + WHEN i <= array_length(proargnames, 1) AND proargnames[i] != '' THEN proargnames[i] + ELSE NULL + END + FROM unnest(proargmodes) WITH ORDINALITY AS t(mode, i) + WHERE mode IN ('i', 'b', 'v') + ) + END +FROM pg_proc +WHERE oid = $1::pg_catalog.regproc +$body$ + , 'cat_tools__usage' + , 'Returns all argument names for a function as an array of text. Empty strings are converted to NULL.' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_types_text' + , $$func pg_catalog.regprocedure$$ + , $$text LANGUAGE sql STABLE$$ + , $body$ +SELECT array_to_string(cat_tools.routine__arg_types($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns all argument types for a function as a comma-separated text string' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__arg_names_text' + , $$func pg_catalog.regprocedure$$ + , $$text LANGUAGE sql STABLE$$ + , $body$ +SELECT array_to_string(cat_tools.routine__arg_names($1), ', ') +$body$ + , 'cat_tools__usage' + , 'Returns all argument names for a function as a comma-separated text string' +); + +@generated@ + +SELECT __cat_tools.create_function( + 'cat_tools.routine__parse_arg_types' + , $$arguments text$$ + , $$pg_catalog.regtype[] LANGUAGE plpgsql$$ + , $body$ +DECLARE + 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' @@ -228,11 +608,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 @@ -243,6 +648,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' , $$ @@ -253,7 +713,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' @@ -265,38 +725,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@ @@ -310,6 +738,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! @@ -361,6 +791,7 @@ CREATE TYPE cat_tools.object_type AS ENUM( , 'access method' -- pg_am ); + @generated@ SELECT __cat_tools.create_function( @@ -471,6 +902,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[] ) @@ -641,64 +1074,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@ @@ -766,7 +1141,7 @@ $fmt$ * attmissingval is explicitly included above (cast to text[] via SED markers). * Omit it here so it doesn't appear twice, and omit oid to avoid conflicts on PG12+. */ - , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval']) + , __cat_tools.omit_column('pg_catalog.pg_attribute', array['oid', 'attmissingval', 'attcacheoff']) , __cat_tools.omit_column('pg_catalog.pg_type') )); REVOKE ALL ON _cat_tools.pg_attribute_v FROM public; @@ -854,8 +1229,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 ) @@ -1228,6 +1603,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' diff --git a/test/sql/zzz_build.sql b/test/build/build.sql similarity index 100% rename from test/sql/zzz_build.sql rename to test/build/build.sql diff --git a/test/expected/zzz_build.out b/test/build/expected/build.out similarity index 81% rename from test/expected/zzz_build.out rename to test/build/expected/build.out index dbe9615..75dcebd 100644 --- a/test/expected/zzz_build.out +++ b/test/build/expected/build.out @@ -82,6 +82,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/build/expected/upgrade.out b/test/build/expected/upgrade.out new file mode 100644 index 0000000..25fdbb1 --- /dev/null +++ b/test/build/expected/upgrade.out @@ -0,0 +1 @@ +\set ECHO none diff --git a/test/build/upgrade.sql b/test/build/upgrade.sql new file mode 100644 index 0000000..203caa0 --- /dev/null +++ b/test/build/upgrade.sql @@ -0,0 +1,28 @@ +\set ECHO none +\i test/pgxntool/psql.sql +\t + +/* + * Sanity check: install a previous version and update to current. + * + * The 0.2.2β†’0.3.0 update script uses ALTER TYPE ... ADD VALUE, which cannot + * run inside a transaction block or in an extension update script + * (PROCESS_UTILITY_QUERY context) on PG11 and below. This restriction was + * lifted in PG12. PG11 and below are therefore skipped entirely. + */ +SELECT current_setting('server_version_num')::int >= 120000 AS pg12plus \gset + +\if :pg12plus +BEGIN; +CREATE EXTENSION cat_tools VERSION '0.2.2'; +-- Suppress expected deprecation warnings from the update. +SET LOCAL client_min_messages = ERROR; +ALTER EXTENSION cat_tools UPDATE; +ROLLBACK; +\else +/* + * PG11 and below: skip the update test. ALTER TYPE ... ADD VALUE cannot run + * inside a transaction block or in an extension update script on PG11 and + * below (PROCESS_UTILITY_QUERY context). This restriction was lifted in PG12. + */ +\endif diff --git a/test/deps.sql b/test/deps.sql index f7fe4d9..26c1bd4 100644 --- a/test/deps.sql +++ b/test/deps.sql @@ -5,7 +5,12 @@ SET client_min_messages = WARNING; -- Note: pgTap is loaded by setup.sql --CREATE EXTENSION IF NOT EXISTS ...; -\i test/.build/active.sql +/* + * Now load our extension. We don't use IF NOT EXISTS here because we want an + * error if the extension is already loaded (because we want to ensure we're + * getting the very latest version). + */ +CREATE EXTENSION cat_tools; -- Used by several unit tests \set no_use_role cat_tools_testing__no_use_role diff --git a/test/expected/function.out b/test/expected/function.out index 29dade1..60e18f6 100644 --- a/test/expected/function.out +++ b/test/expected/function.out @@ -1,11 +1,15 @@ \set ECHO none 1..8 ok 1 - Verify public has no perms -ok 2 - Verify public has no perms +ok 2 - Verify regprocedure() +WARNING: function__arg_types() is deprecated, use routine__parse_arg_types instead 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() +WARNING: function__arg_types() is deprecated, use routine__parse_arg_types instead +ok 4 - Verify function__arg_types() with simple args +WARNING: function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead +ok 5 - Verify function__arg_types_text() with INOUT and OUT +WARNING: function__arg_types_text() is deprecated, use routine__parse_arg_types_text instead +ok 6 - Verify function__arg_types_text() with simple args +ok 7 - Function _cat_tools.function__arg_to_regprocedure(text, text, text) should not be security definer +ok 8 - Function _cat_tools.function__drop_temp(regprocedure, text) should not be security definer # 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..ce4d767 --- /dev/null +++ b/test/expected/permissions.out @@ -0,0 +1,161 @@ +\set ECHO none +1..158 +ok 1 - Permission denied trying to execute cat_tools.currval(table_name text, column_name text) +ok 2 - Permission denied trying to execute cat_tools.enum_range(enum regtype) +ok 3 - Permission denied trying to execute cat_tools.enum_range_srf(enum regtype) +ok 4 - Permission denied trying to execute cat_tools.extension__schemas(extension_names name[]) +ok 5 - Permission denied trying to execute cat_tools.extension__schemas(extension_names text) +ok 6 - Permission denied trying to execute cat_tools.extension__schemas_unique(extension_names name[]) +ok 7 - Permission denied trying to execute cat_tools.extension__schemas_unique(extension_names text) +ok 8 - Permission denied trying to execute cat_tools.function__arg_types(arguments text) +ok 9 - Permission denied trying to execute cat_tools.function__arg_types_text(arguments text) +ok 10 - Permission denied trying to execute cat_tools.get_serial_sequence(table_name text, column_name text) +ok 11 - Permission denied trying to execute cat_tools.name__check(name_to_check text) +ok 12 - Permission denied trying to execute cat_tools.nextval(table_name text, column_name text) +ok 13 - Permission denied trying to execute cat_tools.object__address_classid(object_type cat_tools.object_type) +ok 14 - Permission denied trying to execute cat_tools.object__address_classid(object_type text) +ok 15 - Permission denied trying to execute cat_tools.object__catalog(object_type cat_tools.object_type) +ok 16 - Permission denied trying to execute cat_tools.object__catalog(object_type text) +ok 17 - Permission denied trying to execute cat_tools.object__is_address_unsupported(object_type cat_tools.object_type) +ok 18 - Permission denied trying to execute cat_tools.object__is_address_unsupported(object_type text) +ok 19 - Permission denied trying to execute cat_tools.object__is_shared(object_type cat_tools.object_type) +ok 20 - Permission denied trying to execute cat_tools.object__is_shared(object_type text) +ok 21 - Permission denied trying to execute cat_tools.object__reg_type(object_catalog regclass) +ok 22 - Permission denied trying to execute cat_tools.object__reg_type(object_type cat_tools.object_type) +ok 23 - Permission denied trying to execute cat_tools.object__reg_type(object_type text) +ok 24 - Permission denied trying to execute cat_tools.object__reg_type_catalog(object_identifier_type regtype) +ok 25 - Permission denied trying to execute cat_tools.objects__address_unsupported() +ok 26 - Permission denied trying to execute cat_tools.objects__address_unsupported_srf() +ok 27 - Permission denied trying to execute cat_tools.objects__shared() +ok 28 - Permission denied trying to execute cat_tools.objects__shared_srf() +ok 29 - Permission denied trying to execute cat_tools.pg_attribute__get(relation regclass, column_name name) +ok 30 - Permission denied trying to execute cat_tools.pg_class(rel regclass) +ok 31 - Permission denied trying to execute cat_tools.pg_extension__get(extension_name name) +ok 32 - Permission denied trying to execute cat_tools.regprocedure(function_name text, arguments text) +ok 33 - Permission denied trying to execute cat_tools.relation__column_names(relation regclass) +ok 34 - Permission denied trying to execute cat_tools.relation__is_catalog(relation regclass) +ok 35 - Permission denied trying to execute cat_tools.relation__is_temp(relation regclass) +ok 36 - Permission denied trying to execute cat_tools.relation__kind(relkind cat_tools.relation_relkind) +ok 37 - Permission denied trying to execute cat_tools.relation__kind(relkind text) +ok 38 - Permission denied trying to execute cat_tools.relation__relkind(kind cat_tools.relation_type) +ok 39 - Permission denied trying to execute cat_tools.relation__relkind(kind text) +ok 40 - Permission denied trying to execute cat_tools.routine__arg_names(func regprocedure) +ok 41 - Permission denied trying to execute cat_tools.routine__arg_names_text(func regprocedure) +ok 42 - Permission denied trying to execute cat_tools.routine__arg_types(func regprocedure) +ok 43 - Permission denied trying to execute cat_tools.routine__arg_types_text(func regprocedure) +ok 44 - Permission denied trying to execute cat_tools.routine__argument_mode(proargmode cat_tools.routine_proargmode) +ok 45 - Permission denied trying to execute cat_tools.routine__parallel_safety(proparallel cat_tools.routine_proparallel) +ok 46 - Permission denied trying to execute cat_tools.routine__parse_arg_names(arguments text) +ok 47 - Permission denied trying to execute cat_tools.routine__parse_arg_names_text(arguments text) +ok 48 - Permission denied trying to execute cat_tools.routine__parse_arg_types(arguments text) +ok 49 - Permission denied trying to execute cat_tools.routine__parse_arg_types_text(arguments text) +ok 50 - Permission denied trying to execute cat_tools.routine__type(prokind cat_tools.routine_prokind) +ok 51 - Permission denied trying to execute cat_tools.routine__volatility(provolatile cat_tools.routine_provolatile) +ok 52 - Permission denied trying to execute cat_tools.sequence__last(table_name text, column_name text) +ok 53 - Permission denied trying to execute cat_tools.sequence__next(table_name text, column_name text) +ok 54 - Permission denied trying to execute cat_tools.sequence__set_last(table_name text, column_name text, last_value bigint) +ok 55 - Permission denied trying to execute cat_tools.sequence__set_next(table_name text, column_name text, next_value bigint) +ok 56 - Permission denied trying to execute cat_tools.setval(table_name text, column_name text, new_value bigint, has_been_used boolean DEFAULT true) +ok 57 - Permission denied trying to execute cat_tools.trigger__args_as_text(function_arguments text[]) +ok 58 - Permission denied trying to execute cat_tools.trigger__get_oid(trigger_table regclass, trigger_name text) +ok 59 - Permission denied trying to execute cat_tools.trigger__get_oid__loose(trigger_table regclass, trigger_name text) +ok 60 - Permission denied trying to execute cat_tools.trigger__parse(trigger_oid oid, OUT trigger_table regclass, OUT timing text, OUT events text[], OUT defer text, OUT row_statement text, OUT when_clause text, OUT trigger_function regprocedure, OUT function_arguments text[]) +ok 61 - Permission denied trying to execute cat_tools.trigger__parse(trigger_table regclass, trigger_name text, OUT timing text, OUT events text[], OUT defer text, OUT row_statement text, OUT when_clause text, OUT trigger_function regprocedure, OUT function_arguments text[]) +ok 62 - Permission granted to execute cat_tools.currval(table_name text, column_name text) +ok 63 - Permission granted to execute cat_tools.enum_range(enum regtype) +ok 64 - Permission granted to execute cat_tools.enum_range_srf(enum regtype) +ok 65 - Permission granted to execute cat_tools.extension__schemas(extension_names name[]) +ok 66 - Permission granted to execute cat_tools.extension__schemas(extension_names text) +ok 67 - Permission granted to execute cat_tools.extension__schemas_unique(extension_names name[]) +ok 68 - Permission granted to execute cat_tools.extension__schemas_unique(extension_names text) +ok 69 - Permission granted to execute cat_tools.function__arg_types(arguments text) +ok 70 - Permission granted to execute cat_tools.function__arg_types_text(arguments text) +ok 71 - Permission granted to execute cat_tools.get_serial_sequence(table_name text, column_name text) +ok 72 - Permission granted to execute cat_tools.name__check(name_to_check text) +ok 73 - Permission granted to execute cat_tools.nextval(table_name text, column_name text) +ok 74 - Permission granted to execute cat_tools.object__address_classid(object_type cat_tools.object_type) +ok 75 - Permission granted to execute cat_tools.object__address_classid(object_type text) +ok 76 - Permission granted to execute cat_tools.object__catalog(object_type cat_tools.object_type) +ok 77 - Permission granted to execute cat_tools.object__catalog(object_type text) +ok 78 - Permission granted to execute cat_tools.object__is_address_unsupported(object_type cat_tools.object_type) +ok 79 - Permission granted to execute cat_tools.object__is_address_unsupported(object_type text) +ok 80 - Permission granted to execute cat_tools.object__is_shared(object_type cat_tools.object_type) +ok 81 - Permission granted to execute cat_tools.object__is_shared(object_type text) +ok 82 - Permission granted to execute cat_tools.object__reg_type(object_catalog regclass) +ok 83 - Permission granted to execute cat_tools.object__reg_type(object_type cat_tools.object_type) +ok 84 - Permission granted to execute cat_tools.object__reg_type(object_type text) +ok 85 - Permission granted to execute cat_tools.object__reg_type_catalog(object_identifier_type regtype) +ok 86 - Permission granted to execute cat_tools.objects__address_unsupported() +ok 87 - Permission granted to execute cat_tools.objects__address_unsupported_srf() +ok 88 - Permission granted to execute cat_tools.objects__shared() +ok 89 - Permission granted to execute cat_tools.objects__shared_srf() +ok 90 - Permission granted to execute cat_tools.pg_attribute__get(relation regclass, column_name name) +ok 91 - Permission granted to execute cat_tools.pg_class(rel regclass) +ok 92 - Permission granted to execute cat_tools.pg_extension__get(extension_name name) +ok 93 - Permission granted to execute cat_tools.regprocedure(function_name text, arguments text) +ok 94 - Permission granted to execute cat_tools.relation__column_names(relation regclass) +ok 95 - Permission granted to execute cat_tools.relation__is_catalog(relation regclass) +ok 96 - Permission granted to execute cat_tools.relation__is_temp(relation regclass) +ok 97 - Permission granted to execute cat_tools.relation__kind(relkind cat_tools.relation_relkind) +ok 98 - Permission granted to execute cat_tools.relation__kind(relkind text) +ok 99 - Permission granted to execute cat_tools.relation__relkind(kind cat_tools.relation_type) +ok 100 - Permission granted to execute cat_tools.relation__relkind(kind text) +ok 101 - Permission granted to execute cat_tools.routine__arg_names(func regprocedure) +ok 102 - Permission granted to execute cat_tools.routine__arg_names_text(func regprocedure) +ok 103 - Permission granted to execute cat_tools.routine__arg_types(func regprocedure) +ok 104 - Permission granted to execute cat_tools.routine__arg_types_text(func regprocedure) +ok 105 - Permission granted to execute cat_tools.routine__argument_mode(proargmode cat_tools.routine_proargmode) +ok 106 - Permission granted to execute cat_tools.routine__parallel_safety(proparallel cat_tools.routine_proparallel) +ok 107 - Permission granted to execute cat_tools.routine__parse_arg_names(arguments text) +ok 108 - Permission granted to execute cat_tools.routine__parse_arg_names_text(arguments text) +ok 109 - Permission granted to execute cat_tools.routine__parse_arg_types(arguments text) +ok 110 - Permission granted to execute cat_tools.routine__parse_arg_types_text(arguments text) +ok 111 - Permission granted to execute cat_tools.routine__type(prokind cat_tools.routine_prokind) +ok 112 - Permission granted to execute cat_tools.routine__volatility(provolatile cat_tools.routine_provolatile) +ok 113 - Permission granted to execute cat_tools.sequence__last(table_name text, column_name text) +ok 114 - Permission granted to execute cat_tools.sequence__next(table_name text, column_name text) +ok 115 - Permission granted to execute cat_tools.sequence__set_last(table_name text, column_name text, last_value bigint) +ok 116 - Permission granted to execute cat_tools.sequence__set_next(table_name text, column_name text, next_value bigint) +ok 117 - Permission granted to execute cat_tools.setval(table_name text, column_name text, new_value bigint, has_been_used boolean DEFAULT true) +ok 118 - Permission granted to execute cat_tools.trigger__args_as_text(function_arguments text[]) +ok 119 - Permission granted to execute cat_tools.trigger__get_oid(trigger_table regclass, trigger_name text) +ok 120 - Permission granted to execute cat_tools.trigger__get_oid__loose(trigger_table regclass, trigger_name text) +ok 121 - Permission granted to execute cat_tools.trigger__parse(trigger_oid oid, OUT trigger_table regclass, OUT timing text, OUT events text[], OUT defer text, OUT row_statement text, OUT when_clause text, OUT trigger_function regprocedure, OUT function_arguments text[]) +ok 122 - Permission granted to execute cat_tools.trigger__parse(trigger_table regclass, trigger_name text, OUT timing text, OUT events text[], OUT defer text, OUT row_statement text, OUT when_clause text, OUT trigger_function regprocedure, OUT function_arguments text[]) +ok 123 - Permission denied trying to use type cat_tools.column +ok 124 - Permission denied trying to use type cat_tools.constraint_type +ok 125 - Permission denied trying to use type cat_tools.object_type +ok 126 - Permission denied trying to use type cat_tools.pg_all_foreign_keys +ok 127 - Permission denied trying to use type cat_tools.pg_class_v +ok 128 - Permission denied trying to use type cat_tools.pg_extension_v +ok 129 - Permission denied trying to use type cat_tools.procedure_type +ok 130 - Permission denied trying to use type cat_tools.relation_relkind +ok 131 - Permission denied trying to use type cat_tools.relation_type +ok 132 - Permission denied trying to use type cat_tools.routine_argument +ok 133 - Permission denied trying to use type cat_tools.routine_argument_mode +ok 134 - Permission denied trying to use type cat_tools.routine_parallel_safety +ok 135 - Permission denied trying to use type cat_tools.routine_proargmode +ok 136 - Permission denied trying to use type cat_tools.routine_prokind +ok 137 - Permission denied trying to use type cat_tools.routine_proparallel +ok 138 - Permission denied trying to use type cat_tools.routine_provolatile +ok 139 - Permission denied trying to use type cat_tools.routine_type +ok 140 - Permission denied trying to use type cat_tools.routine_volatility +ok 141 - Permission granted to use type cat_tools.column +ok 142 - Permission granted to use type cat_tools.constraint_type +ok 143 - Permission granted to use type cat_tools.object_type +ok 144 - Permission granted to use type cat_tools.pg_all_foreign_keys +ok 145 - Permission granted to use type cat_tools.pg_class_v +ok 146 - Permission granted to use type cat_tools.pg_extension_v +ok 147 - Permission granted to use type cat_tools.procedure_type +ok 148 - Permission granted to use type cat_tools.relation_relkind +ok 149 - Permission granted to use type cat_tools.relation_type +ok 150 - Permission granted to use type cat_tools.routine_argument +ok 151 - Permission granted to use type cat_tools.routine_argument_mode +ok 152 - Permission granted to use type cat_tools.routine_parallel_safety +ok 153 - Permission granted to use type cat_tools.routine_proargmode +ok 154 - Permission granted to use type cat_tools.routine_prokind +ok 155 - Permission granted to use type cat_tools.routine_proparallel +ok 156 - Permission granted to use type cat_tools.routine_provolatile +ok 157 - Permission granted to use type cat_tools.routine_type +ok 158 - Permission granted to use type cat_tools.routine_volatility +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/relation__.out b/test/expected/relation__.out new file mode 100644 index 0000000..8958738 --- /dev/null +++ b/test/expected/relation__.out @@ -0,0 +1,44 @@ +\set ECHO none +1..41 +ok 1 - Verify count from kinds +ok 2 - Simple sanity check of relation__kind() +ok 3 - Simple sanity check of relation__relkind() +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') +ok 24 - Verify public has no perms +ok 25 - pg_catalog.pg_class is not a temp relation +ok 26 - Create temp table for testing +ok 27 - temp relation is correctly identified as temp +ok 28 - NULL input returns NULL (STRICT function) +ok 29 - Verify public has no perms +ok 30 - pg_catalog.pg_class is in pg_catalog schema +ok 31 - Create temp table for testing +ok 32 - temp relation is not in pg_catalog schema +ok 33 - NULL input returns NULL (STRICT function) +ok 34 - Verify public has no perms +ok 35 - Create temp table with multiple columns +ok 36 - Temp table returns expected column names +ok 37 - Drop middle column from temp table +ok 38 - Temp table with dropped column returns expected column names +ok 39 - Create test table with columns +ok 40 - Test table returns expected column names +ok 41 - NULL input returns NULL (STRICT function) +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/relation_type.out b/test/expected/relation_type.out deleted file mode 100644 index c8d4172..0000000 --- a/test/expected/relation_type.out +++ /dev/null @@ -1,34 +0,0 @@ -\set ECHO none -1..31 -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") -# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/expected/routine__.out b/test/expected/routine__.out new file mode 100644 index 0000000..5c09114 --- /dev/null +++ b/test/expected/routine__.out @@ -0,0 +1,92 @@ +\set ECHO none +1..89 +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') +ok 16 - Verify count from argument_modes +ok 17 - Simple sanity check of routine__argument_mode() +ok 18 - Simple sanity check of routine__argument_mode() with enum +ok 19 - SELECT cat_tools.routine__argument_mode('i'::cat_tools.routine_proargmode) +ok 20 - SELECT cat_tools.routine__argument_mode('o'::cat_tools.routine_proargmode) +ok 21 - SELECT cat_tools.routine__argument_mode('b'::cat_tools.routine_proargmode) +ok 22 - SELECT cat_tools.routine__argument_mode('v'::cat_tools.routine_proargmode) +ok 23 - SELECT cat_tools.routine__argument_mode('t'::cat_tools.routine_proargmode) +ok 24 - SELECT cat_tools.routine__argument_mode('i'::"char") +ok 25 - SELECT cat_tools.routine__argument_mode('o'::"char") +ok 26 - SELECT cat_tools.routine__argument_mode('b'::"char") +ok 27 - SELECT cat_tools.routine__argument_mode('v'::"char") +ok 28 - SELECT cat_tools.routine__argument_mode('t'::"char") +ok 29 - SELECT cat_tools.routine__argument_mode('i') +ok 30 - SELECT cat_tools.routine__argument_mode('o') +ok 31 - SELECT cat_tools.routine__argument_mode('b') +ok 32 - SELECT cat_tools.routine__argument_mode('v') +ok 33 - SELECT cat_tools.routine__argument_mode('t') +ok 34 - Verify count from volatilities +ok 35 - Simple sanity check of routine__volatility() +ok 36 - Simple sanity check of routine__volatility() with enum +ok 37 - SELECT cat_tools.routine__volatility('i'::cat_tools.routine_provolatile) +ok 38 - SELECT cat_tools.routine__volatility('s'::cat_tools.routine_provolatile) +ok 39 - SELECT cat_tools.routine__volatility('v'::cat_tools.routine_provolatile) +ok 40 - SELECT cat_tools.routine__volatility('i'::"char") +ok 41 - SELECT cat_tools.routine__volatility('s'::"char") +ok 42 - SELECT cat_tools.routine__volatility('v'::"char") +ok 43 - SELECT cat_tools.routine__volatility('i') +ok 44 - SELECT cat_tools.routine__volatility('s') +ok 45 - SELECT cat_tools.routine__volatility('v') +ok 46 - Verify count from parallel_safeties +ok 47 - Simple sanity check of routine__parallel_safety() +ok 48 - Simple sanity check of routine__parallel_safety() with enum +ok 49 - SELECT cat_tools.routine__parallel_safety('s'::cat_tools.routine_proparallel) +ok 50 - SELECT cat_tools.routine__parallel_safety('r'::cat_tools.routine_proparallel) +ok 51 - SELECT cat_tools.routine__parallel_safety('u'::cat_tools.routine_proparallel) +ok 52 - SELECT cat_tools.routine__parallel_safety('s'::"char") +ok 53 - SELECT cat_tools.routine__parallel_safety('r'::"char") +ok 54 - SELECT cat_tools.routine__parallel_safety('u'::"char") +ok 55 - SELECT cat_tools.routine__parallel_safety('s') +ok 56 - SELECT cat_tools.routine__parallel_safety('r') +ok 57 - SELECT cat_tools.routine__parallel_safety('u') +ok 58 - Verify public has no perms +ok 59 - Verify public has no perms +ok 60 - Security check should prevent execution when current_user != session_user +ok 61 - Verify routine__parse_arg_types() with INOUT and OUT +ok 62 - Verify routine__parse_arg_types() with just INOUT +ok 63 - Verify routine__parse_arg_types() with just OUT +ok 64 - Verify routine__parse_arg_types() with only inputs +ok 65 - Verify routine__parse_arg_names() with INOUT and OUT +ok 66 - Verify routine__parse_arg_names() with just INOUT +ok 67 - Verify routine__parse_arg_names() with just OUT +ok 68 - Verify routine__parse_arg_names() with only inputs +ok 69 - Create pg_temp.test_function(anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]) +ok 70 - Verify routine__arg_types() returns all argument types +ok 71 - Verify routine__arg_types() with IN arguments only +ok 72 - Verify routine__arg_types() with no arguments +ok 73 - Verify routine__arg_types() with VARIADIC argument +ok 74 - Verify routine__arg_names() returns argument names (unnamed function) +ok 75 - Create pg_temp.named_function with named arguments +ok 76 - Verify routine__arg_names() with named arguments +ok 77 - Verify routine__arg_names() with no arguments +ok 78 - Verify routine__arg_types_text() formatting +ok 79 - Verify routine__arg_types_text() with simple types +ok 80 - Verify routine__arg_types_text() with no arguments +ok 81 - Verify routine__arg_types_text() with VARIADIC +ok 82 - Verify routine__arg_names_text() formatting +ok 83 - Verify routine__arg_names_text() with unnamed arguments +ok 84 - Verify routine__arg_names_text() with built-in function +ok 85 - Verify routine__arg_names_text() with no arguments +ok 86 - Function cat_tools.routine__parse_arg_types(text) should not be security definer +ok 87 - Function cat_tools.routine__parse_arg_names(text) should not be security definer +ok 88 - Function cat_tools.routine__parse_arg_types_text(text) should not be security definer +ok 89 - Function cat_tools.routine__parse_arg_names_text(text) should not be security definer +# TRANSACTION INTENTIONALLY LEFT OPEN! diff --git a/test/load_new.sql b/test/load_new.sql deleted file mode 100644 index d87ae34..0000000 --- a/test/load_new.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Now load our extension. We don't use IF NOT EXISTs here because we want an - * error if the extension is already loaded (because we want to ensure we're - * getting the very latest version). - */ -CREATE EXTENSION cat_tools; diff --git a/test/setup.sql b/test/setup.sql index e0f6476..2578f15 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -33,4 +33,6 @@ SELECT array_to_string(array( ) $body$; + + -- vi: expandtab ts=2 sw=2 diff --git a/test/sql/function.sql b/test/sql/function.sql index 198ae72..d57ff99 100644 --- a/test/sql/function.sql +++ b/test/sql/function.sql @@ -3,40 +3,38 @@ \i test/setup.sql \set s cat_tools -CREATE TEMP VIEW func_calls AS - SELECT * FROM (VALUES - ('function__arg_types'::name, $$'x'$$::text) - , ('regprocedure'::name, $$'x', 'x'$$) - ) v(fname, args) -; -GRANT SELECT ON func_calls TO public; SELECT plan( - 0 - + (SELECT count(*)::int FROM func_calls) - - + 4 -- function__arg_types() - - + 2 -- regprocedure() + 1 -- regprocedure permission check + + 1 -- regprocedure() + + 4 -- deprecated function__arg_types() wrappers + + 2 -- security definer checks for _cat_tools helpers ); SET LOCAL ROLE :no_use_role; SELECT throws_ok( - format( - $$SELECT %I.%I( %L )$$ - , :'s', fname - , args - ) - , '42501' - , NULL - , 'Verify public has no perms' - ) - FROM func_calls -; - -SET LOCAL ROLE :use_role; + format($$SELECT %I.%I( 'x', 'x' )$$, :'s', 'regprocedure') + , '42501' + , NULL + , 'Verify public has no perms' +); + +/* + * Deprecated wrappers call through to routine__parse_arg_types, which has a + * security check that throws when current_user != session_user. SET SESSION + * AUTHORIZATION satisfies that check for the rest of this file. + */ +SET SESSION AUTHORIZATION :use_role; +SELECT is( + :s.regprocedure('array_length', 'anyarray, integer') + , 'array_length(anyarray,integer)'::regprocedure + , 'Verify regprocedure()' +); + +-- Test deprecated wrapper functions still work +\set VERBOSITY terse 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[] @@ -44,37 +42,38 @@ SELECT is( ); SELECT is( - :s.function__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' + :s.function__arg_types($$int, text$$) + , '{int,text}'::regtype[] + , 'Verify function__arg_types() with simple args' ); SELECT is( - :s.function__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' + :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($$anyelement, "char", pg_class, VARIADIC boolean[]$$) - , '{anyelement,"\"char\"",pg_class,boolean[]}'::regtype[] - , 'Verify function__arg_types() with only inputs' -); - -\set args 'anyarray, OUT text, OUT "char", pg_class, int, VARIADIC boolean[]' -SELECT lives_ok( - format( - $$CREATE FUNCTION pg_temp.test_function(%s) LANGUAGE plpgsql AS $body$BEGIN NULL; END$body$;$$ - , :'args' - ) - , format('Create pg_temp.test_function(%s)', :'args') + :s.function__arg_types_text($$int, text$$) + , 'integer, text' + , 'Verify function__arg_types_text() with simple args' ); -SELECT is( - :s.regprocedure( 'pg_temp.test_function', :'args' ) - , 'pg_temp.test_function'::regproc::regprocedure - , 'Verify regprocedure()' -); +/* + * CRITICAL SECURITY TESTS: Helper functions must NOT be SECURITY DEFINER. + * If they were, they could be exploited for SQL injection since they execute + * dynamic SQL. + */ + +\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[]); \i test/pgxntool/finish.sql 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/permissions.sql b/test/sql/permissions.sql new file mode 100644 index 0000000..2867317 --- /dev/null +++ b/test/sql/permissions.sql @@ -0,0 +1,81 @@ +\set ECHO none + +\i test/setup.sql + +/* + * Dynamically verify the permission model for the entire cat_tools public API: + * - no_use_role (no cat_tools__usage grant) cannot use any type or execute any function + * - use_role (has cat_tools__usage) can use all types and execute all functions + * + * Views are created as superuser so both roles can read them via explicit grants. + */ +CREATE TEMP VIEW cat_types 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 SELECT ON cat_types TO :use_role, :no_use_role; + +CREATE TEMP VIEW cat_functions AS +SELECT p.oid, p.proname, pg_get_function_arguments(p.oid) AS args + FROM pg_proc p + WHERE p.pronamespace = 'cat_tools'::regnamespace + ORDER BY proname, pg_get_function_arguments(p.oid) +; +GRANT SELECT ON cat_functions TO :use_role, :no_use_role; + +SELECT plan( + (SELECT count(*)::int FROM cat_types) * 2 -- no_use throws + use lives + + (SELECT count(*)::int FROM cat_functions) * 2 -- no_use denied + use allowed +); + +/* + * Function privilege checks via pg_catalog privilege functions. + * has_function_privilege(user, func, priv) checks the named role's privilege + * including inherited roles, independent of the current session role. + */ +SELECT is( + has_function_privilege(:'no_use_role', oid, 'EXECUTE') + , false + , format('Permission denied trying to execute cat_tools.%s(%s)', proname, args) + ) + FROM cat_functions +; + +SELECT is( + has_function_privilege(:'use_role', oid, 'EXECUTE') + , true + , format('Permission granted to execute cat_tools.%s(%s)', proname, args) + ) + FROM cat_functions +; + +/* + * Type access checks via role switching. + * Attempts actual casts to verify enforcement at the SQL level. + */ +SET LOCAL ROLE :no_use_role; + +SELECT throws_ok( + format('SELECT NULL::%s', type_name) + , '42501' + , NULL + , format('Permission denied trying to use type %s', type_name) + ) + FROM cat_types +; + +SET LOCAL ROLE :use_role; + +SELECT lives_ok( + format('SELECT NULL::%s', type_name) + , format('Permission granted to use type %s', type_name) + ) + FROM cat_types +; + +\i test/pgxntool/finish.sql + +-- vi: expandtab ts=2 sw=2 diff --git a/test/sql/relation__.sql b/test/sql/relation__.sql new file mode 100644 index 0000000..5574f11 --- /dev/null +++ b/test/sql/relation__.sql @@ -0,0 +1,180 @@ +\set ECHO none + +\i test/setup.sql + +\set s cat_tools + +SET LOCAL ROLE :use_role; +CREATE TEMP VIEW kinds AS + SELECT + (cat_tools.enum_range('cat_tools.relation_type'))[gs] AS kind + , (cat_tools.enum_range('cat_tools.relation_relkind'))[gs] AS relkind + FROM generate_series( + 1 + , greatest( + array_upper(cat_tools.enum_range('cat_tools.relation_type'), 1) + , array_upper(cat_tools.enum_range('cat_tools.relation_relkind'), 1) + ) + ) gs +; + +SELECT plan( + (1 + 2 + 2 * (SELECT count(*)::int FROM kinds)) -- relation_type enum mapping + + 5 -- relation__is_temp + + 5 -- relation__is_catalog + + 8 -- relation__column_names +); + +-- relation_type enum mapping +SELECT is( + (SELECT count(*)::int FROM kinds) + , 10 + , 'Verify count from kinds' +); + +SELECT is( + cat_tools.relation__kind('r') + , 'table' + , 'Simple sanity check of relation__kind()' +); +SELECT is( + cat_tools.relation__relkind('table') + , 'r' + , 'Simple sanity check of relation__relkind()' +); + +SELECT is(cat_tools.relation__relkind(kind)::text, relkind, format('SELECT cat_tools.relation_relkind(%L)', kind)) + FROM kinds +; + +SELECT is(cat_tools.relation__kind(relkind)::text, kind, format('SELECT cat_tools.relation_type(%L)', relkind)) + FROM kinds +; + +-- relation__is_temp +\set f relation__is_temp + +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 is_temp_test()$$, 'Create temp table for testing'); + +SELECT is( + cat_tools.relation__is_temp('is_temp_test'::regclass) + , true + , 'temp relation is correctly identified as temp' +); + +SELECT is( + cat_tools.relation__is_temp(NULL) + , NULL + , 'NULL input returns NULL (STRICT function)' +); + +-- relation__is_catalog +\set f relation__is_catalog + +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 is_catalog_test()$$, 'Create temp table for testing'); + +SELECT is( + cat_tools.relation__is_catalog('is_catalog_test'::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)' +); + +-- relation__column_names +\set f relation__column_names + +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 diff --git a/test/sql/relation_type.sql b/test/sql/relation_type.sql deleted file mode 100644 index 69beaf1..0000000 --- a/test/sql/relation_type.sql +++ /dev/null @@ -1,85 +0,0 @@ -\set ECHO none - -\i test/setup.sql - --- test_role is set in test/deps.sql - -SET LOCAL ROLE :use_role; -CREATE TEMP VIEW kinds AS - SELECT - (cat_tools.enum_range('cat_tools.relation_type'))[gs] AS kind - , (cat_tools.enum_range('cat_tools.relation_relkind'))[gs] AS relkind - FROM generate_series( - 1 - , greatest( - array_upper(cat_tools.enum_range('cat_tools.relation_type'), 1) - , array_upper(cat_tools.enum_range('cat_tools.relation_relkind'), 1) - ) - ) gs -; - -SELECT plan( - 1 - + 2 -- Simple is() tests - + 4 -- no_use tests - + 3 * (SELECT count(*)::int FROM kinds) -); - -SELECT is( - (SELECT count(*)::int FROM kinds) - , 8 - , 'Verify count from kinds' -); - -SELECT is( - cat_tools.relation__kind('r') - , 'table'::cat_tools.relation_type - , 'Simple sanity check of relation__kind()' -); -SELECT is( - cat_tools.relation__relkind('table') - , 'r'::cat_tools.relation_relkind - , '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 -; - -SELECT is(cat_tools.relation__kind(relkind)::text, kind, format('SELECT cat_tools.relation_type(%L)', relkind)) - 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__.sql b/test/sql/routine__.sql new file mode 100644 index 0000000..64e0ca7 --- /dev/null +++ b/test/sql/routine__.sql @@ -0,0 +1,425 @@ +\set ECHO none + +\i test/setup.sql + +\set s cat_tools + +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 +; + +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 +; + +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 +; + +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 + 3 * (SELECT count(*)::int FROM routine_kinds)) -- routine__type + + (1 + 2 + 3 * (SELECT count(*)::int FROM argument_modes)) -- routine__argument_mode + + (1 + 2 + 3 * (SELECT count(*)::int FROM volatilities)) -- routine__volatility + + (1 + 2 + 3 * (SELECT count(*)::int FROM parallel_safeties)) -- routine__parallel_safety + + 32 -- routine__parse_arg_*, routine__arg_*, and security definer checks for routine__ callers +); + +-- routine__type +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 +; + +-- routine__argument_mode +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 +; + +-- routine__volatility +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 +; + +-- routine__parallel_safety +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 +; + +CREATE TEMP VIEW func_calls AS + SELECT * FROM (VALUES + ('routine__parse_arg_types'::name, $$'x'$$::text) + , ('routine__parse_arg_names'::name, $$'x'$$::text) + ) v(fname, args) +; +GRANT SELECT ON func_calls TO public; + +SET LOCAL ROLE :no_use_role; + +SELECT throws_ok( + format( + $$SELECT %I.%I( %L )$$ + , :'s', fname + , args + ) + , '42501' + , NULL + , 'Verify public has no perms' + ) + 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.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 routine__parse_arg_types() with INOUT and OUT' +); + +SELECT is( + :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 routine__parse_arg_types() with just INOUT' +); + +SELECT is( + :s.routine__parse_arg_types($$IN in_int int, OUT out_char "char", anyarray, anyelement, boolean DEFAULT false$$) + , '{int,anyarray,anyelement,boolean}'::regtype[] + , 'Verify routine__parse_arg_types() with just OUT' +); + +SELECT is( + :s.routine__parse_arg_types($$anyelement, "char", pg_class, VARIADIC boolean[]$$) + , '{anyelement,"\"char\"",pg_class,boolean[]}'::regtype[] + , '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( + $$CREATE FUNCTION pg_temp.test_function(%s) LANGUAGE plpgsql AS $body$BEGIN NULL; END$body$;$$ + , :'args' + ) + , 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' +); + +/* + * CRITICAL SECURITY TESTS: public routine__ functions must NOT be SECURITY DEFINER. + * If they were, they could be exploited for SQL injection since they execute + * dynamic SQL with elevated privileges. + */ + +-- 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