Skip to content

Commit 820c030

Browse files
committed
Support domains over composite types in PL/Tcl.
Since PL/Tcl does little with SQL types internally, this is just a matter of making it work with composite-domain function arguments and results. In passing, make it allow RECORD-type arguments --- that's a trivial change that nobody had bothered with up to now. Discussion: https://postgr.es/m/4206.1499798337@sss.pgh.pa.us
1 parent 37a795a commit 820c030

File tree

3 files changed

+193
-26
lines changed

3 files changed

+193
-26
lines changed

src/pl/tcl/expected/pltcl_queries.out

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,46 @@ select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
327327
ref2
328328
(1 row)
329329

330+
-- More tests for composite argument/result types
331+
create domain d_dta1 as T_dta1 check ((value).ref1 > 0);
332+
create function tcl_record_arg(record, fldname text) returns int as '
333+
return $1($2)
334+
' language pltcl;
335+
select tcl_record_arg(row('tkey', 42, 'ref2')::T_dta1, 'ref1');
336+
tcl_record_arg
337+
----------------
338+
42
339+
(1 row)
340+
341+
select tcl_record_arg(row('tkey', 42, 'ref2')::d_dta1, 'ref1');
342+
tcl_record_arg
343+
----------------
344+
42
345+
(1 row)
346+
347+
select tcl_record_arg(row(2,4), 'f2');
348+
tcl_record_arg
349+
----------------
350+
4
351+
(1 row)
352+
353+
create function tcl_cdomain_arg(d_dta1) returns int as '
354+
return $1(ref1)
355+
' language pltcl;
356+
select tcl_cdomain_arg(row('tkey', 42, 'ref2'));
357+
tcl_cdomain_arg
358+
-----------------
359+
42
360+
(1 row)
361+
362+
select tcl_cdomain_arg(row('tkey', 42, 'ref2')::T_dta1);
363+
tcl_cdomain_arg
364+
-----------------
365+
42
366+
(1 row)
367+
368+
select tcl_cdomain_arg(row('tkey', -1, 'ref2')); -- fail
369+
ERROR: value for domain d_dta1 violates check constraint "d_dta1_check"
330370
-- Test argisnull primitive
331371
select tcl_argisnull('foo');
332372
tcl_argisnull
@@ -438,6 +478,60 @@ return_next [list a 1 b 2 cow 3]
438478
$$ language pltcl;
439479
select bad_field_srf();
440480
ERROR: column name/value list contains nonexistent column name "cow"
481+
-- test composite and domain-over-composite results
482+
create function tcl_composite_result(int) returns T_dta1 as $$
483+
return [list tkey tkey1 ref1 $1 ref2 ref22]
484+
$$ language pltcl;
485+
select tcl_composite_result(1001);
486+
tcl_composite_result
487+
--------------------------------------------
488+
("tkey1 ",1001,"ref22 ")
489+
(1 row)
490+
491+
select * from tcl_composite_result(1002);
492+
tkey | ref1 | ref2
493+
------------+------+----------------------
494+
tkey1 | 1002 | ref22
495+
(1 row)
496+
497+
create function tcl_dcomposite_result(int) returns d_dta1 as $$
498+
return [list tkey tkey2 ref1 $1 ref2 ref42]
499+
$$ language pltcl;
500+
select tcl_dcomposite_result(1001);
501+
tcl_dcomposite_result
502+
--------------------------------------------
503+
("tkey2 ",1001,"ref42 ")
504+
(1 row)
505+
506+
select * from tcl_dcomposite_result(1002);
507+
tkey | ref1 | ref2
508+
------------+------+----------------------
509+
tkey2 | 1002 | ref42
510+
(1 row)
511+
512+
select * from tcl_dcomposite_result(-1); -- fail
513+
ERROR: value for domain d_dta1 violates check constraint "d_dta1_check"
514+
create function tcl_record_result(int) returns record as $$
515+
return [list q1 sometext q2 $1 q3 moretext]
516+
$$ language pltcl;
517+
select tcl_record_result(42); -- fail
518+
ERROR: function returning record called in context that cannot accept type record
519+
select * from tcl_record_result(42); -- fail
520+
ERROR: a column definition list is required for functions returning "record" at character 15
521+
select * from tcl_record_result(42) as (q1 text, q2 int, q3 text);
522+
q1 | q2 | q3
523+
----------+----+----------
524+
sometext | 42 | moretext
525+
(1 row)
526+
527+
select * from tcl_record_result(42) as (q1 text, q2 int, q3 text, q4 int);
528+
q1 | q2 | q3 | q4
529+
----------+----+----------+----
530+
sometext | 42 | moretext |
531+
(1 row)
532+
533+
select * from tcl_record_result(42) as (q1 text, q2 int, q4 int); -- fail
534+
ERROR: column name/value list contains nonexistent column name "q3"
441535
-- test quote
442536
select tcl_eval('quote foo bar');
443537
ERROR: wrong # args: should be "quote string"

