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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy