From c0e3982a3d713ccacdd2cbeb84de48eab5e774a0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 29 Jun 2026 15:33:34 +0800
Subject: [PATCH v9 1/3] Add typformatin and typformatout to pg_type
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

PostgreSQL allows a base data type to declare optional support functions in
pg_type that define type-specific behaviors, e.g. typmodin/typmodout for type
modifier handling.  This commit adds two more:

typformatin  allows the data type to be a target of format-driven input
                casting.  The function converts a text value to the type's
                internal representation given a format template.  It must
                have signature (text, text) -> type, where the first
                argument is the source text and the second is the format
                template.

typformatout allows values of the data type to be converted to text
                using a format template.  It must have signature
                (type, text) -> text, where the second argument is the
                format template.

typformatin is unlikely to work for array types as-is; supporting
array-element-level format casting requires special handling.

TYPFORMAT_IN and TYPFORMAT_OUT can be specified in CREATE TYPE or via
ALTER TYPE SET, and require superuser privilege.

User-defined types can participate by registering their own formatting
functions.

These support functions enable the SQL-standard CAST(expr AS type FORMAT
'template') construct introduced in the next commit.

The following built-in types are assigned formatting support functions:
    typformatin:  date → to_date, numeric → to_number,
                timestamptz → to_timestamp
    typformatout: int4, int8, float4, float8, numeric, interval,
                timestamp, timestamptz → to_char

discussion: https://postgr.es/m/CACJufxGqm7cYQ5C65Eoh1z-f+aMdhv9_7V=NoLH_p6uuyesi6A@mail.gmail.com
commitfest: https://commitfest.postgresql.org/patch/5957
---
 doc/src/sgml/ref/alter_type.sgml          |  19 +++
 doc/src/sgml/ref/create_type.sgml         |  57 ++++++-
 src/backend/catalog/heap.c                |   4 +
 src/backend/catalog/pg_type.c             |  18 +++
 src/backend/catalog/system_functions.sql  |  11 ++
 src/backend/commands/typecmds.c           | 172 ++++++++++++++++++++++
 src/backend/utils/cache/lsyscache.c       |  77 ++++++++++
 src/include/catalog/pg_type.dat           |   2 +
 src/include/catalog/pg_type.h             |   6 +
 src/test/regress/expected/create_type.out |  23 +++
 src/test/regress/expected/oidjoins.out    |   2 +
 src/test/regress/sql/create_type.sql      |  22 +++
 12 files changed, 412 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/alter_type.sgml b/doc/src/sgml/ref/alter_type.sgml
