On 2024-02-12 19:20 +0100, Tom Lane wrote:
> I wrote:
> > It strikes me that this is basically to_regtype() with the additional
> > option to return the typmod.  That leads to some questions:
> 
> BTW, another way that this problem could be approached is to use
> to_regtype() as-is, with a separate function to obtain the typmod:
> 
> select format_type(to_regtype('timestamp(4)'), to_regtypmod('timestamp(4)'));
> 
> This is intellectually ugly, since it implies parsing the same
> typename string twice.  But on the other hand it avoids the notational
> pain and runtime overhead involved in using a record-returning
> function.  So I think it might be roughly a wash for performance.
> Question to think about is which way is easier to use.  I don't
> have an opinion particularly; just throwing the idea out there.

Out of curiosity, I benchmarked this with the attached to_regtypmod()
patch based on David's v5 applied to a6c21887a9.  The script running
pgbench and its output are included at the end.

Just calling parse_type() vs to_regtype()/to_regtypmod() is a wash for
performance as you thought.  But format_type() performs better with
to_regtypmod() than with parse_type().  Accessing the record fields
returned by parse_type() adds some overhead.

to_regtypmod() is better for our use case in pgTAP which relies on
format_type() to normalize the type name.  The implementation of
to_regtypmod() is also simpler than parse_type().  Usage-wise, both are
clunky IMO.

