Skip to content

Commit

Permalink
Use to_regtypemod on Postgres 17
Browse files Browse the repository at this point in the history
When running on Postgres 17 or higher, take advantage of the new
`to_regtypemod` function to much more correctly and efficiently
normalize type names, including their type modifiers. Notably, interval
types will now also be normalized, so note this fact in the docs.

Update the CI testing to test on Postgres 17 and adjust patch offsets
for the line count change in `pgtap.sql.in`.
  • Loading branch information
theory committed Apr 1, 2024
1 parent f5d166b commit b0184e4
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 52 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
strategy:
matrix:
include:
- { version: 16, upgrade_to: "", update_from: 0.99.0 }
- { version: 17, upgrade_to: "", update_from: 0.99.0 }
- { version: 16, upgrade_to: 17, update_from: 0.99.0 }
- { version: 15, upgrade_to: 16, update_from: 0.99.0 }
- { version: 14, upgrade_to: 15, update_from: 0.99.0 }
- { version: 13, upgrade_to: 14, update_from: 0.99.0 }
Expand All @@ -24,8 +25,8 @@ jobs:
- { version: 9.2, upgrade_to: 9.3, update_from: "" } # updatecheck is not supported prior to 9.3
- { version: 9.1, upgrade_to: 9.2, update_from: "" } # updatecheck is not supported prior to 9.3
# Also test pg_upgrade across many versions
- { version: 9.2, upgrade_to: 16, update_from: "", suffix: –16 }
- { version: 9.4, upgrade_to: 16, update_from: "", suffix: –16 }
- { version: 9.2, upgrade_to: 17, update_from: "", suffix: –17 }
- { version: 9.4, upgrade_to: 17, update_from: "", suffix: –17 }
name: 🐘 PostgreSQL ${{ matrix.version }}${{ matrix.suffix }}
runs-on: ubuntu-latest
container: pgxn/pgxn-tools
Expand Down
3 changes: 2 additions & 1 deletion Changes
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ Revision history for pgTAP

1.3.3
--------------------------

* Improved the correctness and performance of `col_type_is` on Postgres 17
thanks to the introduction of the `to_regtypemod()` function.
* Fix bug introduced in v1.3.2 where `col_type_is` throws an error when the
type isn't in the search path. Thanks to Erik Wienhold for the PR (#332)!
* Removed the reference to PGUnit from the docs, as the project seems to have
Expand Down
6 changes: 3 additions & 3 deletions compat/install-9.1.patch
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
RETURN ok( FALSE, descr ) || E'\n' || diag(
' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname)
);
@@ -6688,10 +6684,6 @@
@@ -6698,10 +6694,6 @@
-- Something went wrong. Record that fact.
errstate := SQLSTATE;
errmsg := SQLERRM;
Expand All @@ -22,15 +22,15 @@
END;

-- Always raise an exception to rollback any changes.
@@ -7159,7 +7151,6 @@
@@ -7169,7 +7161,6 @@
RETURN ok( true, $3 );
EXCEPTION
WHEN datatype_mismatch THEN
- GET STACKED DIAGNOSTICS err_msg = MESSAGE_TEXT;
RETURN ok( false, $3 ) || E'\n' || diag(
E' Number of columns or their types differ between the queries' ||
CASE WHEN have_rec::TEXT = want_rec::text THEN '' ELSE E':\n' ||
@@ -7313,7 +7304,6 @@
@@ -7323,7 +7314,6 @@
RETURN ok( false, $3 );
EXCEPTION
WHEN datatype_mismatch THEN
Expand Down
2 changes: 1 addition & 1 deletion compat/install-9.2.patch
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
RETURN ok( FALSE, descr ) || E'\n' || diag(
' died: ' || _error_diag(SQLSTATE, SQLERRM, detail, hint, context, schname, tabname, colname, chkname, typname)
);
@@ -6696,12 +6691,7 @@
@@ -6706,12 +6701,7 @@
GET STACKED DIAGNOSTICS
detail = PG_EXCEPTION_DETAIL,
hint = PG_EXCEPTION_HINT,
Expand Down
2 changes: 1 addition & 1 deletion compat/install-9.4.patch
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
-- There should have been no exception.
GET STACKED DIAGNOSTICS
detail = PG_EXCEPTION_DETAIL,
@@ -10240,233 +10240,6 @@
@@ -10250,233 +10250,6 @@
), $2);
$$ LANGUAGE SQL immutable;

Expand Down
2 changes: 1 addition & 1 deletion compat/install-9.6.patch
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- sql/pgtap.sql
+++ sql/pgtap.sql
@@ -10222,136 +10222,6 @@
@@ -10232,136 +10232,6 @@
);
$$ LANGUAGE sql;

Expand Down
14 changes: 8 additions & 6 deletions doc/pgtap.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -4520,12 +4520,13 @@ will fail if the table or column in question does not exist.

The type argument may be formatted using the full name of the type or any
supported alias. For example, if you created a `varchar(64)` column, you can
pass the type as either "varchar(64)" or "character varying(64)". Example:
pass the type as either "varchar(64)" or "character varying(64)". Same deal
for timestamps, as in this example:

SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'timespantz(3)' );
SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'timestamptz(3)' );

The exception to this rule is interval types, which must be specified as
rendered by PostgreSQL itself:
The exception to this rule is interval types prior to Postgres 17, which must
be specified as rendered by PostgreSQL itself:

SELECT col_type_is( 'myschema', 'sometable', 'somecolumn', 'interval second(3)' );

Expand Down Expand Up @@ -8316,8 +8317,9 @@ the calling `format_type()` with the type OID and type modifier that define
the column, but returns a `NULL` on an invalid or missing type, rather than
raising an error. Types can be defined by their canonical names or their
aliases, e.g., `character varying` or `varchar`. The exception is `interval`
types, which must be specified exactly as Postgres renders them internally,
e.g., `'interval(0)`, `interval second(0)`, or `interval day to second(4)`.
types prior to Postgres 17, which must be specified exactly as Postgres
renders them internally, e.g., `'interval(0)`, `interval second(0)`, or
`interval day to second(4)`.

try=# SELECT format_type_string('timestamp(3)');
format_type_string
Expand Down
46 changes: 28 additions & 18 deletions sql/pgtap--1.3.2--1.3.3.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,41 @@ CREATE OR REPLACE FUNCTION format_type_string ( TEXT )
RETURNS TEXT AS $$
DECLARE
want_type TEXT := $1;
typmodin_arg cstring[];
typmodin_func regproc;
typmod int;
BEGIN
IF pg_version_num() >= 170000 THEN
-- to_regtypemod() in 17 allows easy and corret normalization.
RETURN format_type(to_regtype(want_type), to_regtypemod(want_type));
END IF;

IF want_type::regtype = 'interval'::regtype THEN
-- RAISE NOTICE 'cannot resolve: %', want_type; -- TODO
-- We cannot normlize interval types without to_regtypemod(), So
-- just return it as is.
RETURN want_type;
END IF;

-- Extract type modifier from type declaration and format as cstring[] literal.
typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}');
-- Use the typmodin functions to correctly normalize types.
DECLARE
typmodin_arg cstring[];
typmodin_func regproc;
typmod int;
BEGIN
-- Extract type modifier from type declaration and format as cstring[] literal.
typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}');

-- Find typmodin function for want_type.
SELECT typmodin INTO typmodin_func
FROM pg_catalog.pg_type
WHERE oid = want_type::regtype;
-- Find typmodin function for want_type.
SELECT typmodin INTO typmodin_func
FROM pg_catalog.pg_type
WHERE oid = want_type::regtype;

IF typmodin_func = 0 THEN
-- Easy: types without typemods.
RETURN format_type(want_type::regtype, null);
END IF;
IF typmodin_func = 0 THEN
-- Easy: types without typemods.
RETURN format_type(want_type::regtype, null);
END IF;

-- Get typemod via type-specific typmodin function.
EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod;
RETURN format_type(want_type::regtype, typmod);
EXCEPTION WHEN OTHERS THEN RETURN NULL;
-- Get typemod via type-specific typmodin function.
EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod;
RETURN format_type(want_type::regtype, typmod);
END;
EXCEPTION WHEN OTHERS THEN RETURN NULL;
END;
$$ LANGUAGE PLPGSQL STABLE;
46 changes: 28 additions & 18 deletions sql/pgtap.sql.in
Original file line number Diff line number Diff line change
Expand Up @@ -1465,32 +1465,42 @@ CREATE OR REPLACE FUNCTION format_type_string ( TEXT )
RETURNS TEXT AS $$
DECLARE
want_type TEXT := $1;
typmodin_arg cstring[];
typmodin_func regproc;
typmod int;
BEGIN
IF pg_version_num() >= 170000 THEN
-- to_regtypemod() in 17 allows easy and corret normalization.
RETURN format_type(to_regtype(want_type), to_regtypemod(want_type));
END IF;

IF want_type::regtype = 'interval'::regtype THEN
-- RAISE NOTICE 'cannot resolve: %', want_type; -- TODO
-- We cannot normlize interval types without to_regtypemod(), So
-- just return it as is.
RETURN want_type;
END IF;

-- Extract type modifier from type declaration and format as cstring[] literal.
typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}');
-- Use the typmodin functions to correctly normalize types.
DECLARE
typmodin_arg cstring[];
typmodin_func regproc;
typmod int;
BEGIN
-- Extract type modifier from type declaration and format as cstring[] literal.
typmodin_arg := translate(substring(want_type FROM '[(][^")]+[)]'), '()', '{}');

-- Find typmodin function for want_type.
SELECT typmodin INTO typmodin_func
FROM pg_catalog.pg_type
WHERE oid = want_type::regtype;
-- Find typmodin function for want_type.
SELECT typmodin INTO typmodin_func
FROM pg_catalog.pg_type
WHERE oid = want_type::regtype;

IF typmodin_func = 0 THEN
-- Easy: types without typemods.
RETURN format_type(want_type::regtype, null);
END IF;
IF typmodin_func = 0 THEN
-- Easy: types without typemods.
RETURN format_type(want_type::regtype, null);
END IF;

-- Get typemod via type-specific typmodin function.
EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod;
RETURN format_type(want_type::regtype, typmod);
EXCEPTION WHEN OTHERS THEN RETURN NULL;
-- Get typemod via type-specific typmodin function.
EXECUTE format('SELECT %s(%L)', typmodin_func, typmodin_arg) INTO typmod;
RETURN format_type(want_type::regtype, typmod);
END;
EXCEPTION WHEN OTHERS THEN RETURN NULL;
END;
$$ LANGUAGE PLPGSQL STABLE;

Expand Down

0 comments on commit b0184e4

Please sign in to comment.