index 025a3ee48f5..f7ba47c870c 100644
--- a/doc/src/sgml/ref/alter_type.sgml
+++ b/doc/src/sgml/ref/alter_type.sgml
@@ -194,6 +194,25 @@ ALTER TYPE <replaceable class="parameter">name</replaceable> SET ( <replaceable
          requires superuser privilege.
         </para>
        </listitem>
+
+       <listitem>
+        <para>
+         <literal>TYPFORMAT_IN</literal> can be set to the name of a type-specific
+         input format function, or <literal>NONE</literal> to remove
+         the type's input format function.  Using this option
+         requires superuser privilege.
+        </para>
+       </listitem>
+
+       <listitem>
+        <para>
+         <literal>TYPFORMAT_OUT</literal> can be set to the name of a type-specific
+         output format function, or <literal>NONE</literal> to remove
+         the type's output format function.  Using this option
+         requires superuser privilege.
+        </para>
+       </listitem>
+
        <listitem>
         <para>
          <literal>SUBSCRIPT</literal> can be set to the name of a type-specific
diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index 994dfc65268..115a7593b3b 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -44,6 +44,8 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> (
     [ , TYPMOD_IN = <replaceable class="parameter">type_modifier_input_function</replaceable> ]
     [ , TYPMOD_OUT = <replaceable class="parameter">type_modifier_output_function</replaceable> ]
     [ , ANALYZE = <replaceable class="parameter">analyze_function</replaceable> ]
+    [ , TYPFORMAT_IN = <replaceable class="parameter">type_format_input_function</replaceable> ]
+    [ , TYPFORMAT_OUT = <replaceable class="parameter">type_format_out_function</replaceable> ]
     [ , SUBSCRIPT = <replaceable class="parameter">subscript_function</replaceable> ]
     [ , INTERNALLENGTH = { <replaceable class="parameter">internallength</replaceable> | VARIABLE } ]
     [ , PASSEDBYVALUE ]
@@ -210,7 +212,9 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
    <replaceable class="parameter">send_function</replaceable>,
    <replaceable class="parameter">type_modifier_input_function</replaceable>,
    <replaceable class="parameter">type_modifier_output_function</replaceable>,
-   <replaceable class="parameter">analyze_function</replaceable>, and
+   <replaceable class="parameter">analyze_function</replaceable>,
+   <replaceable class="parameter">type_format_input_function</replaceable>,
+   <replaceable class="parameter">type_format_out_function</replaceable>, and
    <replaceable class="parameter">subscript_function</replaceable>
    are optional.  Generally these functions have to be coded in C
    or another low-level language.
@@ -332,6 +336,30 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
    in <filename>src/include/commands/vacuum.h</filename>.
   </para>
 
+  <para>
+   The optional <replaceable class="parameter">type_format_input_function</replaceable>
+   converts a text value to the type's internal representation using a format
+   template, supporting the <literal>CAST(<replaceable>expr</replaceable> AS
+   <replaceable>type</replaceable> FORMAT '<replaceable>template</replaceable>')</literal>
+   SQL syntax for input.  It must be declared to take two arguments of type
+   <type>text</type> — the first is the value to convert and the second is the
+   format pattern — and return the data type being defined.  For example,
+   <function>to_date</function> serves as the format input function for
+   <type>date</type>, and <function>to_number</function> for <type>numeric</type>.
+  </para>
+
+  <para>
+   The optional <replaceable class="parameter">type_format_out_function</replaceable>
+   converts a value of the type to a text representation using a format template,
+   supporting the <literal>CAST(<replaceable>expr</replaceable> AS text FORMAT
+   '<replaceable>template</replaceable>')</literal> SQL syntax for output.  It must
+   be declared to take two arguments — the first of the data type being defined and
+   the second of type <type>text</type> (the format pattern) — and return
+   <type>text</type>.  For example, <function>to_char</function> serves as the
+   format output function for <type>numeric</type>, <type>timestamp</type>, and
+   other types.
+  </para>
+
   <para>
    The optional <replaceable class="parameter">subscript_function</replaceable>
    allows the data type to be subscripted in SQL commands.  Specifying this
@@ -721,6 +749,33 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">type_format_input_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of a function that converts a text value to the type's
+      internal form using a format template.  The function must accept two
+      <type>text</type> arguments (value and format pattern) and return the
+      new data type.  See the description of <literal>TYPFORMAT_IN</literal>
+      above for details.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">type_format_output_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of a function that converts a value of the type to a text
+      representation using a format template.  The function must accept the
+      data type as its first argument and a <type>text</type> format pattern
+      as its second, and return <type>text</type>.  See the description of
+      <literal>TYPFORMAT_OUT</literal> above for details.
+     </para>
+    </listitem>
+   </varlistentry>
+
+
    <varlistentry>
     <term><replaceable class="parameter">subscript_function</replaceable></term>
     <listitem>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 88087654de9..7152c16bd34 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1085,6 +1085,8 @@ AddNewRelationType(const char *typeName,
 				   InvalidOid,	/* typmodin procedure - none */
 				   InvalidOid,	/* typmodout procedure - none */
 				   InvalidOid,	/* analyze procedure - default */
+				   InvalidOid,	/* input format procedure */
+				   InvalidOid,	/* output format procedure */
 				   InvalidOid,	/* subscript procedure - none */
 				   InvalidOid,	/* array element type - irrelevant */
 				   false,		/* this is not an array type */
@@ -1411,6 +1413,8 @@ heap_create_with_catalog(const char *relname,
 				   InvalidOid,	/* typmodin procedure - none */
 				   InvalidOid,	/* typmodout procedure - none */
 				   F_ARRAY_TYPANALYZE,	/* array analyze procedure */
+				   InvalidOid,	/* array don't have input format procedure */
+				   InvalidOid,	/* array don't have output format procedure */
 				   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
 				   new_type_oid,	/* array element type - the rowtype */
 				   true,		/* yes, this is an array type */
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index fc369c35aa6..a3e60f7a2fc 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -113,6 +113,8 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	values[Anum_pg_type_typmodin - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_type_typmodout - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(InvalidOid);
+	values[Anum_pg_type_typformatin - 1] = ObjectIdGetDatum(InvalidOid);
+	values[Anum_pg_type_typformatout - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_type_typalign - 1] = CharGetDatum(TYPALIGN_INT);
 	values[Anum_pg_type_typstorage - 1] = CharGetDatum(TYPSTORAGE_PLAIN);
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(false);
@@ -210,6 +212,8 @@ TypeCreate(Oid newTypeOid,
 		   Oid typmodinProcedure,
 		   Oid typmodoutProcedure,
 		   Oid analyzeProcedure,
+		   Oid inputformatProcedure,
+		   Oid outputformatProcedure,
 		   Oid subscriptProcedure,
 		   Oid elementType,
 		   bool isImplicitArray,
@@ -369,6 +373,8 @@ TypeCreate(Oid newTypeOid,
 	values[Anum_pg_type_typmodin - 1] = ObjectIdGetDatum(typmodinProcedure);
 	values[Anum_pg_type_typmodout - 1] = ObjectIdGetDatum(typmodoutProcedure);
 	values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(analyzeProcedure);
+	values[Anum_pg_type_typformatin - 1] = ObjectIdGetDatum(inputformatProcedure);
+	values[Anum_pg_type_typformatout - 1] = ObjectIdGetDatum(outputformatProcedure);
 	values[Anum_pg_type_typalign - 1] = CharGetDatum(alignment);
 	values[Anum_pg_type_typstorage - 1] = CharGetDatum(storage);
 	values[Anum_pg_type_typnotnull - 1] = BoolGetDatum(typeNotNull);
@@ -677,6 +683,18 @@ GenerateTypeDependencies(HeapTuple typeTuple,
 		add_exact_object_address(&referenced, addrs_normal);
 	}
 
+	if (OidIsValid(typeForm->typformatin))
+	{
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typformatin);
+		add_exact_object_address(&referenced, addrs_normal);
+	}
+
+	if (OidIsValid(typeForm->typformatout))
+	{
+		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typformatout);
+		add_exact_object_address(&referenced, addrs_normal);
+	}
+
 	if (OidIsValid(typeForm->typsubscript))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, typeForm->typsubscript);
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index c3c0a6e84ed..5089c41c407 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -366,3 +366,14 @@ CREATE OR REPLACE FUNCTION ts_debug(document text,
 BEGIN ATOMIC
     SELECT * FROM ts_debug(get_current_ts_config(), $1);
 END;
+
+ALTER TYPE timestamptz SET (TYPFORMAT_IN = to_timestamp);
+
+ALTER TYPE int8 SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE int4 SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE interval SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE numeric SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE float8 SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE float4 SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE timestamp SET (TYPFORMAT_OUT = to_char);
+ALTER TYPE timestamptz SET (TYPFORMAT_OUT = to_char);
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e9c3215ccec..64c94ecf828 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -94,6 +94,8 @@ typedef struct
 	bool		updateTypmodin;
 	bool		updateTypmodout;
 	bool		updateAnalyze;
+	bool		updateTypformatin;
+	bool		updateTypformatout;
 	bool		updateSubscript;
 	/* New values for relevant attributes */
 	char		storage;
@@ -102,6 +104,8 @@ typedef struct
 	Oid			typmodinOid;
 	Oid			typmodoutOid;
 	Oid			analyzeOid;
+	Oid			typformatinOid;
+	Oid			typformatoutOid;
 	Oid			subscriptOid;
 } AlterTypeRecurseParams;
 
@@ -124,6 +128,8 @@ static Oid	findTypeSendFunction(List *procname, Oid typeOid);
 static Oid	findTypeTypmodinFunction(List *procname);
 static Oid	findTypeTypmodoutFunction(List *procname);
 static Oid	findTypeAnalyzeFunction(List *procname, Oid typeOid);
+static Oid	findTypeTypformatinFunction(List *procname, Oid typeOid);
+static Oid	findTypeTypformatoutFunction(List *procname, Oid typeOid);
 static Oid	findTypeSubscriptingFunction(List *procname, Oid typeOid);
 static Oid	findRangeSubOpclass(List *opcname, Oid subtype);
 static Oid	findRangeCanonicalFunction(List *procname, Oid typeOid);
@@ -163,6 +169,8 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	List	   *typmodinName = NIL;
 	List	   *typmodoutName = NIL;
 	List	   *analyzeName = NIL;
+	List	   *inputformatName = NIL;
+	List	   *outputformatName = NIL;
 	List	   *subscriptName = NIL;
 	char		category = TYPCATEGORY_USER;
 	bool		preferred = false;
@@ -182,6 +190,8 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	DefElem    *typmodinNameEl = NULL;
 	DefElem    *typmodoutNameEl = NULL;
 	DefElem    *analyzeNameEl = NULL;
+	DefElem    *typformatinNameEl = NULL;
+	DefElem    *typformatoutNameEl = NULL;
 	DefElem    *subscriptNameEl = NULL;
 	DefElem    *categoryEl = NULL;
 	DefElem    *preferredEl = NULL;
@@ -199,6 +209,8 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	Oid			typmodinOid = InvalidOid;
 	Oid			typmodoutOid = InvalidOid;
 	Oid			analyzeOid = InvalidOid;
+	Oid			typformatinOid = InvalidOid;
+	Oid			typformatoutOid = InvalidOid;
 	Oid			subscriptOid = InvalidOid;
 	char	   *array_type;
 	Oid			array_oid;
@@ -305,6 +317,10 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 		else if (strcmp(defel->defname, "analyze") == 0 ||
 				 strcmp(defel->defname, "analyse") == 0)
 			defelp = &analyzeNameEl;
+		else if (strcmp(defel->defname, "typformat_in") == 0)
+			defelp = &typformatinNameEl;
+		else if (strcmp(defel->defname, "typformat_out") == 0)
+			defelp = &typformatoutNameEl;
 		else if (strcmp(defel->defname, "subscript") == 0)
 			defelp = &subscriptNameEl;
 		else if (strcmp(defel->defname, "category") == 0)
@@ -374,6 +390,11 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 		typmodoutName = defGetQualifiedName(typmodoutNameEl);
 	if (analyzeNameEl)
 		analyzeName = defGetQualifiedName(analyzeNameEl);
+	if (typformatinNameEl)
+		inputformatName = defGetQualifiedName(typformatinNameEl);
+	if (typformatoutNameEl)
+		outputformatName = defGetQualifiedName(typformatoutNameEl);
+
 	if (subscriptNameEl)
 		subscriptName = defGetQualifiedName(subscriptNameEl);
 	if (categoryEl)
@@ -500,6 +521,12 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	if (analyzeName)
 		analyzeOid = findTypeAnalyzeFunction(analyzeName, typoid);
 
+	if (inputformatName)
+		typformatinOid = findTypeTypformatinFunction(inputformatName, typoid);
+
+	if (outputformatName)
+		typformatoutOid = findTypeTypformatoutFunction(outputformatName, typoid);
+
 	/*
 	 * Likewise look up the subscripting function if any.  If it is not
 	 * specified, but a typelem is specified, allow that if
@@ -552,6 +579,12 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 	if (analyzeOid && !object_ownercheck(ProcedureRelationId, analyzeOid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
 					   NameListToString(analyzeName));
+	if (typformatinOid && !object_ownercheck(ProcedureRelationId, typformatinOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
+					   NameListToString(inputformatName));
+	if (typformatoutOid && !object_ownercheck(ProcedureRelationId, typformatoutOid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
+					   NameListToString(outputformatName));
 	if (subscriptOid && !object_ownercheck(ProcedureRelationId, subscriptOid, GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
 					   NameListToString(subscriptName));
@@ -590,6 +623,8 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 				   typmodinOid, /* typmodin procedure */
 				   typmodoutOid,	/* typmodout procedure */
 				   analyzeOid,	/* analyze procedure */
+				   typformatinOid,	/* typformatin procedure */
+				   typformatoutOid, /* typformatout procedure */
 				   subscriptOid,	/* subscript procedure */
 				   elemType,	/* element type ID */
 				   false,		/* this is not an implicit array type */
@@ -632,6 +667,8 @@ DefineType(ParseState *pstate, List *names, List *parameters)
 			   typmodinOid,		/* typmodin procedure */
 			   typmodoutOid,	/* typmodout procedure */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
+			   InvalidOid,		/* array don't have input format procedure */
+			   InvalidOid,		/* array don't output format procedure */
 			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
 			   typoid,			/* element type ID */
 			   true,			/* yes this is an array type */
@@ -1075,6 +1112,8 @@ DefineDomain(ParseState *pstate, CreateDomainStmt *stmt)
 				   InvalidOid,	/* typmodin procedure - none */
 				   InvalidOid,	/* typmodout procedure - none */
 				   analyzeProcedure,	/* analyze procedure */
+				   InvalidOid,	/* typformatin procedure - none */
+				   InvalidOid,	/* typformatout procedure - none */
 				   InvalidOid,	/* subscript procedure - none */
 				   InvalidOid,	/* no array element type */
 				   false,		/* this isn't an array */
@@ -1116,6 +1155,8 @@ DefineDomain(ParseState *pstate, CreateDomainStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
+			   InvalidOid,		/* typformatin procedure - none */
+			   InvalidOid,		/* typformatout procedure - none */
 			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
 			   address.objectId,	/* element type ID */
 			   true,			/* yes this is an array type */
@@ -1238,6 +1279,8 @@ DefineEnum(CreateEnumStmt *stmt)
 				   InvalidOid,	/* typmodin procedure - none */
 				   InvalidOid,	/* typmodout procedure - none */
 				   InvalidOid,	/* analyze procedure - default */
+				   InvalidOid,	/* typformatin procedure - none */
+				   InvalidOid,	/* typformatout procedure - none */
 				   InvalidOid,	/* subscript procedure - none */
 				   InvalidOid,	/* element type ID */
 				   false,		/* this is not an array type */
@@ -1279,6 +1322,8 @@ DefineEnum(CreateEnumStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
+			   InvalidOid,		/* typformatin procedure - none */
+			   InvalidOid,		/* typformatout procedure - none */
 			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
 			   enumTypeAddr.objectId,	/* element type ID */
 			   true,			/* yes this is an array type */
@@ -1592,6 +1637,8 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   InvalidOid,	/* typmodin procedure - none */
 				   InvalidOid,	/* typmodout procedure - none */
 				   F_RANGE_TYPANALYZE,	/* analyze procedure */
+				   InvalidOid,	/* typformatin procedure - none */
+				   InvalidOid,	/* typformatout procedure - none */
 				   InvalidOid,	/* subscript procedure - none */
 				   InvalidOid,	/* element type ID - none */
 				   false,		/* this is not an array type */
@@ -1659,6 +1706,8 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 				   InvalidOid,	/* typmodin procedure - none */
 				   InvalidOid,	/* typmodout procedure - none */
 				   F_MULTIRANGE_TYPANALYZE, /* analyze procedure */
+				   InvalidOid,	/* typformatin procedure - none */
+				   InvalidOid,	/* typformatout procedure - none */
 				   InvalidOid,	/* subscript procedure - none */
 				   InvalidOid,	/* element type ID - none */
 				   false,		/* this is not an array type */
@@ -1698,6 +1747,8 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
+			   InvalidOid,		/* typformatin procedure - none */
+			   InvalidOid,		/* typformatout procedure - none */
 			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
 			   typoid,			/* element type ID */
 			   true,			/* yes this is an array type */
@@ -1737,6 +1788,8 @@ DefineRange(ParseState *pstate, CreateRangeStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
+			   InvalidOid,		/* typformatin procedure - none */
+			   InvalidOid,		/* typformatout procedure - none */
 			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
 			   multirangeOid,	/* element type ID */
 			   true,			/* yes this is an array type */
@@ -2301,6 +2354,89 @@ findTypeAnalyzeFunction(List *procname, Oid typeOid)
 	return procOid;
 }
 
+static Oid
+findTypeTypformatoutFunction(List *procname, Oid typeOid)
+{
+	Oid			argList[2];
+	Oid			procOid;
+
+	/*
+	 * typformatout functions must take exactly two arguments: the first is
+	 * the data type being formatted (typeOid), and the second is text (the
+	 * format string). They must return text. given a value of the type and a
+	 * format pattern, produce a text representation.
+	 */
+	argList[0] = typeOid;
+	argList[1] = TEXTOID;
+
+	procOid = LookupFuncName(procname, 2, argList, true);
+	if (!OidIsValid(procOid))
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_FUNCTION),
+				errmsg("function %s does not exist",
+					   func_signature_string(procname, 2, NIL, argList)),
+				errhint("A data type typformatout function must accept exactly two arguments: the first must be the data type itself (%s) and the second must be %s (the format pattern)",
+						format_type_be(typeOid),
+						format_type_be(TEXTOID)));
+
+	if (get_func_rettype(procOid) != TEXTOID)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("typformatout function %s must return type %s",
+					   NameListToString(procname), format_type_be(TEXTOID)));
+
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("typformatout function %s should not be volatile",
+					   NameListToString(procname)));
+
+	return procOid;
+}
+
+static Oid
+findTypeTypformatinFunction(List *procname, Oid typeOid)
+{
+	Oid			argList[2];
+	Oid			procOid;
+
+	/*
+	 * typformatin functions always take two text argument and return that
+	 * target data type.
+	 *
+	 * No need to worry about array type, since we can use the array type get
+	 * the array element type and element type typmod
+	 */
+	argList[0] = TEXTOID;
+	argList[1] = TEXTOID;
+
+	procOid = LookupFuncName(procname, 2, argList, true);
+	if (!OidIsValid(procOid))
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_FUNCTION),
+				errmsg("function %s does not exist",
+					   func_signature_string(procname, 2, NIL, argList)),
+				errhint("A data type typformatin function must accept two argument of type %s.",
+						format_type_be(TEXTOID)));
+
+	if (get_func_rettype(procOid) != typeOid)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("typformatin function %s must return type %s",
+					   NameListToString(procname),
+					   format_type_be(typeOid)));
+
+	/* Just a warning for now, per comments in findTypeInputFunction */
+	if (func_volatile(procOid) == PROVOLATILE_VOLATILE)
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("typformatin function %s should not be volatile",
+					   NameListToString(procname)));
+
+	return procOid;
+}
+
 static Oid
 findTypeSubscriptingFunction(List *procname, Oid typeOid)
 {
@@ -4481,6 +4617,32 @@ AlterType(AlterTypeStmt *stmt)
 			/* Replacing an analyze function requires superuser. */
 			requireSuper = true;
 		}
