From 3f936aacc057e4b391ab953fea2ffb689a12a8bc Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 30 Nov 2008 19:01:29 +0000
Subject: [PATCH] Add a "LIKE = typename" clause to CREATE TYPE for base types.
  This allows the basic representational details (typlen, typalign, typbyval,
 typstorage) to be copied from an existing type rather than listed explicitly
 in the CREATE TYPE command.  The immediate reason for this is to provide a
 simple solution for add-on modules that want to define types represented as
 int8, float4, or float8: as of 8.4 the appropriate PASSEDBYVALUE setting is
 platform-specific and so it's hard for a SQL script to know what to do.

This patch fixes the contrib/isn breakage reported by Rushabh Lathia.
---
 contrib/isn/isn.sql.in            |  34 ++--
 doc/src/sgml/ref/create_type.sgml |  34 +++-
 src/backend/commands/typecmds.c   | 251 ++++++++++++++++++++----------
 3 files changed, 208 insertions(+), 111 deletions(-)

diff --git a/contrib/isn/isn.sql.in b/contrib/isn/isn.sql.in
index 48f14134af7..1963fbbee34 100644
--- a/contrib/isn/isn.sql.in
+++ b/contrib/isn/isn.sql.in
@@ -1,4 +1,4 @@
-/* $PostgreSQL: pgsql/contrib/isn/isn.sql.in,v 1.8 2007/11/13 04:24:28 momjian Exp $ */
+/* $PostgreSQL: pgsql/contrib/isn/isn.sql.in,v 1.9 2008/11/30 19:01:29 tgl Exp $ */
 
 -- Adjust this setting to control where the objects get created.
 SET search_path = public;
