Support domains over composite types in PL/Tcl.
authorTom Lane
Thu, 26 Oct 2017 20:00:17 +0000 (16:00 -0400)
committerTom Lane
Thu, 26 Oct 2017 20:00:17 +0000 (16:00 -0400)
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

src/pl/tcl/expected/pltcl_queries.out
src/pl/tcl/pltcl.c
src/pl/tcl/sql/pltcl_queries.sql

index 5f50f4688787a5ff055f0025e4e43acfb8dcc3ed..736671cc1bc97ddf92080df4f26264cb6dd3791d 100644 (file)
@@ -327,6 +327,46 @@ select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
  ref2                
 (1 row)
 
+-- More tests for composite argument/result types
+create domain d_dta1 as T_dta1 check ((value).ref1 > 0);
+create function tcl_record_arg(record, fldname text) returns int as '
+    return $1($2)
+' language pltcl;
+select tcl_record_arg(row('tkey', 42, 'ref2')::T_dta1, 'ref1');
+ tcl_record_arg 
+----------------
+             42
+(1 row)
+
+select tcl_record_arg(row('tkey', 42, 'ref2')::d_dta1, 'ref1');
+ tcl_record_arg 
+----------------
+             42
+(1 row)
+
+select tcl_record_arg(row(2,4), 'f2');
+ tcl_record_arg 
+----------------
+              4
+(1 row)
+
+create function tcl_cdomain_arg(d_dta1) returns int as '
+    return $1(ref1)
+' language pltcl;
+select tcl_cdomain_arg(row('tkey', 42, 'ref2'));
+ tcl_cdomain_arg 
+-----------------
+              42
+(1 row)
+
+select tcl_cdomain_arg(row('tkey', 42, 'ref2')::T_dta1);
+ tcl_cdomain_arg 
+-----------------
+              42
+(1 row)
+
+select tcl_cdomain_arg(row('tkey', -1, 'ref2'));  -- fail
+ERROR:  value for domain d_dta1 violates check constraint "d_dta1_check"
 -- Test argisnull primitive
 select tcl_argisnull('foo');
  tcl_argisnull 
@@ -438,6 +478,60 @@ return_next [list a 1 b 2 cow 3]
 $$ language pltcl;
 select bad_field_srf();
 ERROR:  column name/value list contains nonexistent column name "cow"
+-- test composite and domain-over-composite results
+create function tcl_composite_result(int) returns T_dta1 as $$
+return [list tkey tkey1 ref1 $1 ref2 ref22]
+$$ language pltcl;
+select tcl_composite_result(1001);
+            tcl_composite_result            
+--------------------------------------------
+ ("tkey1     ",1001,"ref22               ")
+(1 row)
+
+select * from tcl_composite_result(1002);
+    tkey    | ref1 |         ref2         
+------------+------+----------------------
+ tkey1      | 1002 | ref22               
+(1 row)
+
+create function tcl_dcomposite_result(int) returns d_dta1 as $$
+return [list tkey tkey2 ref1 $1 ref2 ref42]
+$$ language pltcl;
+select tcl_dcomposite_result(1001);
+           tcl_dcomposite_result            
+--------------------------------------------
+ ("tkey2     ",1001,"ref42               ")
+(1 row)
+
+select * from tcl_dcomposite_result(1002);
+    tkey    | ref1 |         ref2         
+------------+------+----------------------
+ tkey2      | 1002 | ref42               
+(1 row)
+
+select * from tcl_dcomposite_result(-1);  -- fail
+ERROR:  value for domain d_dta1 violates check constraint "d_dta1_check"
+create function tcl_record_result(int) returns record as $$
+return [list q1 sometext q2 $1 q3 moretext]
+$$ language pltcl;
+select tcl_record_result(42);  -- fail
+ERROR:  function returning record called in context that cannot accept type record
+select * from tcl_record_result(42);  -- fail
+ERROR:  a column definition list is required for functions returning "record" at character 15
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text);
+    q1    | q2 |    q3    
+----------+----+----------
+ sometext | 42 | moretext
+(1 row)
+
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text, q4 int);
+    q1    | q2 |    q3    | q4 
+----------+----+----------+----
+ sometext | 42 | moretext |   
+(1 row)
+
+select * from tcl_record_result(42) as (q1 text, q2 int, q4 int);  -- fail
+ERROR:  column name/value list contains nonexistent column name "q3"
 -- test quote
 select tcl_eval('quote foo bar');
 ERROR:  wrong # args: should be "quote string"
index 09f87ec791638bcc2236fa8f182c978957e52bb0..6d97ddc99bd5f3277b5fd44af9b3e914cdf4fb47 100644 (file)
@@ -143,10 +143,13 @@ typedef struct pltcl_proc_desc
    bool        fn_readonly;    /* is function readonly? */
    bool        lanpltrusted;   /* is it pltcl (vs. pltclu)? */
    pltcl_interp_desc *interp_desc; /* interpreter to use */
+   Oid         result_typid;   /* OID of fn's result type */
    FmgrInfo    result_in_func; /* input function for fn's result type */
    Oid         result_typioparam;  /* param to pass to same */
    bool        fn_retisset;    /* true if function returns a set */
    bool        fn_retistuple;  /* true if function returns composite */
+   bool        fn_retisdomain; /* true if function returns domain */
+   void       *domain_info;    /* opaque cache for domain checks */
    int         nargs;          /* number of arguments */
    /* these arrays have nargs entries: */
    FmgrInfo   *arg_out_func;   /* output fns for arg types */
@@ -988,11 +991,26 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
         * result type is a named composite type, so it's not exactly trivial.
         * Maybe worth improving someday.
         */
-       if (get_call_result_type(fcinfo, NULL, &td) != TYPEFUNC_COMPOSITE)
-           ereport(ERROR,
-                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                    errmsg("function returning record called in context "
-                           "that cannot accept type record")));
+       switch (get_call_result_type(fcinfo, NULL, &td))
+       {
+           case TYPEFUNC_COMPOSITE:
+               /* success */
+               break;
+           case TYPEFUNC_COMPOSITE_DOMAIN:
+               Assert(prodesc->fn_retisdomain);
+               break;
+           case TYPEFUNC_RECORD:
+               /* failed to determine actual type of RECORD */
+               ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("function returning record called in context "
+                               "that cannot accept type record")));
+               break;
+           default:
+               /* result type isn't composite? */
+               elog(ERROR, "return type must be a row type");
+               break;
+       }
 
        Assert(!call_state->ret_tupdesc);
        Assert(!call_state->attinmeta);
@@ -1490,22 +1508,21 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
         ************************************************************/
        if (!is_trigger && !is_event_trigger)
        {
-           typeTup =
-               SearchSysCache1(TYPEOID,
-                               ObjectIdGetDatum(procStruct->prorettype));
+           Oid         rettype = procStruct->prorettype;
+
+           typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
            if (!HeapTupleIsValid(typeTup))
-               elog(ERROR, "cache lookup failed for type %u",
-                    procStruct->prorettype);
+               elog(ERROR, "cache lookup failed for type %u", rettype);
            typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 
            /* Disallow pseudotype result, except VOID and RECORD */
            if (typeStruct->typtype == TYPTYPE_PSEUDO)
            {
-               if (procStruct->prorettype == VOIDOID ||
-                   procStruct->prorettype == RECORDOID)
+               if (rettype == VOIDOID ||
+                   rettype == RECORDOID)
                     /* okay */ ;
-               else if (procStruct->prorettype == TRIGGEROID ||
-                        procStruct->prorettype == EVTTRIGGEROID)
+               else if (rettype == TRIGGEROID ||
+                        rettype == EVTTRIGGEROID)
                    ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                             errmsg("trigger functions can only be called as triggers")));
@@ -1513,17 +1530,19 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
                    ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                             errmsg("PL/Tcl functions cannot return type %s",
-                                   format_type_be(procStruct->prorettype))));
+                                   format_type_be(rettype))));
            }
 
+           prodesc->result_typid = rettype;
            fmgr_info_cxt(typeStruct->typinput,
                          &(prodesc->result_in_func),
                          proc_cxt);
            prodesc->result_typioparam = getTypeIOParam(typeTup);
 
            prodesc->fn_retisset = procStruct->proretset;
-           prodesc->fn_retistuple = (procStruct->prorettype == RECORDOID ||
-                                     typeStruct->typtype == TYPTYPE_COMPOSITE);
+           prodesc->fn_retistuple = type_is_rowtype(rettype);
+           prodesc->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
+           prodesc->domain_info = NULL;
 
            ReleaseSysCache(typeTup);
        }