Benchmark script:

        #!/usr/bin/env bash
        
        set -eu
        
        cat <<'SQL' > parse_type.sql
        SELECT parse_type('interval second(0)');
        SQL
        
        cat <<'SQL' > parse_type_and_format.sql
        SELECT format_type(p.typid, p.typmod) FROM parse_type('interval 
second(0)') p;
        SQL
        
        cat <<'SQL' > to_regtypmod.sql
        SELECT to_regtype('interval second(0)'), to_regtypmod('interval 
second(0)');
        SQL
        
        cat <<'SQL' > to_regtypmod_and_format.sql
        SELECT format_type(to_regtype('interval second(0)'), 
to_regtypmod('interval second(0)'));
        SQL
        
        for f in \
            parse_type.sql \
            parse_type_and_format.sql \
            to_regtypmod.sql \
            to_regtypmod_and_format.sql
        do
            pgbench -n -f "$f" -T10 postgres
            echo
        done

pgbench output:

        pgbench (17devel)
        transaction type: parse_type.sql
        scaling factor: 1
        query mode: simple
        number of clients: 1
        number of threads: 1
        maximum number of tries: 1
        duration: 10 s
        number of transactions actually processed: 277017
        number of failed transactions: 0 (0.000%)
        latency average = 0.036 ms
        initial connection time = 1.623 ms
        tps = 27706.005513 (without initial connection time)
        
        pgbench (17devel)
        transaction type: parse_type_and_format.sql
        scaling factor: 1
        query mode: simple
        number of clients: 1
        number of threads: 1
        maximum number of tries: 1
        duration: 10 s
        number of transactions actually processed: 222487
        number of failed transactions: 0 (0.000%)
        latency average = 0.045 ms
        initial connection time = 1.603 ms
        tps = 22252.095670 (without initial connection time)
        
        pgbench (17devel)
        transaction type: to_regtypmod.sql
        scaling factor: 1
        query mode: simple
        number of clients: 1
        number of threads: 1
        maximum number of tries: 1
        duration: 10 s
        number of transactions actually processed: 276134
        number of failed transactions: 0 (0.000%)
        latency average = 0.036 ms
        initial connection time = 1.570 ms
        tps = 27617.628259 (without initial connection time)
        
        pgbench (17devel)
        transaction type: to_regtypmod_and_format.sql
        scaling factor: 1
        query mode: simple
        number of clients: 1
        number of threads: 1
        maximum number of tries: 1
        duration: 10 s
        number of transactions actually processed: 270820
        number of failed transactions: 0 (0.000%)
        latency average = 0.037 ms
        initial connection time = 1.631 ms
        tps = 27086.331104 (without initial connection time)

-- 
Erik
>From 0b60432a84d63a7fccaae0fe123a0aa2ae67493b Mon Sep 17 00:00:00 2001
From: Erik Wienhold <e...@ewie.name>
Date: Sun, 18 Feb 2024 17:33:35 +0100
Subject: [PATCH] Add to_regtypmod() for benchmarking against parse_type()

---
 src/backend/utils/adt/regproc.c       | 18 ++++++++++
 src/include/catalog/pg_proc.dat       |  3 ++
 src/test/regress/expected/regproc.out | 51 +++++++++++++++++++++++++++
 src/test/regress/sql/regproc.sql      | 26 ++++++++++++++
 4 files changed, 98 insertions(+)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 4fee27a139..6285dc7192 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -2067,3 +2067,21 @@ parse_type(PG_FUNCTION_ARGS)
        return HeapTupleGetDatum(rettuple);
 #undef PARSE_TYPE_STRING_COLS
 }
+
+Datum
+to_regtypmod(PG_FUNCTION_ARGS)
+{
+       const char *type;            /* the type string we want to resolve */
+       Oid         typid;           /* the resolved type oid */
+       int32       typmod;          /* the resolved type modifier */
+
+       type = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+       /*
+        * Parse type-name argument to obtain type OID and encoded typmod. We 
don't
+        * need to handle parseTypeString failure, just let the error be raised.
+        */
+       (void) parseTypeString(type, &typid, &typmod, NULL);
+
+       PG_RETURN_INT32(typmod);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6cad101bca..befeaeec37 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -2190,6 +2190,9 @@
   proargtypes => 'text', proallargtypes => '{text,oid,int4}',
   proargmodes => '{i,o,o}', proargnames => '{typname,typid,typmod}',
   prosrc => 'parse_type' },
+{ oid => '8402', descr => 'covert type name to typmod',
+  proname => 'to_regtypmod', provolatile => 's', prorettype => 'int4',
+  proargtypes => 'text', prosrc => 'to_regtypmod' },
 { oid => '1084', descr => 'I/O',
   proname => 'date_in', provolatile => 's', prorettype => 'date',
   proargtypes => 'cstring', prosrc => 'date_in' },
diff --git a/src/test/regress/expected/regproc.out 
b/src/test/regress/expected/regproc.out
index 033f6a81a5..c548b320c3 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -595,3 +595,54 @@ SELECT format_type(typid, typmod) FROM p;
  character varying(128)
 (7 rows)
 
+-- Test to_regtypmod
+SELECT * FROM to_regtypmod('text');
+ to_regtypmod 
+--------------
+           -1
+(1 row)
+
+SELECT * FROM to_regtypmod(NULL);
+ to_regtypmod 
+--------------
+             
+(1 row)
+
+-- Test to_regtypmod errors
+SELECT to_regtypmod('nonesuch'); -- error expected
+ERROR:  type "nonesuch" does not exist
+SELECT to_regtypmod('interval nonesuch'); -- grammar error expected
+ERROR:  syntax error at or near "nonesuch"
+LINE 1: SELECT to_regtypmod('interval nonesuch');
+                 ^
+CONTEXT:  invalid type name "interval nonesuch"
+SELECT to_regtypmod('year(4)'); -- grammar error expected
+ERROR:  type "year" does not exist
+-- Test to_regtypmod with various aliases and grammar-based types
+WITH s(s) AS (
+    SELECT * FROM unnest(ARRAY[
+        'timestamp(4)',
+        'interval(0)',
+        'interval second(0)',
+        'timestamptz',
+        'timestamptz(6)',
+        'varchar',
+        'varchar(128)'
+    ])
+),
+p(typid, typmod) AS (
+    SELECT to_regtype(s), to_regtypmod(s)
+      FROM s
+)
+SELECT format_type(typid, typmod) FROM p;
+          format_type           
+--------------------------------
+ timestamp(4) without time zone
+ interval(0)
+ interval second(0)
+ timestamp with time zone
+ timestamp(6) with time zone
+ character varying
+ character varying(128)
+(7 rows)
+
diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql
index 489463aa9e..ac19737012 100644
--- a/src/test/regress/sql/regproc.sql
+++ b/src/test/regress/sql/regproc.sql
@@ -173,3 +173,29 @@ p(typid, typmod) AS (
 )
 SELECT format_type(typid, typmod) FROM p;
 
+-- Test to_regtypmod
+SELECT * FROM to_regtypmod('text');
+SELECT * FROM to_regtypmod(NULL);
+
+-- Test to_regtypmod errors
+SELECT to_regtypmod('nonesuch'); -- error expected
+SELECT to_regtypmod('interval nonesuch'); -- grammar error expected
+SELECT to_regtypmod('year(4)'); -- grammar error expected
+
+-- Test to_regtypmod with various aliases and grammar-based types
+WITH s(s) AS (
+    SELECT * FROM unnest(ARRAY[
+        'timestamp(4)',
+        'interval(0)',
+        'interval second(0)',
+        'timestamptz',
+        'timestamptz(6)',
+        'varchar',
+        'varchar(128)'
+    ])
+),
+p(typid, typmod) AS (
+    SELECT to_regtype(s), to_regtypmod(s)
+      FROM s
+)
+SELECT format_type(typid, typmod) FROM p;
-- 
2.43.2

Reply via email to