@@ -28,9 +28,7 @@ CREATE OR REPLACE FUNCTION ean13_out(ean13)
 CREATE TYPE ean13 (
 	INPUT = ean13_in,
 	OUTPUT = ean13_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE ean13
 	IS 'International European Article Number (EAN13)';
@@ -48,9 +46,7 @@ CREATE OR REPLACE FUNCTION ean13_out(isbn13)
 CREATE TYPE isbn13 (
 	INPUT = isbn13_in,
 	OUTPUT = ean13_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE isbn13
 	IS 'International Standard Book Number 13 (ISBN13)';
@@ -68,9 +64,7 @@ CREATE OR REPLACE FUNCTION ean13_out(ismn13)
 CREATE TYPE ismn13 (
 	INPUT = ismn13_in,
 	OUTPUT = ean13_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE ismn13
 	IS 'International Standard Music Number 13 (ISMN13)';
@@ -88,9 +82,7 @@ CREATE OR REPLACE FUNCTION ean13_out(issn13)
 CREATE TYPE issn13 (
 	INPUT = issn13_in,
 	OUTPUT = ean13_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE issn13
 	IS 'International Standard Serial Number 13 (ISSN13)';
@@ -110,9 +102,7 @@ CREATE OR REPLACE FUNCTION isn_out(isbn)
 CREATE TYPE isbn (
 	INPUT = isbn_in,
 	OUTPUT = isn_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE isbn
 	IS 'International Standard Book Number (ISBN)';
@@ -130,9 +120,7 @@ CREATE OR REPLACE FUNCTION isn_out(ismn)
 CREATE TYPE ismn (
 	INPUT = ismn_in,
 	OUTPUT = isn_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE ismn
 	IS 'International Standard Music Number (ISMN)';
@@ -150,9 +138,7 @@ CREATE OR REPLACE FUNCTION isn_out(issn)
 CREATE TYPE issn (
 	INPUT = issn_in,
 	OUTPUT = isn_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE issn
 	IS 'International Standard Serial Number (ISSN)';
@@ -170,9 +156,7 @@ CREATE OR REPLACE FUNCTION isn_out(upc)
 CREATE TYPE upc (
 	INPUT = upc_in,
 	OUTPUT = isn_out,
-	INTERNALLENGTH = 8,
-	ALIGNMENT = double,
-	STORAGE = PLAIN
+	LIKE = pg_catalog.int8
 );
 COMMENT ON TYPE upc
 	IS 'Universal Product Code (UPC)';
diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index 222d41d28bb..78b11b8a80e 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_type.sgml,v 1.78 2008/11/14 10:22:46 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_type.sgml,v 1.79 2008/11/30 19:01:29 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -39,6 +39,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> (
     [ , PASSEDBYVALUE ]
     [ , ALIGNMENT = <replaceable class="parameter">alignment</replaceable> ]
     [ , STORAGE = <replaceable class="parameter">storage</replaceable> ]
+    [ , LIKE = <replaceable class="parameter">like_type</replaceable> ]
     [ , CATEGORY = <replaceable class="parameter">category</replaceable> ]
     [ , PREFERRED = <replaceable class="parameter">preferred</replaceable> ]
     [ , DEFAULT = <replaceable class="parameter">default</replaceable> ]
@@ -290,6 +291,21 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
    <literal>external</literal> items.)
   </para>
 
+  <para>
+   The <replaceable class="parameter">like_type</replaceable> parameter
+   provides an alternative method for specifying the basic representation
+   properties of a data type: copy them from some existing type. The values of
+   <replaceable class="parameter">internallength</replaceable>,
+   <replaceable class="parameter">passedbyvalue</replaceable>,
+   <replaceable class="parameter">alignment</replaceable>, and
+   <replaceable class="parameter">storage</replaceable> are copied from the
+   named type.  (It is possible, though usually undesirable, to override
+   some of these values by specifying them along with the <literal>LIKE</>
+   clause.)  Specifying representation this way is especially useful when
+   the low-level implementation of the new type <quote>piggybacks</> on an
+   existing type in some fashion.
+  </para>
+
   <para>
    The <replaceable class="parameter">category</replaceable> and
    <replaceable class="parameter">preferred</replaceable> parameters can be
@@ -524,6 +540,22 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">like_type</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing data type that the new type will have the
+      same representation as.  The values of
+      <replaceable class="parameter">internallength</replaceable>,
+      <replaceable class="parameter">passedbyvalue</replaceable>,
+      <replaceable class="parameter">alignment</replaceable>, and
+      <replaceable class="parameter">storage</replaceable>
+      are copied from that type, unless overridden by explicit
+      specification elsewhere in this <command>CREATE TYPE</> command.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">category</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 2ea9021a9ba..38416fa67f2 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.126 2008/11/02 01:45:28 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.127 2008/11/30 19:01:29 tgl Exp $
  *
  * DESCRIPTION
  *	  The "DefineFoo" routines take the parse tree and pick out the
@@ -100,7 +100,6 @@ DefineType(List *names, List *parameters)
 	char	   *typeName;
 	Oid			typeNamespace;
 	int16		internalLength = -1;	/* default: variable-length */
-	Oid			elemType = InvalidOid;
 	List	   *inputName = NIL;
 	List	   *outputName = NIL;
 	List	   *receiveName = NIL;
@@ -108,13 +107,31 @@ DefineType(List *names, List *parameters)
 	List	   *typmodinName = NIL;
 	List	   *typmodoutName = NIL;
 	List	   *analyzeName = NIL;
-	char	   *defaultValue = NULL;
-	bool		byValue = false;
 	char		category = TYPCATEGORY_USER;
 	bool		preferred = false;
 	char		delimiter = DEFAULT_TYPDELIM;
+	Oid			elemType = InvalidOid;
+	char	   *defaultValue = NULL;
+	bool		byValue = false;
 	char		alignment = 'i';	/* default alignment */
 	char		storage = 'p';	/* default TOAST storage method */
+	DefElem	   *likeTypeEl = NULL;
+	DefElem	   *internalLengthEl = NULL;
+	DefElem	   *inputNameEl = NULL;
+	DefElem	   *outputNameEl = NULL;
+	DefElem	   *receiveNameEl = NULL;
+	DefElem	   *sendNameEl = NULL;
+	DefElem	   *typmodinNameEl = NULL;
+	DefElem	   *typmodoutNameEl = NULL;
+	DefElem	   *analyzeNameEl = NULL;
+	DefElem	   *categoryEl = NULL;
+	DefElem	   *preferredEl = NULL;
+	DefElem	   *delimiterEl = NULL;
+	DefElem	   *elemTypeEl = NULL;
+	DefElem	   *defaultValueEl = NULL;
+	DefElem	   *byValueEl = NULL;
+	DefElem	   *alignmentEl = NULL;
+	DefElem	   *storageEl = NULL;
 	Oid			inputOid;
 	Oid			outputOid;
 	Oid			receiveOid = InvalidOid;
@@ -124,10 +141,10 @@ DefineType(List *names, List *parameters)
 	Oid			analyzeOid = InvalidOid;
 	char	   *array_type;
 	Oid			array_oid;
-	ListCell   *pl;
 	Oid			typoid;
 	Oid			resulttype;
 	Relation	pg_type;
+	ListCell   *pl;
 
 	/*
 	 * As of Postgres 8.4, we require superuser privilege to create a base
@@ -202,111 +219,175 @@ DefineType(List *names, List *parameters)
 					 errmsg("type \"%s\" already exists", typeName)));
 	}
 
+	/* Extract the parameters from the parameter list */
 	foreach(pl, parameters)
 	{
 		DefElem    *defel = (DefElem *) lfirst(pl);
+		DefElem   **defelp;
 
-		if (pg_strcasecmp(defel->defname, "internallength") == 0)
-			internalLength = defGetTypeLength(defel);
+		if (pg_strcasecmp(defel->defname, "like") == 0)
+			defelp = &likeTypeEl;
+		else if (pg_strcasecmp(defel->defname, "internallength") == 0)
+			defelp = &internalLengthEl;
 		else if (pg_strcasecmp(defel->defname, "input") == 0)
-			inputName = defGetQualifiedName(defel);
+			defelp = &inputNameEl;
 		else if (pg_strcasecmp(defel->defname, "output") == 0)
-			outputName = defGetQualifiedName(defel);
+			defelp = &outputNameEl;
 		else if (pg_strcasecmp(defel->defname, "receive") == 0)
-			receiveName = defGetQualifiedName(defel);
+			defelp = &receiveNameEl;
 		else if (pg_strcasecmp(defel->defname, "send") == 0)
-			sendName = defGetQualifiedName(defel);
+			defelp = &sendNameEl;
 		else if (pg_strcasecmp(defel->defname, "typmod_in") == 0)
-			typmodinName = defGetQualifiedName(defel);
+			defelp = &typmodinNameEl;
 		else if (pg_strcasecmp(defel->defname, "typmod_out") == 0)
-			typmodoutName = defGetQualifiedName(defel);
+			defelp = &typmodoutNameEl;
 		else if (pg_strcasecmp(defel->defname, "analyze") == 0 ||
 				 pg_strcasecmp(defel->defname, "analyse") == 0)
-			analyzeName = defGetQualifiedName(defel);
+			defelp = &analyzeNameEl;
 		else if (pg_strcasecmp(defel->defname, "category") == 0)
-		{
-			char	   *p = defGetString(defel);
-
-			category = p[0];
-			/* restrict to non-control ASCII */
-			if (category < 32 || category > 126)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("invalid type category \"%s\": must be simple ASCII",
-								p)));
-		}
+			defelp = &categoryEl;
 		else if (pg_strcasecmp(defel->defname, "preferred") == 0)
-			preferred = defGetBoolean(defel);
+			defelp = &preferredEl;
 		else if (pg_strcasecmp(defel->defname, "delimiter") == 0)
-		{
-			char	   *p = defGetString(defel);
-
-			delimiter = p[0];
-			/* XXX shouldn't we restrict the delimiter? */
-		}
+			defelp = &delimiterEl;
 		else if (pg_strcasecmp(defel->defname, "element") == 0)
-		{
-			elemType = typenameTypeId(NULL, defGetTypeName(defel), NULL);
-			/* disallow arrays of pseudotypes */
-			if (get_typtype(elemType) == TYPTYPE_PSEUDO)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("array element type cannot be %s",
-								format_type_be(elemType))));
-		}
+			defelp = &elemTypeEl;
 		else if (pg_strcasecmp(defel->defname, "default") == 0)
-			defaultValue = defGetString(defel);
+			defelp = &defaultValueEl;
 		else if (pg_strcasecmp(defel->defname, "passedbyvalue") == 0)
-			byValue = defGetBoolean(defel);
+			defelp = &byValueEl;
 		else if (pg_strcasecmp(defel->defname, "alignment") == 0)
-		{
-			char	   *a = defGetString(defel);
-
-			/*
-			 * Note: if argument was an unquoted identifier, parser will have
-			 * applied translations to it, so be prepared to recognize
-			 * translated type names as well as the nominal form.
-			 */
-			if (pg_strcasecmp(a, "double") == 0 ||
-				pg_strcasecmp(a, "float8") == 0 ||
-				pg_strcasecmp(a, "pg_catalog.float8") == 0)
-				alignment = 'd';
-			else if (pg_strcasecmp(a, "int4") == 0 ||
-					 pg_strcasecmp(a, "pg_catalog.int4") == 0)
-				alignment = 'i';
-			else if (pg_strcasecmp(a, "int2") == 0 ||
-					 pg_strcasecmp(a, "pg_catalog.int2") == 0)
-				alignment = 's';
-			else if (pg_strcasecmp(a, "char") == 0 ||
-					 pg_strcasecmp(a, "pg_catalog.bpchar") == 0)
-				alignment = 'c';
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("alignment \"%s\" not recognized", a)));
-		}
+			defelp = &alignmentEl;
 		else if (pg_strcasecmp(defel->defname, "storage") == 0)
-		{
-			char	   *a = defGetString(defel);
-
-			if (pg_strcasecmp(a, "plain") == 0)
-				storage = 'p';
-			else if (pg_strcasecmp(a, "external") == 0)
-				storage = 'e';
-			else if (pg_strcasecmp(a, "extended") == 0)
-				storage = 'x';
-			else if (pg_strcasecmp(a, "main") == 0)
-				storage = 'm';
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("storage \"%s\" not recognized", a)));
-		}
+			defelp = &storageEl;
 		else
