typecmds.c says:
"Domains over composite types might be made to work in the future, but not today."

Attached is a patch that allows domains over composite types, together with test cases in domaincomp.sql. A domain over a composite type has typtype TYPTYPE_DOMAIN, but typrelid and typrelkind are empty: that information is only available in the pg_type record of the base type. The remainder of the patch follows from that choice. While parsing a record expression into a row type, an extra coercion node had to be inserted to ensure that the domain checks are called.

All regression tests are ok, comments are highly appreciated.

--

Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
new file mode 100644
index 4f4dd69..1c8dc64
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
***************
*** 36,41 ****
--- 36,42 ----
  #include "access/xact.h"
  #include "catalog/catalog.h"
  #include "utils/fmgroids.h"
+ #include "utils/lsyscache.h"
  #include "utils/pg_lzcompress.h"
  #include "utils/rel.h"
  #include "utils/typcache.h"
*************** toast_flatten_tuple_attribute(Datum valu
*** 965,971 ****
  	 * Break down the tuple into fields.
  	 */
  	olddata = DatumGetHeapTupleHeader(value);
! 	Assert(typeId == HeapTupleHeaderGetTypeId(olddata));
  	Assert(typeMod == HeapTupleHeaderGetTypMod(olddata));
  	/* Build a temporary HeapTuple control structure */
  	tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
--- 966,973 ----
  	 * Break down the tuple into fields.
  	 */
  	olddata = DatumGetHeapTupleHeader(value);
! 	Assert((typeId == HeapTupleHeaderGetTypeId(olddata)
! 			|| (getBaseType(typeId) == HeapTupleHeaderGetTypeId(olddata))));
  	Assert(typeMod == HeapTupleHeaderGetTypMod(olddata));
  	/* Build a temporary HeapTuple control structure */
  	tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata);
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
new file mode 100644
index f8eb5bc..c9aef53
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** DefineDomain(CreateDomainStmt *stmt)
*** 814,827 ****
  	basetypeoid = HeapTupleGetOid(typeTup);
  
  	/*
! 	 * Base type must be a plain base type, another domain or an enum. Domains
! 	 * over pseudotypes would create a security hole.  Domains over composite
! 	 * types might be made to work in the future, but not today.
  	 */
  	typtype = baseType->typtype;
  	if (typtype != TYPTYPE_BASE &&
  		typtype != TYPTYPE_DOMAIN &&
! 		typtype != TYPTYPE_ENUM)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
  				 errmsg("\"%s\" is not a valid base type for a domain",
--- 814,827 ----
  	basetypeoid = HeapTupleGetOid(typeTup);
  
  	/*
! 	 * Base type must be a plain base type, composite type, another domain or
! 	 * an enum. Domains over pseudotypes would create a security hole.
  	 */
  	typtype = baseType->typtype;
  	if (typtype != TYPTYPE_BASE &&
  		typtype != TYPTYPE_DOMAIN &&
! 		typtype != TYPTYPE_ENUM &&
! 		typtype != TYPTYPE_COMPOSITE)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
  				 errmsg("\"%s\" is not a valid base type for a domain",
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
new file mode 100644
index 0418972..39a1835
*** a/src/backend/parser/parse_coerce.c
--- b/src/backend/parser/parse_coerce.c
*************** coerce_type(ParseState *pstate, Node *no
*** 369,382 ****
  		return result;
  	}
  	if (inputTypeId == RECORDOID &&
! 		ISCOMPLEX(targetTypeId))
  	{
  		/* Coerce a RECORD to a specific complex type */
  		return coerce_record_to_complex(pstate, node, targetTypeId,
  										ccontext, cformat, location);
  	}
  	if (targetTypeId == RECORDOID &&
! 		ISCOMPLEX(inputTypeId))
  	{
  		/* Coerce a specific complex type to RECORD */
  		/* NB: we do NOT want a RelabelType here */
--- 369,382 ----
  		return result;
  	}
  	if (inputTypeId == RECORDOID &&
! 		isComplex(targetTypeId))
  	{
  		/* Coerce a RECORD to a specific complex type */
  		return coerce_record_to_complex(pstate, node, targetTypeId,
  										ccontext, cformat, location);
  	}
  	if (targetTypeId == RECORDOID &&
! 		isComplex(inputTypeId))
  	{
  		/* Coerce a specific complex type to RECORD */
  		/* NB: we do NOT want a RelabelType here */
*************** can_coerce_type(int nargs, Oid *input_ty
*** 478,491 ****
  		 * coerce (may need tighter checking here)
  		 */
  		if (inputTypeId == RECORDOID &&
! 			ISCOMPLEX(targetTypeId))
  			continue;
  
  		/*
  		 * If input is a composite type and target is RECORD, accept
  		 */
  		if (targetTypeId == RECORDOID &&
! 			ISCOMPLEX(inputTypeId))
  			continue;
  
  #ifdef NOT_USED					/* not implemented yet */
--- 478,491 ----
  		 * coerce (may need tighter checking here)
  		 */
  		if (inputTypeId == RECORDOID &&
! 			isComplex(targetTypeId))
  			continue;
  
  		/*
  		 * If input is a composite type and target is RECORD, accept
  		 */
  		if (targetTypeId == RECORDOID &&
! 			isComplex(inputTypeId))
  			continue;
  
  #ifdef NOT_USED					/* not implemented yet */
*************** coerce_record_to_complex(ParseState *pst
*** 841,846 ****
--- 841,847 ----
  						 CoercionForm cformat,
  						 int location)
  {
+ 	Node       *result;
  	RowExpr    *rowexpr;
  	TupleDesc	tupdesc;
  	List	   *args = NIL;
*************** coerce_record_to_complex(ParseState *pst
*** 949,955 ****
  	rowexpr->row_format = cformat;
  	rowexpr->colnames = NIL;	/* not needed for named target type */
  	rowexpr->location = location;
! 	return (Node *) rowexpr;
  }
  
  /*
--- 950,965 ----
  	rowexpr->row_format = cformat;
  	rowexpr->colnames = NIL;	/* not needed for named target type */
  	rowexpr->location = location;
! 
! 	result = (Node *) rowexpr;
! 
! 	if (get_typtype(targetTypeId) == TYPTYPE_DOMAIN)
! 	{
! 		result = coerce_to_domain(result, InvalidOid, -1, targetTypeId,
! 								  cformat, location, true, false);
! 	}
! 
! 	return (Node *) result;
  }
  
  /*
*************** IsBinaryCoercible(Oid srctype, Oid targe
*** 1811,1817 ****
  
  	/* Also accept any composite type as coercible to RECORD */
  	if (targettype == RECORDOID)
! 		if (ISCOMPLEX(srctype))
  			return true;
  
  	/* Also accept any composite array type as coercible to RECORD[] */
--- 1821,1827 ----
  
  	/* Also accept any composite type as coercible to RECORD */
  	if (targettype == RECORDOID)
! 		if (isComplex(srctype))
  			return true;
  
  	/* Also accept any composite array type as coercible to RECORD[] */
*************** is_complex_array(Oid typid)
*** 2079,2085 ****
  {
  	Oid			elemtype = get_element_type(typid);
  
! 	return (OidIsValid(elemtype) && ISCOMPLEX(elemtype));
  }
  
  
--- 2089,2095 ----
  {
  	Oid			elemtype = get_element_type(typid);
  
! 	return (OidIsValid(elemtype) && isComplex(elemtype));
  }
  
  
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
new file mode 100644
index 08f0439..86a4b81
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** unknown_attribute(ParseState *pstate, No
*** 370,376 ****
  		/* Have to do it by reference to the type of the expression */
  		Oid			relTypeId = exprType(relref);
  
! 		if (ISCOMPLEX(relTypeId))
  			ereport(ERROR,
  					(errcode(ERRCODE_UNDEFINED_COLUMN),
  					 errmsg("column \"%s\" not found in data type %s",
--- 370,376 ----
  		/* Have to do it by reference to the type of the expression */
  		Oid			relTypeId = exprType(relref);
  
! 		if (isComplex(relTypeId))
  			ereport(ERROR,
  					(errcode(ERRCODE_UNDEFINED_COLUMN),
  					 errmsg("column \"%s\" not found in data type %s",
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index 75f1e20..172291e
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 178,184 ****
  	{
  		Oid			argtype = actual_arg_types[0];
  
! 		if (argtype == RECORDOID || ISCOMPLEX(argtype))
  		{
  			retval = ParseComplexProjection(pstate,
  											strVal(linitial(funcname)),
--- 178,184 ----
  	{
  		Oid			argtype = actual_arg_types[0];
  
! 		if (argtype == RECORDOID || isComplex(argtype))
  		{
  			retval = ParseComplexProjection(pstate,
  											strVal(linitial(funcname)),
*************** func_get_detail(List *funcname,
*** 1033,1039 ****
  							break;
  						case COERCION_PATH_COERCEVIAIO:
  							if ((sourceType == RECORDOID ||
! 								 ISCOMPLEX(sourceType)) &&
  							  TypeCategory(targetType) == TYPCATEGORY_STRING)
  								iscoercion = false;
  							else
--- 1033,1039 ----
  							break;
  						case COERCION_PATH_COERCEVIAIO:
  							if ((sourceType == RECORDOID ||
! 								 isComplex(sourceType)) &&
  							  TypeCategory(targetType) == TYPCATEGORY_STRING)
  								iscoercion = false;
  							else
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
new file mode 100644
index ac62cbc..1fbe958
*** a/src/backend/parser/parse_type.c
--- b/src/backend/parser/parse_type.c
*************** typeidTypeRelid(Oid type_id)
*** 630,635 ****
--- 630,648 ----
  	return result;
  }
  
+ bool
+ isComplex(Oid typeid)
+ {
+ 	Oid basetype;
+ 
+ 	if (typeidTypeRelid(typeid) != InvalidOid)
+ 		return true;
+ 
+ 	basetype = getBaseType(typeid);
+ 
+ 	return (typeidTypeRelid(basetype) != InvalidOid);
+ }
+ 
  /*
   * error context callback for parse failure during parseTypeString()
   */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
new file mode 100644
index d3b2a5a..de6789d
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
*************** getBaseTypeAndTypmod(Oid typid, int32 *t
*** 2132,2137 ****
--- 2132,2175 ----
  }
  
  /*
+  * getBaseTypeAndTypeAndRelid
+  *		If the given type is a domain, return its base type and relid.
+  */
+ Oid
+ getBaseTypeAndTypeAndRelid(Oid typid, char *type, Oid *relid)
+ {
+ 	/*
+ 	 * We loop to find the bottom base type in a stack of domains.
+ 	 */
+ 	for (;;)
+ 	{
+ 		HeapTuple	tup;
+ 		Form_pg_type typTup;
+ 
+ 		tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+ 		if (!HeapTupleIsValid(tup))
+ 			elog(ERROR, "cache lookup failed for type %u", typid);
+ 		typTup = (Form_pg_type) GETSTRUCT(tup);
+ 		if (typTup->typtype != TYPTYPE_DOMAIN)
+ 		{
+ 			*relid = typTup->typrelid;
+ 			*type = typTup->typtype;
+ 
+ 			/* Not a domain, so done */
+ 			ReleaseSysCache(tup);
+ 			break;
+ 		}
+ 
+ 		typid = typTup->typbasetype;
+ 
+ 		ReleaseSysCache(tup);
+ 	}
+ 
+ 	return typid;
+ }
+ 
+ 
+ /*
   * get_typavgwidth
   *
   *	  Given a type OID and a typmod value (pass -1 if typmod is unknown),
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
new file mode 100644
index 2769a30..a2c1e15
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
*************** lookup_type_cache(Oid type_id, int flags
*** 131,136 ****
--- 131,139 ----
  {
  	TypeCacheEntry *typentry;
  	bool		found;
+ 	char  basetyptype = 0;
+ 	Oid   basetyprelid = InvalidOid;
+ 	Oid   baseoid = InvalidOid;
  
  	if (TypeCacheHash == NULL)
  	{
*************** lookup_type_cache(Oid type_id, int flags
*** 190,195 ****
--- 193,204 ----
  		typentry->typtype = typtup->typtype;
  		typentry->typrelid = typtup->typrelid;
  
+ 		/*
+ 		 * If it's a domain type of a composite type, get the base type and relid
+ 		 */
+ 		if (typentry->typtype == TYPTYPE_DOMAIN)
+ 			baseoid = getBaseTypeAndTypeAndRelid(typtup->typbasetype, &basetyptype, &basetyprelid);
+ 
  		ReleaseSysCache(tp);
  	}
  
*************** lookup_type_cache(Oid type_id, int flags
*** 359,373 ****
  	 */
  	if ((flags & TYPECACHE_TUPDESC) &&
  		typentry->tupDesc == NULL &&
! 		typentry->typtype == TYPTYPE_COMPOSITE)
  	{
  		Relation	rel;
  
! 		if (!OidIsValid(typentry->typrelid))	/* should not happen */
  			elog(ERROR, "invalid typrelid for composite type %u",
! 				 typentry->type_id);
! 		rel = relation_open(typentry->typrelid, AccessShareLock);
! 		Assert(rel->rd_rel->reltype == typentry->type_id);
  
  		/*
  		 * Link to the tupdesc and increment its refcount (we assert it's a
--- 368,386 ----
  	 */
  	if ((flags & TYPECACHE_TUPDESC) &&
  		typentry->tupDesc == NULL &&
! 		((typentry->typtype == TYPTYPE_COMPOSITE) ||
! 		 ((typentry->typtype == TYPTYPE_DOMAIN) && (basetyptype == TYPTYPE_COMPOSITE))))
  	{
  		Relation	rel;
  
! 		Oid relid = typentry->typtype == TYPTYPE_COMPOSITE ? typentry->typrelid : basetyprelid;
! 		Oid type_id = typentry->typtype == TYPTYPE_COMPOSITE ? typentry->type_id : baseoid;
! 
! 		if (!OidIsValid(relid))	/* should not happen */
  			elog(ERROR, "invalid typrelid for composite type %u",
! 				 type_id);
! 		rel = relation_open(relid, AccessShareLock);
! 		Assert(rel->rd_rel->reltype == type_id);
  
  		/*
  		 * Link to the tupdesc and increment its refcount (we assert it's a
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
new file mode 100644
index aa249fa..a854600
*** a/src/backend/utils/fmgr/funcapi.c
--- b/src/backend/utils/fmgr/funcapi.c
***************
*** 20,25 ****
--- 20,26 ----
  #include "funcapi.h"
  #include "nodes/nodeFuncs.h"
  #include "parser/parse_coerce.h"
+ #include "parser/parse_type.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
*************** get_type_func_class(Oid typid)
*** 660,667 ****
  	{
  		case TYPTYPE_COMPOSITE:
  			return TYPEFUNC_COMPOSITE;
- 		case TYPTYPE_BASE:
  		case TYPTYPE_DOMAIN:
  		case TYPTYPE_ENUM:
  			return TYPEFUNC_SCALAR;
  		case TYPTYPE_PSEUDO:
--- 661,672 ----
  	{
  		case TYPTYPE_COMPOSITE:
  			return TYPEFUNC_COMPOSITE;
  		case TYPTYPE_DOMAIN:
+ 			if (isComplex(typid))
+ 				return TYPEFUNC_COMPOSITE;
+ 			else
+ 				return TYPEFUNC_SCALAR;
+ 		case TYPTYPE_BASE:
  		case TYPTYPE_ENUM:
  			return TYPEFUNC_SCALAR;
  		case TYPTYPE_PSEUDO:
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
new file mode 100644
index 373acd8..d0c15cf
*** a/src/include/parser/parse_type.h
--- b/src/include/parser/parse_type.h
*************** extern Oid	typeidTypeRelid(Oid type_id);
*** 47,52 ****
  
  extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p);
  
! #define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid)
  
  #endif   /* PARSE_TYPE_H */
--- 47,53 ----
  
  extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p);
  
! extern bool isComplex(Oid typeid);
! 
  
  #endif   /* PARSE_TYPE_H */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
new file mode 100644
index 0a419dc..750cdd4
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
*************** extern Oid	get_typcollation(Oid typid);
*** 129,134 ****
--- 129,135 ----
  extern bool type_is_collatable(Oid typid);
  extern Oid	getBaseType(Oid typid);
  extern Oid	getBaseTypeAndTypmod(Oid typid, int32 *typmod);
+ extern Oid  getBaseTypeAndTypeAndRelid(Oid typid, char *type, Oid *relid);
  extern int32 get_typavgwidth(Oid typid, int32 typmod);
  extern int32 get_attavgwidth(Oid relid, AttrNumber attnum);
  extern bool get_attstatsslot(HeapTuple statstuple,
diff --git a/src/test/regress/expected/domaincomp.out b/src/test/regress/expected/domaincomp.out
new file mode 100644
index ...191fa69
*** a/src/test/regress/expected/domaincomp.out
--- b/src/test/regress/expected/domaincomp.out
***************
*** 0 ****
--- 1,207 ----
+ --
+ -- Test domains on composite types.
+ --
+ create type droptest as (
+        r    double precision,
+        i    double precision
+ );
+ -- Test Comment / Drop
+ create domain domaindroptest droptest;
+ comment on domain domaindroptest is 'About to drop this..';
+ create domain dependenttypetest domaindroptest;
+ -- fail because of dependent type
+ drop domain domaindroptest;
+ ERROR:  cannot drop type domaindroptest because other objects depend on it
+ DETAIL:  type dependenttypetest depends on type domaindroptest
+ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ drop domain domaindroptest cascade;
+ NOTICE:  drop cascades to type dependenttypetest
+ -- this should fail because already gone
+ drop domain domaindroptest cascade;
+ ERROR:  type "domaindroptest" does not exist
+ drop type droptest;
+ -- test table type
+ create table inventory_item (
+        name             text,
+        supplier_id      integer,
+        price            numeric
+ );
+ create domain item_d as inventory_item
+ check (VALUE IS NULL OR (VALUE).price IS NOT NULL);
+ create domain cheap_d as inventory_item
+ check ((VALUE).price < 1);
+ create domain expensive_d as inventory_item
+ check ((VALUE).price > 10);
+ -- Test domain input.
+ -- Note: the point of checking both INSERT and COPY FROM is that INSERT
+ -- exercises CoerceToDomain while COPY exercises domain_in.
+ -- Test explicit coercions --- these should succeed
+ SELECT cast(ROW('fuzzy dice', 42, 1.99) as item_d);
+           row           
+ ------------------------
+  ("fuzzy dice",42,1.99)
+ (1 row)
+ 
+ -- Test tables using domains
+ create table basictest
+            ( test item_d,
+              cheap cheap_d,
+              expensive expensive_d
+            );
+ INSERT INTO basictest values (NULL, ROW('fuzzy dice', 42, 1.99), ROW('fuzzy dice', 42, 11.99)); -- bad cheap_d
+ ERROR:  value for domain cheap_d violates check constraint "cheap_d_check"
+ INSERT INTO basictest values (ROW('normal dice', 42, NULL), NULL, NULL); -- Bad item_d with NULL price
+ ERROR:  value for domain item_d violates check constraint "item_d_check"
+ INSERT INTO basictest values (NULL, ROW('fuzzy dice', 42, 0.99), ROW('fuzzy dice', 42, 11.99)); -- good
+ -- Test copy
+ COPY basictest (cheap) FROM stdin; -- fail
+ ERROR:  value for domain cheap_d violates check constraint "cheap_d_check"
+ CONTEXT:  COPY basictest, line 1, column cheap: "("fuzzy dice",42, 999)"
+ COPY basictest (cheap) FROM stdin;
+ select * from basictest;
+  test |         cheap          |        expensive        
+ ------+------------------------+-------------------------
+       | ("fuzzy dice",42,0.99) | ("fuzzy dice",42,11.99)
+       | ("fuzzy dice",42,0.9)  | 
+ (2 rows)
+ 
+ -- check that domains inherit operations from base types
+ select cheap = cast(ROW('fuzzy dice',42, 0.99) as inventory_item)
+ from basictest;
+  ?column? 
+ ----------
+  t
+  f
+ (2 rows)
+ 
+ drop table basictest;
+ drop domain item_d;
+ drop domain cheap_d;
+ drop domain expensive_d;
+ create domain dnotnull inventory_item NOT NULL;
+ create domain dnull    inventory_item;
+ create domain dcheck   inventory_item NOT NULL CHECK ((VALUE).name = 'a' OR (VALUE).name = 'c' OR (VALUE).name = 'd');
+ create table nulltest
+            ( col1 dnotnull
+            , col2 dnotnull NULL  -- NOT NULL in the domain cannot be overridden
+            , col3 dnull    NOT NULL
+            , col4 dnull
+            , col5 dcheck CHECK ((col5).name IN ('c', 'd'))
+            );
+ INSERT INTO nulltest DEFAULT VALUES;
+ ERROR:  domain dnotnull does not allow null values
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('c',NULL,NULL));  -- Good
+ insert into nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), NULL);
+ ERROR:  domain dcheck does not allow null values
+ insert into nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('a',NULL,NULL));
+ ERROR:  new row for relation "nulltest" violates check constraint "nulltest_col5_check"
+ INSERT INTO nulltest values (NULL, ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('d',NULL,NULL));
+ ERROR:  domain dnotnull does not allow null values
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), NULL, ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('c',NULL,NULL));
+ ERROR:  domain dnotnull does not allow null values
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), NULL, ROW('d',NULL,NULL), ROW('c',NULL,NULL));
+ ERROR:  null value in column "col3" violates not-null constraint
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), NULL, ROW('d',NULL,NULL)); -- Good
+ -- Test copy
+ COPY nulltest FROM stdin; --fail
+ ERROR:  null value in column "col3" violates not-null constraint
+ CONTEXT:  COPY nulltest, line 1: "(a,,)	(b,,)	\N	(d,,)	(d,,)"
+ COPY nulltest FROM stdin; --fail
+ ERROR:  domain dcheck does not allow null values
+ CONTEXT:  COPY nulltest, line 1, column col5: null input
+ -- Last row is bad
+ COPY nulltest FROM stdin;
+ ERROR:  new row for relation "nulltest" violates check constraint "nulltest_col5_check"
+ CONTEXT:  COPY nulltest, line 3: "(a,,)	(b,,)	(c,,)	\N	(a,,)"
+ select * from nulltest;
+  col1  | col2  | col3  | col4  | col5  
+ -------+-------+-------+-------+-------
+  (a,,) | (b,,) | (c,,) | (d,,) | (c,,)
+  (a,,) | (b,,) | (c,,) |       | (d,,)
+ (2 rows)
+ 
+ -- Test out coerced (casted) constraints
+ SELECT cast(ROW('1',NULL,NULL) as dnotnull);
+   row  
+ -------
+  (1,,)
+ (1 row)
+ 
+ SELECT cast(NULL as dnotnull); -- fail
+ ERROR:  domain dnotnull does not allow null values
+ SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
+ ERROR:  domain dnotnull does not allow null values
+ SELECT cast(col4 as dnotnull) from nulltest; -- fail
+ ERROR:  domain dnotnull does not allow null values
+ -- cleanup
+ drop table nulltest;
+ drop domain dnotnull restrict;
+ drop domain dnull restrict;
+ drop domain dcheck restrict;
+ create domain ddef1 inventory_item DEFAULT ROW('f',1,2);
+ -- Type mixing, function returns int8
+ create domain ddef3 inventory_item DEFAULT ROW(5,NULL,NULL);
+ create sequence ddef4_seq;
+ create domain ddef4 inventory_item DEFAULT ROW(nextval('ddef4_seq'),nextval('ddef4_seq'),nextval('ddef4_seq'));
+ create domain ddef5 inventory_item NOT NULL DEFAULT ROW(NULL,12.12,NULL);
+ create table defaulttest
+             ( col1 ddef1
+             , col3 ddef3
+             , col4 ddef4 PRIMARY KEY
+             , col5 ddef1 NOT NULL DEFAULT NULL
+             , col7 ddef4 DEFAULT ROW(8000,8000,8000)
+             , col8 ddef5
+             );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "defaulttest_pkey" for table "defaulttest"
+ insert into defaulttest(col4) values(ROW(0,0,0)); -- fails, col5 defaults to null
+ ERROR:  null value in column "col5" violates not-null constraint
+ alter table defaulttest alter column col5 drop default;
+ insert into defaulttest default values; -- succeeds, inserts domain default
+ -- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
+ alter table defaulttest alter column col5 set default null;
+ insert into defaulttest(col4) values(ROW(0,0,0)); -- fails
+ ERROR:  null value in column "col5" violates not-null constraint
+ alter table defaulttest alter column col5 drop default;
+ insert into defaulttest default values;
+ insert into defaulttest default values;
+ -- Test defaults with copy
+ COPY defaulttest(col5) FROM stdin;
+ select * from defaulttest;
+   col1   | col3  |    col4    |  col5   |       col7       |  col8  
+ ---------+-------+------------+---------+------------------+--------
+  (f,1,2) | (5,,) | (1,2,3)    | (f,1,2) | (8000,8000,8000) | (,12,)
+  (f,1,2) | (5,,) | (4,5,6)    | (f,1,2) | (8000,8000,8000) | (,12,)
+  (f,1,2) | (5,,) | (7,8,9)    | (f,1,2) | (8000,8000,8000) | (,12,)
+  (f,1,2) | (5,,) | (10,11,12) | (a,,)   | (8000,8000,8000) | (,12,)
+ (4 rows)
+ 
+ drop table defaulttest cascade;
+ -- Test domains over domains
+ create domain level1 inventory_item;
+ create domain level2 level1 check ((VALUE).price > 10);
+ create domain level3 level2 check ((VALUE).price > 100);
+ select ROW(NULL,NULL,1)::level1;
+   row  
+ -------
+  (,,1)
+ (1 row)
+ 
+ select ROW(NULL,NULL,1)::level2; --fail
+ ERROR:  value for domain level2 violates check constraint "level2_check"
+ select ROW(NULL,NULL,20)::level3; --fail
+ ERROR:  value for domain level3 violates check constraint "level3_check"
+ create temp table dtest(f1 level3);
+ insert into dtest values(ROW(NULL,NULL,200));
+ insert into dtest values(ROW(NULL,NULL,20)); -- fail
+ ERROR:  value for domain level3 violates check constraint "level3_check"
+ drop table dtest;
+ drop domain level1 cascade;
+ NOTICE:  drop cascades to 2 other objects
+ DETAIL:  drop cascades to type level2
+ drop cascades to type level3
+ drop table inventory_item cascade;
+ NOTICE:  drop cascades to 4 other objects
+ DETAIL:  drop cascades to type ddef1
+ drop cascades to type ddef3
+ drop cascades to type ddef4
+ drop cascades to type ddef5
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index 376f28d..1aec114
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: select_views portals_p2 foreign_ke
*** 97,101 ****
--- 97,103 ----
  # ----------
  test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
  
+ test: domaincomp
+ 
  # run stats by itself because its delay may be insufficient under heavy load
  test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index bb654f9..fcfe75f
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: plpgsql
*** 113,118 ****
--- 113,119 ----
  test: copy2
  test: temp
  test: domain
+ test: domaincomp
  test: rangefuncs
  test: prepare
  test: without_oid
diff --git a/src/test/regress/sql/domaincomp.sql b/src/test/regress/sql/domaincomp.sql
new file mode 100644
index ...00f55c9
*** a/src/test/regress/sql/domaincomp.sql
--- b/src/test/regress/sql/domaincomp.sql
***************
*** 0 ****
--- 1,182 ----
+ --
+ -- Test domains on composite types.
+ --
+ 
+ create type droptest as (
+        r    double precision,
+        i    double precision
+ );
+ 
+ -- Test Comment / Drop
+ create domain domaindroptest droptest;
+ comment on domain domaindroptest is 'About to drop this..';
+ 
+ create domain dependenttypetest domaindroptest;
+ 
+ -- fail because of dependent type
+ drop domain domaindroptest;
+ 
+ drop domain domaindroptest cascade;
+ 
+ -- this should fail because already gone
+ drop domain domaindroptest cascade;
+ 
+ drop type droptest;
+ 
+ -- test table type
+ create table inventory_item (
+        name             text,
+        supplier_id      integer,
+        price            numeric
+ );
+ 
+ create domain item_d as inventory_item
+ check (VALUE IS NULL OR (VALUE).price IS NOT NULL);
+ create domain cheap_d as inventory_item
+ check ((VALUE).price < 1);
+ create domain expensive_d as inventory_item
+ check ((VALUE).price > 10);
+ 
+ -- Test domain input.
+ 
+ -- Note: the point of checking both INSERT and COPY FROM is that INSERT
+ -- exercises CoerceToDomain while COPY exercises domain_in.
+ 
+ -- Test explicit coercions --- these should succeed
+ SELECT cast(ROW('fuzzy dice', 42, 1.99) as item_d);
+ 
+ -- Test tables using domains
+ create table basictest
+            ( test item_d,
+              cheap cheap_d,
+              expensive expensive_d
+            );
+ 
+ INSERT INTO basictest values (NULL, ROW('fuzzy dice', 42, 1.99), ROW('fuzzy dice', 42, 11.99)); -- bad cheap_d
+ INSERT INTO basictest values (ROW('normal dice', 42, NULL), NULL, NULL); -- Bad item_d with NULL price
+ INSERT INTO basictest values (NULL, ROW('fuzzy dice', 42, 0.99), ROW('fuzzy dice', 42, 11.99)); -- good
+ 
+ -- Test copy
+ COPY basictest (cheap) FROM stdin; -- fail
+ ("fuzzy dice",42, 999)
+ \.
+ 
+ COPY basictest (cheap) FROM stdin;
+ ("fuzzy dice",42, 0.9)
+ \.
+ 
+ select * from basictest;
+ 
+ -- check that domains inherit operations from base types
+ select cheap = cast(ROW('fuzzy dice',42, 0.99) as inventory_item)
+ from basictest;
+ 
+ drop table basictest;
+ drop domain item_d;
+ drop domain cheap_d;
+ drop domain expensive_d;
+ 
+ create domain dnotnull inventory_item NOT NULL;
+ create domain dnull    inventory_item;
+ create domain dcheck   inventory_item NOT NULL CHECK ((VALUE).name = 'a' OR (VALUE).name = 'c' OR (VALUE).name = 'd');
+ 
+ create table nulltest
+            ( col1 dnotnull
+            , col2 dnotnull NULL  -- NOT NULL in the domain cannot be overridden
+            , col3 dnull    NOT NULL
+            , col4 dnull
+            , col5 dcheck CHECK ((col5).name IN ('c', 'd'))
+            );
+ INSERT INTO nulltest DEFAULT VALUES;
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('c',NULL,NULL));  -- Good
+ insert into nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), NULL);
+ insert into nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('a',NULL,NULL));
+ INSERT INTO nulltest values (NULL, ROW('b',NULL,NULL), ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('d',NULL,NULL));
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), NULL, ROW('c',NULL,NULL), ROW('d',NULL,NULL), ROW('c',NULL,NULL));
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), NULL, ROW('d',NULL,NULL), ROW('c',NULL,NULL));
+ INSERT INTO nulltest values (ROW('a',NULL,NULL), ROW('b',NULL,NULL), ROW('c',NULL,NULL), NULL, ROW('d',NULL,NULL)); -- Good
+ 
+ -- Test copy
+ COPY nulltest FROM stdin; --fail
+ (a,,)	(b,,)	\N	(d,,)	(d,,)
+ \.
+ 
+ COPY nulltest FROM stdin; --fail
+ (a,,)	(b,,)	(c,,)	(d,,)	\N
+ \.
+ 
+ -- Last row is bad
+ COPY nulltest FROM stdin;
+ (a,,)	(b,,)	(c,,)	\N	(c,,)
+ (a,,)	(b,,)	(c,,)	\N	(d,,)
+ (a,,)	(b,,)	(c,,)	\N	(a,,)
+ \.
+ 
+ select * from nulltest;
+ 
+ -- Test out coerced (casted) constraints
+ SELECT cast(ROW('1',NULL,NULL) as dnotnull);
+ SELECT cast(NULL as dnotnull); -- fail
+ SELECT cast(cast(NULL as dnull) as dnotnull); -- fail
+ SELECT cast(col4 as dnotnull) from nulltest; -- fail
+ 
+ -- cleanup
+ drop table nulltest;
+ drop domain dnotnull restrict;
+ drop domain dnull restrict;
+ drop domain dcheck restrict;
+ 
+ 
+ create domain ddef1 inventory_item DEFAULT ROW('f',1,2);
+ -- Type mixing, function returns int8
+ create domain ddef3 inventory_item DEFAULT ROW(5,NULL,NULL);
+ create sequence ddef4_seq;
+ create domain ddef4 inventory_item DEFAULT ROW(nextval('ddef4_seq'),nextval('ddef4_seq'),nextval('ddef4_seq'));
+ create domain ddef5 inventory_item NOT NULL DEFAULT ROW(NULL,12.12,NULL);
+ 
+ create table defaulttest
+             ( col1 ddef1
+             , col3 ddef3
+             , col4 ddef4 PRIMARY KEY
+             , col5 ddef1 NOT NULL DEFAULT NULL
+             , col7 ddef4 DEFAULT ROW(8000,8000,8000)
+             , col8 ddef5
+             );
+ insert into defaulttest(col4) values(ROW(0,0,0)); -- fails, col5 defaults to null
+ alter table defaulttest alter column col5 drop default;
+ insert into defaulttest default values; -- succeeds, inserts domain default
+ -- We used to treat SET DEFAULT NULL as equivalent to DROP DEFAULT; wrong
+ alter table defaulttest alter column col5 set default null;
+ insert into defaulttest(col4) values(ROW(0,0,0)); -- fails
+ alter table defaulttest alter column col5 drop default;
+ insert into defaulttest default values;
+ insert into defaulttest default values;
+ 
+ -- Test defaults with copy
+ COPY defaulttest(col5) FROM stdin;
+ (a,,)
+ \.
+ 
+ select * from defaulttest;
+ 
+ drop table defaulttest cascade;
+ 
+ -- Test domains over domains
+ create domain level1 inventory_item;
+ create domain level2 level1 check ((VALUE).price > 10);
+ create domain level3 level2 check ((VALUE).price > 100);
+ 
+ select ROW(NULL,NULL,1)::level1;
+ select ROW(NULL,NULL,1)::level2; --fail
+ select ROW(NULL,NULL,20)::level3; --fail
+ 
+ create temp table dtest(f1 level3);
+ 
+ insert into dtest values(ROW(NULL,NULL,200));
+ insert into dtest values(ROW(NULL,NULL,20)); -- fail
+ 
+ drop table dtest;
+ drop domain level1 cascade;
+ 
+ drop table inventory_item cascade;
+ 
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to