I started to look into allowing domains over composite types, which is
another never-implemented case that there's no very good reason not to
allow.  Well, other than the argument that the SQL standard only allows
domains over "predefined" (built-in) types ... but we blew past that
restriction ages ago.

Any thought that there might be some fundamental problem with that was
soon dispelled when I noticed that we allow domains over arrays of
composite types.  Ahem.

They even work, mostly.  I wrote a few test cases and couldn't find
anything that failed except for attempts to insert or update multiple
subfields of the same base column; that's because process_matched_tle()
fails to look through CoerceToDomain nodes.  But that turns out to be a
bug even for the simpler case of domains over arrays of scalars, which
is certainly supported.  That is, given

create domain domainint4arr int4[];
create table domarrtest (testint4arr domainint4arr);

this ought to work:

insert into domarrtest (testint4arr[1], testint4arr[3]) values (11,22);

It would work with a plain-array target column, but with the domain
it fails with

ERROR:  multiple assignments to same column "testint4arr"

Hence, the attached proposed patch, which fixes that oversight and
adds some relevant test cases.  (I've not yet looked to see if any
documentation changes would be appropriate.)

I think this is a bug fix and should be back-patched.  Any objections?

                        regards, tom lane

diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index f3c7526..0a6f4f3 100644
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** process_matched_tle(TargetEntry *src_tle
*** 934,939 ****
--- 934,940 ----
  					const char *attrName)
  {
  	TargetEntry *result;
+ 	CoerceToDomain *coerce_expr = NULL;
  	Node	   *src_expr;
  	Node	   *prior_expr;
  	Node	   *src_input;
*************** process_matched_tle(TargetEntry *src_tle
*** 970,979 ****
--- 971,1000 ----
  	 * For FieldStore, instead of nesting we can generate a single
  	 * FieldStore with multiple target fields.  We must nest when
  	 * ArrayRefs are involved though.
+ 	 *
+ 	 * As a further complication, the destination column might be a domain,
+ 	 * resulting in each assignment containing a CoerceToDomain node over a
+ 	 * FieldStore or ArrayRef.  These should have matching target domains, so
+ 	 * we strip them and reconstitute a single CoerceToDomain over the
+ 	 * combined FieldStore/ArrayRef nodes.  (Notice that this has the result
+ 	 * that the domain's checks are applied only after we do all the field or
+ 	 * element updates, not after each one.  This is arguably desirable.)
  	 *----------
  	 */
  	src_expr = (Node *) src_tle->expr;
  	prior_expr = (Node *) prior_tle->expr;
+ 
+ 	if (src_expr && IsA(src_expr, CoerceToDomain) &&
+ 		prior_expr && IsA(prior_expr, CoerceToDomain) &&
+ 		((CoerceToDomain *) src_expr)->resulttype ==
+ 		((CoerceToDomain *) prior_expr)->resulttype)
+ 	{
+ 		/* we assume without checking that resulttypmod/resultcollid match */
+ 		coerce_expr = (CoerceToDomain *) src_expr;
+ 		src_expr = (Node *) ((CoerceToDomain *) src_expr)->arg;
+ 		prior_expr = (Node *) ((CoerceToDomain *) prior_expr)->arg;
+ 	}
+ 
  	src_input = get_assignment_input(src_expr);
  	prior_input = get_assignment_input(prior_expr);
  	if (src_input == NULL ||
*************** process_matched_tle(TargetEntry *src_tle
*** 1042,1047 ****
--- 1063,1078 ----
  		newexpr = NULL;
  	}
  
+ 	if (coerce_expr)
+ 	{
+ 		/* put back the CoerceToDomain */
+ 		CoerceToDomain *newcoerce = makeNode(CoerceToDomain);
+ 
+ 		memcpy(newcoerce, coerce_expr, sizeof(CoerceToDomain));
+ 		newcoerce->arg = (Expr *) newexpr;
+ 		newexpr = (Node *) newcoerce;
+ 	}
+ 
  	result = flatCopyTargetEntry(src_tle);
  	result->expr = (Expr *) newexpr;
  	return result;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 41b70e2..9fb750d 100644
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
*************** INSERT INTO domarrtest values ('{2,2}', 
*** 107,112 ****
--- 107,113 ----
  INSERT INTO domarrtest values (NULL, '{{"a","b","c"},{"d","e","f"}}');
  INSERT INTO domarrtest values (NULL, '{{"toolong","b","c"},{"d","e","f"}}');
  ERROR:  value too long for type character varying(4)
+ INSERT INTO domarrtest (testint4arr[1], testint4arr[3]) values (11,22);
  select * from domarrtest;
    testint4arr  |    testchar4arr     
  ---------------+---------------------
*************** select * from domarrtest;
*** 115,121 ****
   {2,2}         | {{a,b},{c,d},{e,f}}
   {2,2}         | {{a},{c}}
                 | {{a,b,c},{d,e,f}}
! (5 rows)
  
  select testint4arr[1], testchar4arr[2:2] from domarrtest;
   testint4arr | testchar4arr 
--- 116,123 ----
   {2,2}         | {{a,b},{c,d},{e,f}}
   {2,2}         | {{a},{c}}
                 | {{a,b,c},{d,e,f}}
!  {11,NULL,22}  | 
! (6 rows)
  
  select testint4arr[1], testchar4arr[2:2] from domarrtest;
   testint4arr | testchar4arr 
*************** select testint4arr[1], testchar4arr[2:2]
*** 125,131 ****
             2 | {{c,d}}
             2 | {{c}}
               | {{d,e,f}}
! (5 rows)
  
  select array_dims(testint4arr), array_dims(testchar4arr) from domarrtest;
   array_dims | array_dims 
--- 127,134 ----
             2 | {{c,d}}
             2 | {{c}}
               | {{d,e,f}}
!           11 | 
! (6 rows)
  
  select array_dims(testint4arr), array_dims(testchar4arr) from domarrtest;
   array_dims | array_dims 
*************** select array_dims(testint4arr), array_di
*** 135,141 ****
   [1:2]      | [1:3][1:2]
   [1:2]      | [1:2][1:1]
              | [1:2][1:3]
! (5 rows)
  
  COPY domarrtest FROM stdin;
  COPY domarrtest FROM stdin;	-- fail
--- 138,145 ----
   [1:2]      | [1:3][1:2]
   [1:2]      | [1:2][1:1]
              | [1:2][1:3]
!  [1:3]      | 
! (6 rows)
  
  COPY domarrtest FROM stdin;
  COPY domarrtest FROM stdin;	-- fail
*************** select * from domarrtest;
*** 149,157 ****
   {2,2}         | {{a,b},{c,d},{e,f}}
   {2,2}         | {{a},{c}}
                 | {{a,b,c},{d,e,f}}
   {3,4}         | {q,w,e}
                 | 
! (7 rows)
  
  drop table domarrtest;
  drop domain domainint4arr restrict;
--- 153,173 ----
   {2,2}         | {{a,b},{c,d},{e,f}}
   {2,2}         | {{a},{c}}
                 | {{a,b,c},{d,e,f}}
+  {11,NULL,22}  | 
   {3,4}         | {q,w,e}
                 | 
! (8 rows)
! 
! update domarrtest set
!   testint4arr[1] = testint4arr[1] + 1,
!   testint4arr[3] = testint4arr[3] - 1
! where testchar4arr is null;
! select * from domarrtest where testchar4arr is null;
!    testint4arr    | testchar4arr 
! ------------------+--------------
!  {12,NULL,21}     | 
!  {NULL,NULL,NULL} | 
! (2 rows)
  
  drop table domarrtest;
  drop domain domainint4arr restrict;
*************** select pg_typeof('{1,2,3}'::dia || 42); 
*** 182,187 ****
--- 198,289 ----
  (1 row)
  
  drop domain dia;
+ -- Test domains over arrays of composite
+ create type comptype as (r float8, i float8);
+ create domain dcomptypea as comptype[];
+ create table dcomptable (d1 dcomptypea unique);
+ insert into dcomptable values (array[row(1,2)]::dcomptypea);
+ insert into dcomptable values (array[row(3,4), row(5,6)]::comptype[]);
+ insert into dcomptable values (array[row(7,8)::comptype, row(9,10)::comptype]);
+ insert into dcomptable values (array[row(1,2)]::dcomptypea);  -- fail on uniqueness
+ ERROR:  duplicate key value violates unique constraint "dcomptable_d1_key"
+ DETAIL:  Key (d1)=({"(1,2)"}) already exists.
+ insert into dcomptable (d1[1]) values(row(9,10));
+ insert into dcomptable (d1[1].r) values(11);
+ select * from dcomptable;
+          d1         
+ --------------------
+  {"(1,2)"}
+  {"(3,4)","(5,6)"}
+  {"(7,8)","(9,10)"}
+  {"(9,10)"}
+  {"(11,)"}
+ (5 rows)
+ 
+ select d1[2], d1[1].r, d1[1].i from dcomptable;
+    d1   | r  | i  
+ --------+----+----
+         |  1 |  2
+  (5,6)  |  3 |  4
+  (9,10) |  7 |  8
+         |  9 | 10
+         | 11 |   
+ (5 rows)
+ 
+ update dcomptable set d1[2] = row(d1[2].i, d1[2].r);
+ select * from dcomptable;
+          d1         
+ --------------------
+  {"(1,2)","(,)"}
+  {"(3,4)","(6,5)"}
+  {"(7,8)","(10,9)"}
+  {"(9,10)","(,)"}
+  {"(11,)","(,)"}
+ (5 rows)
+ 
+ update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0;
+ select * from dcomptable;
+          d1         
+ --------------------
+  {"(11,)","(,)"}
+  {"(2,2)","(,)"}
+  {"(4,4)","(6,5)"}
+  {"(8,8)","(10,9)"}
+  {"(10,10)","(,)"}
+ (5 rows)
+ 
+ alter domain dcomptypea add constraint c1 check (value[1].r <= value[1].i);
+ alter domain dcomptypea add constraint c2 check (value[1].r > value[1].i);  -- fail
+ ERROR:  column "d1" of table "dcomptable" contains values that violate the new constraint
+ select array[row(2,1)]::dcomptypea;  -- fail
+ ERROR:  value for domain dcomptypea violates check constraint "c1"
+ insert into dcomptable values (array[row(1,2)]::comptype[]);
+ insert into dcomptable values (array[row(2,1)]::comptype[]);  -- fail
+ ERROR:  value for domain dcomptypea violates check constraint "c1"
+ insert into dcomptable (d1[1].r) values(99);
+ insert into dcomptable (d1[1].r, d1[1].i) values(99, 100);
+ insert into dcomptable (d1[1].r, d1[1].i) values(100, 99);  -- fail
+ ERROR:  value for domain dcomptypea violates check constraint "c1"
+ update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0;  -- fail
+ ERROR:  value for domain dcomptypea violates check constraint "c1"
+ update dcomptable set d1[1].r = d1[1].r - 1 where d1[1].i > 0;
+ select * from dcomptable;
+          d1         
+ --------------------
+  {"(11,)","(,)"}
+  {"(99,)"}
+  {"(1,2)","(,)"}
+  {"(3,4)","(6,5)"}
+  {"(7,8)","(10,9)"}
+  {"(9,10)","(,)"}
+  {"(0,2)"}
+  {"(98,100)"}
+ (8 rows)
+ 
+ drop table dcomptable;
+ drop type comptype cascade;
+ NOTICE:  drop cascades to type dcomptypea
+ -- Test not-null restrictions
  create domain dnotnull varchar(15) NOT NULL;
  create domain dnull    varchar(15);
  create domain dcheck   varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd');
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 407d3ef..1fd7b11 100644
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
*************** INSERT INTO domarrtest values ('{2,2}', 
*** 85,90 ****
--- 85,91 ----
  INSERT INTO domarrtest values ('{2,2}', '{{"a"},{"c"}}');
  INSERT INTO domarrtest values (NULL, '{{"a","b","c"},{"d","e","f"}}');
  INSERT INTO domarrtest values (NULL, '{{"toolong","b","c"},{"d","e","f"}}');
+ INSERT INTO domarrtest (testint4arr[1], testint4arr[3]) values (11,22);
  select * from domarrtest;
  select testint4arr[1], testchar4arr[2:2] from domarrtest;
  select array_dims(testint4arr), array_dims(testchar4arr) from domarrtest;
*************** COPY domarrtest FROM stdin;	-- fail
*** 100,105 ****
--- 101,113 ----
  
  select * from domarrtest;
  
+ update domarrtest set
+   testint4arr[1] = testint4arr[1] + 1,
+   testint4arr[3] = testint4arr[3] - 1
+ where testchar4arr is null;
+ 
+ select * from domarrtest where testchar4arr is null;
+ 
  drop table domarrtest;
  drop domain domainint4arr restrict;
  drop domain domainchar4arr restrict;
*************** select pg_typeof('{1,2,3}'::dia);
*** 111,116 ****
--- 119,164 ----
  select pg_typeof('{1,2,3}'::dia || 42); -- should be int[] not dia
  drop domain dia;
  
+ 
+ -- Test domains over arrays of composite
+ 
+ create type comptype as (r float8, i float8);
+ create domain dcomptypea as comptype[];
+ create table dcomptable (d1 dcomptypea unique);
+ 
+ insert into dcomptable values (array[row(1,2)]::dcomptypea);
+ insert into dcomptable values (array[row(3,4), row(5,6)]::comptype[]);
+ insert into dcomptable values (array[row(7,8)::comptype, row(9,10)::comptype]);
+ insert into dcomptable values (array[row(1,2)]::dcomptypea);  -- fail on uniqueness
+ insert into dcomptable (d1[1]) values(row(9,10));
+ insert into dcomptable (d1[1].r) values(11);
+ 
+ select * from dcomptable;
+ select d1[2], d1[1].r, d1[1].i from dcomptable;
+ update dcomptable set d1[2] = row(d1[2].i, d1[2].r);
+ select * from dcomptable;
+ update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0;
+ select * from dcomptable;
+ 
+ alter domain dcomptypea add constraint c1 check (value[1].r <= value[1].i);
+ alter domain dcomptypea add constraint c2 check (value[1].r > value[1].i);  -- fail
+ 
+ select array[row(2,1)]::dcomptypea;  -- fail
+ insert into dcomptable values (array[row(1,2)]::comptype[]);
+ insert into dcomptable values (array[row(2,1)]::comptype[]);  -- fail
+ insert into dcomptable (d1[1].r) values(99);
+ insert into dcomptable (d1[1].r, d1[1].i) values(99, 100);
+ insert into dcomptable (d1[1].r, d1[1].i) values(100, 99);  -- fail
+ update dcomptable set d1[1].r = d1[1].r + 1 where d1[1].i > 0;  -- fail
+ update dcomptable set d1[1].r = d1[1].r - 1 where d1[1].i > 0;
+ select * from dcomptable;
+ 
+ drop table dcomptable;
+ drop type comptype cascade;
+ 
+ 
+ -- Test not-null restrictions
+ 
  create domain dnotnull varchar(15) NOT NULL;
  create domain dnull    varchar(15);
  create domain dcheck   varchar(15) NOT NULL CHECK (VALUE = 'a' OR VALUE = 'c' OR VALUE = 'd');
-- 
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