@@ -1537,21 +1556,22 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid,
            proc_internal_args[0] = '\0';
            for (i = 0; i < prodesc->nargs; i++)
            {
-               typeTup = SearchSysCache1(TYPEOID,
-                                         ObjectIdGetDatum(procStruct->proargtypes.values[i]));
+               Oid         argtype = procStruct->proargtypes.values[i];
+
+               typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
                if (!HeapTupleIsValid(typeTup))
-                   elog(ERROR, "cache lookup failed for type %u",
-                        procStruct->proargtypes.values[i]);
+                   elog(ERROR, "cache lookup failed for type %u", argtype);
                typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
 
-               /* Disallow pseudotype argument */
-               if (typeStruct->typtype == TYPTYPE_PSEUDO)
+               /* Disallow pseudotype argument, except RECORD */
+               if (typeStruct->typtype == TYPTYPE_PSEUDO &&
+                   argtype != RECORDOID)
                    ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                             errmsg("PL/Tcl functions cannot accept type %s",
-                                   format_type_be(procStruct->proargtypes.values[i]))));
+                                   format_type_be(argtype))));
 
-               if (typeStruct->typtype == TYPTYPE_COMPOSITE)
+               if (type_is_rowtype(argtype))
                {
                    prodesc->arg_is_rowtype[i] = true;
                    snprintf(buf, sizeof(buf), "__PLTcl_Tup_%d", i + 1);
@@ -3075,6 +3095,7 @@ static HeapTuple
 pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
                         pltcl_call_state *call_state)
 {
+   HeapTuple   tuple;
    TupleDesc   tupdesc;
    AttInMetadata *attinmeta;
    char      **values;
@@ -3133,7 +3154,16 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
        values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1]));
    }
 
-   return BuildTupleFromCStrings(attinmeta, values);
+   tuple = BuildTupleFromCStrings(attinmeta, values);
+
+   /* if result type is domain-over-composite, check domain constraints */
+   if (call_state->prodesc->fn_retisdomain)
+       domain_check(HeapTupleGetDatum(tuple), false,
+                    call_state->prodesc->result_typid,
+                    &call_state->prodesc->domain_info,
+                    call_state->prodesc->fn_cxt);
+
+   return tuple;
 }
 
 /**********************************************************************
index dabd8cd35f08b7224be738a80d8a0a015eb7592c..71c1238bd20c68e8340e9551501a7e05ed3b168b 100644 (file)
@@ -89,6 +89,26 @@ truncate trigger_test;
 select tcl_composite_arg_ref1(row('tkey', 42, 'ref2'));
 select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
 
+-- More tests for composite argument/result types
+
+create domain d_dta1 as T_dta1 check ((value).ref1 > 0);
+
+create function tcl_record_arg(record, fldname text) returns int as '
+    return $1($2)
+' language pltcl;
+
+select tcl_record_arg(row('tkey', 42, 'ref2')::T_dta1, 'ref1');
+select tcl_record_arg(row('tkey', 42, 'ref2')::d_dta1, 'ref1');
+select tcl_record_arg(row(2,4), 'f2');
+
+create function tcl_cdomain_arg(d_dta1) returns int as '
+    return $1(ref1)
+' language pltcl;
+
+select tcl_cdomain_arg(row('tkey', 42, 'ref2'));
+select tcl_cdomain_arg(row('tkey', 42, 'ref2')::T_dta1);
+select tcl_cdomain_arg(row('tkey', -1, 'ref2'));  -- fail
+
 -- Test argisnull primitive
 select tcl_argisnull('foo');
 select tcl_argisnull('');
@@ -136,6 +156,29 @@ return_next [list a 1 b 2 cow 3]
 $$ language pltcl;
 select bad_field_srf();
 
+-- test composite and domain-over-composite results
+create function tcl_composite_result(int) returns T_dta1 as $$
+return [list tkey tkey1 ref1 $1 ref2 ref22]
+$$ language pltcl;
+select tcl_composite_result(1001);
+select * from tcl_composite_result(1002);
+
+create function tcl_dcomposite_result(int) returns d_dta1 as $$
+return [list tkey tkey2 ref1 $1 ref2 ref42]
+$$ language pltcl;
+select tcl_dcomposite_result(1001);
+select * from tcl_dcomposite_result(1002);
+select * from tcl_dcomposite_result(-1);  -- fail
+
+create function tcl_record_result(int) returns record as $$
+return [list q1 sometext q2 $1 q3 moretext]
+$$ language pltcl;
+select tcl_record_result(42);  -- fail
+select * from tcl_record_result(42);  -- fail
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text);
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text, q4 int);
+select * from tcl_record_result(42) as (q1 text, q2 int, q4 int);  -- fail
+
 -- test quote
 select tcl_eval('quote foo bar');
 select tcl_eval('quote [format %c 39]');