+		{
+			/* WARNING, not ERROR, for historical backwards-compatibility */
 			ereport(WARNING,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("type attribute \"%s\" not recognized",
 							defel->defname)));
+			continue;
+		}
+		if (*defelp != NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("conflicting or redundant options")));
+		*defelp = defel;
+	}
+
+	/*
+	 * Now interpret the options; we do this separately so that LIKE can
+	 * be overridden by other options regardless of the ordering in the
+	 * parameter list.
+	 */
+	if (likeTypeEl)
+	{
+		Type	 likeType;
+		Form_pg_type likeForm;
+
+		likeType = typenameType(NULL, defGetTypeName(likeTypeEl), NULL);
+		likeForm = (Form_pg_type) GETSTRUCT(likeType);
+		internalLength = likeForm->typlen;
+		byValue = likeForm->typbyval;
+		alignment = likeForm->typalign;
+		storage = likeForm->typstorage;
+		ReleaseSysCache(likeType);
+	}
+	if (internalLengthEl)
+		internalLength = defGetTypeLength(internalLengthEl);
+	if (inputNameEl)
+		inputName = defGetQualifiedName(inputNameEl);
+	if (outputNameEl)
+		outputName = defGetQualifiedName(outputNameEl);
+	if (receiveNameEl)
+		receiveName = defGetQualifiedName(receiveNameEl);
+	if (sendNameEl)
+		sendName = defGetQualifiedName(sendNameEl);
+	if (typmodinNameEl)
+		typmodinName = defGetQualifiedName(typmodinNameEl);
+	if (typmodoutNameEl)
+		typmodoutName = defGetQualifiedName(typmodoutNameEl);
+	if (analyzeNameEl)
+		analyzeName = defGetQualifiedName(analyzeNameEl);
+	if (categoryEl)
+	{
+		char	   *p = defGetString(categoryEl);
+
+		category = p[0];
+		/* restrict to non-control ASCII */
+		if (category < 32 || category > 126)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("invalid type category \"%s\": must be simple ASCII",
+							p)));
+	}
+	if (preferredEl)
+		preferred = defGetBoolean(preferredEl);
+	if (delimiterEl)
+	{
+		char	   *p = defGetString(delimiterEl);
+
+		delimiter = p[0];
+		/* XXX shouldn't we restrict the delimiter? */
+	}
+	if (elemTypeEl)
+	{
+		elemType = typenameTypeId(NULL, defGetTypeName(elemTypeEl), NULL);
+		/* disallow arrays of pseudotypes */
+		if (get_typtype(elemType) == TYPTYPE_PSEUDO)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("array element type cannot be %s",
+							format_type_be(elemType))));
+	}
+	if (defaultValueEl)
+		defaultValue = defGetString(defaultValueEl);
+	if (byValueEl)
+		byValue = defGetBoolean(byValueEl);
+	if (alignmentEl)
+	{
+		char	   *a = defGetString(alignmentEl);
+
+		/*
+		 * Note: if argument was an unquoted identifier, parser will have
+		 * applied translations to it, so be prepared to recognize
+		 * translated type names as well as the nominal form.
+		 */
+		if (pg_strcasecmp(a, "double") == 0 ||
+			pg_strcasecmp(a, "float8") == 0 ||
+			pg_strcasecmp(a, "pg_catalog.float8") == 0)
+			alignment = 'd';
+		else if (pg_strcasecmp(a, "int4") == 0 ||
+				 pg_strcasecmp(a, "pg_catalog.int4") == 0)
+			alignment = 'i';
+		else if (pg_strcasecmp(a, "int2") == 0 ||
+				 pg_strcasecmp(a, "pg_catalog.int2") == 0)
+			alignment = 's';
+		else if (pg_strcasecmp(a, "char") == 0 ||
+				 pg_strcasecmp(a, "pg_catalog.bpchar") == 0)
+			alignment = 'c';
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("alignment \"%s\" not recognized", a)));
+	}
+	if (storageEl)
+	{
+		char	   *a = defGetString(storageEl);
+
+		if (pg_strcasecmp(a, "plain") == 0)
+			storage = 'p';
+		else if (pg_strcasecmp(a, "external") == 0)
+			storage = 'e';
+		else if (pg_strcasecmp(a, "extended") == 0)
+			storage = 'x';
+		else if (pg_strcasecmp(a, "main") == 0)
+			storage = 'm';
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("storage \"%s\" not recognized", a)));
 	}
 
 	/*
-- 
GitLab