+		else if (strcmp(defel->defname, "typformat_in") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.typformatinOid =
+					findTypeTypformatinFunction(defGetQualifiedName(defel),
+												typeOid);
+			else
+				atparams.typformatinOid = InvalidOid;	/* NONE, remove function */
+
+			atparams.updateTypformatin = true;
+			/* Replacing an typformat_in function requires superuser. */
+			requireSuper = true;
+		}
+		else if (strcmp(defel->defname, "typformat_out") == 0)
+		{
+			if (defel->arg != NULL)
+				atparams.typformatoutOid =
+					findTypeTypformatoutFunction(defGetQualifiedName(defel),
+												 typeOid);
+			else
+				atparams.typformatoutOid = InvalidOid;	/* NONE, remove function */
+
+			atparams.updateTypformatout = true;
+			/* Replacing an typformat_out function requires superuser. */
+			requireSuper = true;
+		}
 		else if (strcmp(defel->defname, "subscript") == 0)
 		{
 			if (defel->arg != NULL)
@@ -4650,6 +4812,16 @@ AlterTypeRecurse(Oid typeOid, bool isImplicitArray,
 		replaces[Anum_pg_type_typanalyze - 1] = true;
 		values[Anum_pg_type_typanalyze - 1] = ObjectIdGetDatum(atparams->analyzeOid);
 	}
+	if (atparams->updateTypformatin)
+	{
+		replaces[Anum_pg_type_typformatin - 1] = true;
+		values[Anum_pg_type_typformatin - 1] = ObjectIdGetDatum(atparams->typformatinOid);
+	}
+	if (atparams->updateTypformatout)
+	{
+		replaces[Anum_pg_type_typformatout - 1] = true;
+		values[Anum_pg_type_typformatout - 1] = ObjectIdGetDatum(atparams->typformatoutOid);
+	}
 	if (atparams->updateSubscript)
 	{
 		replaces[Anum_pg_type_typsubscript - 1] = true;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 036de5f79ef..beb8a87f1c2 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3391,6 +3391,83 @@ type_is_collatable(Oid typid)
 	return OidIsValid(get_typcollation(typid));
 }
 
+/*
+ * getTypeInputFormatInfo
+ *
+ *		Get info needed for converting values of a type to internal form base on a format
+ */
+void
+getTypeInputFormatInfo(Oid type, Oid *typInputFormat, bool missing_ok)
+{
+	HeapTuple	typeTuple;
+	Form_pg_type pt;
+
+	typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
+	if (!HeapTupleIsValid(typeTuple))
+		elog(ERROR, "cache lookup failed for type %u", type);
+	pt = (Form_pg_type) GETSTRUCT(typeTuple);
+
+	if (!pt->typisdefined)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("type %s is only a shell",
+					   format_type_be(type)));
+
+	if (!OidIsValid(pt->typformatin))
+	{
+		if (missing_ok)
+			*typInputFormat = InvalidOid;
+		else
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_FUNCTION),
+					errmsg("no typformatin function available for type %s",
+						   format_type_be(type)));
+	}
+	else
+		*typInputFormat = pt->typformatin;
+
+	ReleaseSysCache(typeTuple);
+}
+
+/*
+ * getTypeInputFormatInfo
+ *
+ *		Get info needed for converting values of a type to text based on a format
+ */
+void
+getTypeOutputFormatInfo(Oid type, Oid *typOutputFormat, bool missing_ok)
+{
+	HeapTuple	typeTuple;
+	Form_pg_type pt;
+
+	typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type));
+	if (!HeapTupleIsValid(typeTuple))
+		elog(ERROR, "cache lookup failed for type %u", type);
+	pt = (Form_pg_type) GETSTRUCT(typeTuple);
+
+	if (!pt->typisdefined)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("type %s is only a shell",
+					   format_type_be(type)));
+
+	if (!OidIsValid(pt->typformatout))
+	{
+		if (missing_ok)
+			*typOutputFormat = InvalidOid;
+		else
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_FUNCTION),
+					errmsg("no typformatout function available for type %s",
+						   format_type_be(type)));
+	}
+	else
+		*typOutputFormat = pt->typformatout;
+
+	ReleaseSysCache(typeTuple);
+}
+
+
 
 /*
  * get_typsubscript
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 42ee494601b..dc3ad9715e0 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -287,6 +287,7 @@
 { oid => '1082', array_type_oid => '1182', descr => 'date',
   typname => 'date', typlen => '4', typbyval => 't', typcategory => 'D',
   typinput => 'date_in', typoutput => 'date_out', typreceive => 'date_recv',
+  typformatin => 'to_date',
   typsend => 'date_send', typalign => 'i' },
 { oid => '1083', array_type_oid => '1183', descr => 'time of day',
   typname => 'time', typlen => '8', typbyval => 't', typcategory => 'D',
@@ -350,6 +351,7 @@
   typinput => 'numeric_in', typoutput => 'numeric_out',
   typreceive => 'numeric_recv', typsend => 'numeric_send',
   typmodin => 'numerictypmodin', typmodout => 'numerictypmodout',
+  typformatin => 'to_number',
   typalign => 'i', typstorage => 'm' },
 
 { oid => '1790', array_type_oid => '2201',
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 74183ec5a2e..f84a1a968bc 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -150,6 +150,10 @@ CATALOG(pg_type,1247,TypeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71,TypeRelati
 	 */
 	regproc		typanalyze BKI_DEFAULT(-) BKI_ARRAY_DEFAULT(array_typanalyze) BKI_LOOKUP_OPT(pg_proc);
 