src/pl/tcl/pltcl.c

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,13 @@ typedef struct pltcl_proc_desc
143143
bool fn_readonly; /* is function readonly? */
144144
bool lanpltrusted; /* is it pltcl (vs. pltclu)? */
145145
pltcl_interp_desc *interp_desc; /* interpreter to use */
146+
Oid result_typid; /* OID of fn's result type */
146147
FmgrInfo result_in_func; /* input function for fn's result type */
147148
Oid result_typioparam; /* param to pass to same */
148149
bool fn_retisset; /* true if function returns a set */
149150
bool fn_retistuple; /* true if function returns composite */
151+
bool fn_retisdomain; /* true if function returns domain */
152+
void *domain_info; /* opaque cache for domain checks */
150153
int nargs; /* number of arguments */
151154
/* these arrays have nargs entries: */
152155
FmgrInfo *arg_out_func; /* output fns for arg types */
@@ -988,11 +991,26 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
988991
* result type is a named composite type, so it's not exactly trivial.
989992
* Maybe worth improving someday.
990993
*/
991-
if (get_call_result_type(fcinfo, NULL, &td) != TYPEFUNC_COMPOSITE)
992-
ereport(ERROR,
993-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
994-
errmsg("function returning record called in context "
995-
"that cannot accept type record")));
994+
switch (get_call_result_type(fcinfo, NULL, &td))
995+
{
996+
case TYPEFUNC_COMPOSITE:
997+
/* success */
998+
break;
999+
case TYPEFUNC_COMPOSITE_DOMAIN:
1000+
Assert(prodesc->fn_retisdomain);
1001+
break;
1002+
case TYPEFUNC_RECORD:
1003+
/* failed to determine actual type of RECORD */
1004+
ereport(ERROR,
1005+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1006+
errmsg("function returning record called in context "
1007+
"that cannot accept type record")));
1008+
break;
1009+
default:
1010+
/* result type isn't composite? */
1011+
elog(ERROR, "return type must be a row type");
1012+
break;
1013+
}
9961014

9971015
Assert(!call_state->ret_tupdesc);
9981016
Assert(!call_state->attinmeta);
@@ -1490,40 +1508,41 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
14901508
************************************************************/
14911509
if (!is_trigger && !is_event_trigger)
14921510
{
1493-
typeTup =
1494-
SearchSysCache1(TYPEOID,
1495-
ObjectIdGetDatum(procStruct->prorettype));
1511+
Oid rettype = procStruct->prorettype;
1512+
1513+
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
14961514
if (!HeapTupleIsValid(typeTup))
1497-
elog(ERROR, "cache lookup failed for type %u",
1498-
procStruct->prorettype);
1515+
elog(ERROR, "cache lookup failed for type %u", rettype);
14991516
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
15001517

15011518
/* Disallow pseudotype result, except VOID and RECORD */
15021519
if (typeStruct->typtype == TYPTYPE_PSEUDO)
15031520
{
1504-
if (procStruct->prorettype == VOIDOID ||
1505-
procStruct->prorettype == RECORDOID)
1521+
if (rettype == VOIDOID ||
1522+
rettype == RECORDOID)
15061523
/* okay */ ;
1507-
else if (procStruct->prorettype == TRIGGEROID ||
1508-
procStruct->prorettype == EVTTRIGGEROID)
1524+
else if (rettype == TRIGGEROID ||
1525+
rettype == EVTTRIGGEROID)
15091526
ereport(ERROR,
15101527
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15111528
errmsg("trigger functions can only be called as triggers")));
15121529
else
15131530
ereport(ERROR,
15141531
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15151532
errmsg("PL/Tcl functions cannot return type %s",
1516-
format_type_be(procStruct->prorettype))));
1533+
format_type_be(rettype))));
15171534
}
15181535

1536+
prodesc->result_typid = rettype;
15191537
fmgr_info_cxt(typeStruct->typinput,
15201538
&(prodesc->result_in_func),
15211539
proc_cxt);
15221540
prodesc->result_typioparam = getTypeIOParam(typeTup);
15231541

15241542
prodesc->fn_retisset = procStruct->proretset;
1525-
prodesc->fn_retistuple = (procStruct->prorettype == RECORDOID ||
1526-
typeStruct->typtype == TYPTYPE_COMPOSITE);
1543+
prodesc->fn_retistuple = type_is_rowtype(rettype);
1544+
prodesc->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
1545+
prodesc->domain_info = NULL;
15271546

