From 313458b6aa94eb2d4d5e45b2f6497fab99cb4ca0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 8 Mar 2024 20:40:43 -0500 Subject: [PATCH] Add to_regtypemod() SQL function The `to_regtypemod()` function uses the underlying `parseTypeString()` C function to parse a string representing a data type into a type ID (which id discards) and a typmod, which it returns. This value is suitable for passing as the second argument to `format_type()`, which allow one to derive the formal SQL typmod from a string: SELECT format_type( to_regtype('varchar(32)'), to_regtypemod('varchar(32)') ); format_type ----------------------- character varying(32) This function also resolves types whose typmod is determined by the SQL parser or some step after that, such as interval types where the stored field option is encoded in the typmod: SELECT format_type( to_regtype('interval second(0)'), to_regtypemod('interval second(0)') ); format_type -------------------- interval second(0) Useful for unit tests for against column data types, for example. Based on code originally written by Erik Wienhold for use in pgTAP. Like `to_regtype()`, `to_regtypemod()` raises an error when the `parseTypeString()` function drops into the SQL grammar parser raises an error. So invalid type names like `inteval` or `interval nonesuch` raise an error, rather than return `NULL`. Note this behavior in the docs, and also update the documentation for `to_regtype()` to note this behavior and cite examples. While at it, fix an "use of undefined value in sprintf" error in `genbki.pl` that arose when I had specified `prorettype => 'integer'` instead of `prorettype => 'int4'` in `pg_proc.dat`. I only realized the invalid type name when I fixed the Perl bug. --- doc/src/sgml/func.sgml | 54 +++++++++++++++++++--- src/backend/catalog/genbki.pl | 2 +- src/backend/utils/adt/regproc.c | 29 ++++++++++++ src/include/catalog/pg_proc.dat | 3 ++ src/test/regress/expected/regproc.out | 66 +++++++++++++++++++++++++++ src/test/regress/sql/regproc.sql | 14 ++++++ 6 files changed, 160 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 5b225ccf4f5f1..31321f27b0ba4 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -24872,7 +24872,7 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); - + format_type @@ -25570,7 +25570,7 @@ SELECT collation for ('foo' COLLATE "de_DE"); - + to_regtype @@ -25578,13 +25578,53 @@ SELECT collation for ('foo' COLLATE "de_DE"); regtype - Translates a textual type name to its OID. A similar result is - obtained by casting the string to type regtype (see - ); however, this function will return - NULL rather than throwing an error if the name is - not found. + Parses a string of text, extracts a potential type name from it, and + translates that name into an OID. A similar result is obtained by + casting the string to type regtype (see + ). Failure to extract a valid potential + type name results in an error. For example: + +SELECT to_regtype('interval nonesuch'); +ERROR: syntax error at or near "nonesuch" +LINE 1: select to_regtype('interval nonesuch'); + ^ +CONTEXT: invalid type name "interval nonesuch" + + However, if the extracted name is not known to the system, this function + will return NULL. For example: + +SELECT to_regtype('party'); + to_regtype +------------ + + + + + + + + + to_regtypemod + + to_regtypemod ( text ) + integer + + + Parses a string of text, extracts a potential type name from it, and + translates its type modifier, if any. Failure to extract a valid + potential type name results in an error; however, if the extracted name + is not known to the system, this function will return + NULL. Complements , and + can be passed to . For example: + +SELECT format_type(to_regtype('varchar(32)'), to_regtypemod('varchar(32)')); + format_type +----------------------- + character varying(32) + + diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index 94afdc5491dd8..a0c299f067b6b 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -1098,7 +1098,7 @@ sub lookup_oids { warn sprintf "unresolved OID reference \"%s\" in %s.dat field %s line %s\n", - $lookupname, $catname, $attname, $bki_values->{line_number}; + $lookupname || '', $catname || '', $attname || '', $bki_values->{line_number} || ''; $num_errors++; } } diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c index 1e3bf3f5fd6f6..75b8429e0d240 100644 --- a/src/backend/utils/adt/regproc.c +++ b/src/backend/utils/adt/regproc.c @@ -1220,6 +1220,35 @@ to_regtype(PG_FUNCTION_ARGS) PG_RETURN_DATUM(result); } + +/* + * to_regtypemod() complements to_regtype, returning the typmod for the type, + * if any. + * + * If the type name is not found, we return NULL. + * + * Internally it relies on the Postgres core parseTypeString() function defined + * in src/backend/parser/parse_type.c. + */ +Datum +to_regtypemod(PG_FUNCTION_ARGS) +{ + char *typ_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Oid typid; + int32 typmod; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + /* + * Parse type-name argument to obtain the encoded typmod. Return NULL + * on failure. + */ + if (!parseTypeString(typ_name, &typid, &typmod, (Node *) &escontext)) { + PG_RETURN_NULL(); + } + + PG_RETURN_INT32(typmod); +} + /* * regtypeout - converts type OID to "typ_name" */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 177d81a891c33..770591c7346ee 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7155,6 +7155,9 @@ { oid => '3493', descr => 'convert type name to regtype', proname => 'to_regtype', provolatile => 's', prorettype => 'regtype', proargtypes => 'text', prosrc => 'to_regtype' }, +{ oid => '8401', descr => 'convert type name to type mod', + proname => 'to_regtypemod', provolatile => 's', prorettype => 'int4', + proargtypes => 'text', prosrc => 'to_regtypemod' }, { oid => '1079', descr => 'convert text to regclass', proname => 'regclass', provolatile => 's', prorettype => 'regclass', proargtypes => 'text', prosrc => 'text_regclass' }, diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out index a9420850b87ec..ff1ee5b52c531 100644 --- a/src/test/regress/expected/regproc.out +++ b/src/test/regress/expected/regproc.out @@ -544,3 +544,69 @@ SELECT * FROM pg_input_error_info('way.too.many.names', 'regtype'); ERROR: improper qualified name (too many dotted names): way.too.many.names SELECT * FROM pg_input_error_info('no_such_catalog.schema.name', 'regtype'); ERROR: cross-database references are not implemented: no_such_catalog.schema.name +-- Test to_regtypemod +SELECT to_regtypemod('text'); + to_regtypemod +--------------- + -1 +(1 row) + +SELECT to_regtypemod('timestamp(4)'); + to_regtypemod +--------------- + 4 +(1 row) + +SELECT to_regtypemod('interval(0)'); + to_regtypemod +--------------- + 2147418112 +(1 row) + +SELECT to_regtypemod('interval second(0)'); + to_regtypemod +--------------- + 268435456 +(1 row) + +SELECT to_regtypemod('timestamptz'); + to_regtypemod +--------------- + -1 +(1 row) + +SELECT to_regtypemod('timestamptz(6)'); + to_regtypemod +--------------- + 6 +(1 row) + +SELECT to_regtypemod('varchar'); + to_regtypemod +--------------- + -1 +(1 row) + +SELECT to_regtypemod('varchar(128)'); + to_regtypemod +--------------- + 132 +(1 row) + +SELECT to_regtypemod(NULL); -- returns null on null input + to_regtypemod +--------------- + +(1 row) + +SELECT to_regtypemod('year(4)'); -- error trapped, returns null + to_regtypemod +--------------- + +(1 row) + +SELECT to_regtypemod('interval nonesuch'); -- grammar error raised, not trapped +ERROR: syntax error at or near "nonesuch" +LINE 1: SELECT to_regtypemod('interval nonesuch'); + ^ +CONTEXT: invalid type name "interval nonesuch" diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql index de2aa881a8dc0..c1717e1f8fc43 100644 --- a/src/test/regress/sql/regproc.sql +++ b/src/test/regress/sql/regproc.sql @@ -145,3 +145,17 @@ SELECT * FROM pg_input_error_info('incorrect type name syntax', 'regtype'); SELECT * FROM pg_input_error_info('numeric(1,2,3)', 'regtype'); -- bogus typmod SELECT * FROM pg_input_error_info('way.too.many.names', 'regtype'); SELECT * FROM pg_input_error_info('no_such_catalog.schema.name', 'regtype'); + +-- Test to_regtypemod +SELECT to_regtypemod('text'); +SELECT to_regtypemod('timestamp(4)'); +SELECT to_regtypemod('interval(0)'); +SELECT to_regtypemod('interval second(0)'); +SELECT to_regtypemod('timestamptz'); +SELECT to_regtypemod('timestamptz(6)'); +SELECT to_regtypemod('varchar'); +SELECT to_regtypemod('varchar(128)'); + +SELECT to_regtypemod(NULL); -- returns null on null input +SELECT to_regtypemod('year(4)'); -- error trapped, returns null +SELECT to_regtypemod('interval nonesuch'); -- grammar error raised, not trapped