+	regproc		typformatin BKI_DEFAULT(-) BKI_LOOKUP_OPT(pg_proc);
+
+	regproc		typformatout BKI_DEFAULT(-) BKI_LOOKUP_OPT(pg_proc);
+
 	/* ----------------
 	 * typalign is the alignment required when storing a value of this
 	 * type.  It applies to storage on disk as well as most
@@ -371,6 +375,8 @@ extern ObjectAddress TypeCreate(Oid newTypeOid,
 								Oid typmodinProcedure,
 								Oid typmodoutProcedure,
 								Oid analyzeProcedure,
+								Oid inputformatProcedure,
+								Oid outputformatProcedure,
 								Oid subscriptProcedure,
 								Oid elementType,
 								bool isImplicitArray,
diff --git a/src/test/regress/expected/create_type.out b/src/test/regress/expected/create_type.out
index 5181c4290b4..119e2916c91 100644
--- a/src/test/regress/expected/create_type.out
+++ b/src/test/regress/expected/create_type.out
@@ -93,6 +93,29 @@ CREATE FUNCTION text_w_default_out(text_w_default)
 NOTICE:  argument type text_w_default is only a shell
 LINE 1: CREATE FUNCTION text_w_default_out(text_w_default)
                                            ^
+-- error: typformat_in input argument must be (text, text)
+CREATE TYPE int42 (
+   internallength = 4,
+   input = int42_in,
+   output = int42_out,
+   typformat_in = int42_in,
+   alignment = int4,
+   default = 42,
+   passedbyvalue
+);
+ERROR:  function int42_in(text, text) does not exist
+HINT:  A data type typformatin function must accept two argument of type text.
+-- error: typformat_in result type must be same as the result type
+CREATE TYPE int42 (
+   internallength = 4,
+   input = int42_in,
+   output = int42_out,
+   typformat_in = to_date,
+   alignment = int4,
+   default = 42,
+   passedbyvalue
+);
+ERROR:  typformatin function to_date must return type int42
 CREATE TYPE int42 (
    internallength = 4,
    input = int42_in,
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index d64169b7bf0..0c4d193d82c 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -69,6 +69,8 @@ NOTICE:  checking pg_type {typsend} => pg_proc {oid}
 NOTICE:  checking pg_type {typmodin} => pg_proc {oid}
 NOTICE:  checking pg_type {typmodout} => pg_proc {oid}
 NOTICE:  checking pg_type {typanalyze} => pg_proc {oid}
+NOTICE:  checking pg_type {typformatin} => pg_proc {oid}
+NOTICE:  checking pg_type {typformatout} => pg_proc {oid}
 NOTICE:  checking pg_type {typbasetype} => pg_type {oid}
 NOTICE:  checking pg_type {typcollation} => pg_collation {oid}
 NOTICE:  checking pg_attribute {attrelid} => pg_class {oid}
diff --git a/src/test/regress/sql/create_type.sql b/src/test/regress/sql/create_type.sql
index c25018029c2..8ccf35dbf1b 100644
--- a/src/test/regress/sql/create_type.sql
+++ b/src/test/regress/sql/create_type.sql
@@ -86,6 +86,28 @@ CREATE FUNCTION text_w_default_out(text_w_default)
    AS 'textout'
    LANGUAGE internal STRICT IMMUTABLE;
 
+-- error: typformat_in input argument must be (text, text)
+CREATE TYPE int42 (
+   internallength = 4,
+   input = int42_in,
+   output = int42_out,
+   typformat_in = int42_in,
+   alignment = int4,
+   default = 42,
+   passedbyvalue
+);
+
+-- error: typformat_in result type must be same as the result type
+CREATE TYPE int42 (
+   internallength = 4,
+   input = int42_in,
+   output = int42_out,
+   typformat_in = to_date,
+   alignment = int4,
+   default = 42,
+   passedbyvalue
+);
+
 CREATE TYPE int42 (
    internallength = 4,
    input = int42_in,
-- 
2.34.1