15281547
ReleaseSysCache(typeTup);
15291548
}
@@ -1537,21 +1556,22 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
15371556
proc_internal_args[0] = '\0';
15381557
for (i = 0; i < prodesc->nargs; i++)
15391558
{
1540-
typeTup = SearchSysCache1(TYPEOID,
1541-
ObjectIdGetDatum(procStruct->proargtypes.values[i]));
1559+
Oid argtype = procStruct->proargtypes.values[i];
1560+
1561+
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
15421562
if (!HeapTupleIsValid(typeTup))
1543-
elog(ERROR, "cache lookup failed for type %u",
1544-
procStruct->proargtypes.values[i]);
1563+
elog(ERROR, "cache lookup failed for type %u", argtype);
15451564
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
15461565

1547-
/* Disallow pseudotype argument */
1548-
if (typeStruct->typtype == TYPTYPE_PSEUDO)
1566+
/* Disallow pseudotype argument, except RECORD */
1567+
if (typeStruct->typtype == TYPTYPE_PSEUDO &&
1568+
argtype != RECORDOID)
15491569
ereport(ERROR,
15501570
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15511571
errmsg("PL/Tcl functions cannot accept type %s",
1552-
format_type_be(procStruct->proargtypes.values[i]))));
1572+
format_type_be(argtype))));
15531573

1554-
if (typeStruct->typtype == TYPTYPE_COMPOSITE)
1574+
if (type_is_rowtype(argtype))
15551575
{
15561576
prodesc->arg_is_rowtype[i] = true;
15571577
snprintf(buf, sizeof(buf), "__PLTcl_Tup_%d", i + 1);
@@ -3075,6 +3095,7 @@ static HeapTuple
30753095
pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
30763096
pltcl_call_state *call_state)
30773097
{
3098+
HeapTuple tuple;
30783099
TupleDesc tupdesc;
30793100
AttInMetadata *attinmeta;
30803101
char **values;
@@ -3133,7 +3154,16 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
31333154
values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1]));
31343155
}
31353156

3136-
return BuildTupleFromCStrings(attinmeta, values);
3157+
tuple = BuildTupleFromCStrings(attinmeta, values);
3158+
3159+
/* if result type is domain-over-composite, check domain constraints */
3160+
if (call_state->prodesc->fn_retisdomain)
3161+
domain_check(HeapTupleGetDatum(tuple), false,
3162+
call_state->prodesc->result_typid,
3163+
&call_state->prodesc->domain_info,
3164+
call_state->prodesc->fn_cxt);
3165+
3166+
return tuple;
31373167
}
31383168

31393169
/**********************************************************************

src/pl/tcl/sql/pltcl_queries.sql

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,26 @@ truncate trigger_test;
8989
select tcl_composite_arg_ref1(row('tkey', 42, 'ref2'));
9090
select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
9191

92+
-- More tests for composite argument/result types
93+
94+
create domain d_dta1 as T_dta1 check ((value).ref1 > 0);
95+
96+
create function tcl_record_arg(record, fldname text) returns int as '
97+
return $1($2)
98+
' language pltcl;
99+
100+
select tcl_record_arg(row('tkey', 42, 'ref2')::T_dta1, 'ref1');
101+
select tcl_record_arg(row('tkey', 42, 'ref2')::d_dta1, 'ref1');
102+
select tcl_record_arg(row(2,4), 'f2');
103+
104+
create function tcl_cdomain_arg(d_dta1) returns int as '
105+
return $1(ref1)
106+
' language pltcl;
107+
108+
select tcl_cdomain_arg(row('tkey', 42, 'ref2'));
109+
select tcl_cdomain_arg(row('tkey', 42, 'ref2')::T_dta1);
110+
select tcl_cdomain_arg(row('tkey', -1, 'ref2')); -- fail
111+
92112
-- Test argisnull primitive
93113
select tcl_argisnull('foo');
94114
select tcl_argisnull('');
@@ -136,6 +156,29 @@ return_next [list a 1 b 2 cow 3]
136156
$$ language pltcl;
137157
select bad_field_srf();
138158

159+
-- test composite and domain-over-composite results
160+
create function tcl_composite_result(int) returns T_dta1 as $$
161+
return [list tkey tkey1 ref1 $1 ref2 ref22]
162+
$$ language pltcl;
163+
select tcl_composite_result(1001);
164+
select * from tcl_composite_result(1002);
165+
166+
create function tcl_dcomposite_result(int) returns d_dta1 as $$
167+
return [list tkey tkey2 ref1 $1 ref2 ref42]
168+
$$ language pltcl;
169+
select tcl_dcomposite_result(1001);
170+
select * from tcl_dcomposite_result(1002);
171+
select * from tcl_dcomposite_result(-1); -- fail
172+
173+
create function tcl_record_result(int) returns record as $$
174+
return [list q1 sometext q2 $1 q3 moretext]
175+
$$ language pltcl;
176+
select tcl_record_result(42); -- fail
177+
select * from tcl_record_result(42); -- fail
178+
select * from tcl_record_result(42) as (q1 text, q2 int, q3 text);
179+
select * from tcl_record_result(42) as (q1 text, q2 int, q3 text, q4 int);
180+
select * from tcl_record_result(42) as (q1 text, q2 int, q4 int); -- fail
181+
139182
-- test quote
140183
select tcl_eval('quote foo bar');
141184
select tcl_eval('quote [format %c 39]');

0 commit comments

Comments
 (0)