polymorphic arguments and return type for PL/pgSQL - Mailing list pgsql-patches

From Joe Conway
Subject polymorphic arguments and return type for PL/pgSQL
Date
Msg-id 3EFFD340.7000009@joeconway.com
Whole thread Raw
Responses Re: polymorphic arguments and return type for PL/pgSQL
List pgsql-patches
The attached patch enables PL/pgSQL functions (but not triggers) to
accept and return polymorphic types. It is careful to return false from
func_up_to_date() if any of the polymorphic types change from
call-to-call. It also falls back to the pg_proc declared types if the
caller didn't setup the FuncExpr node.

Here is an example that I can add to the plpgsql regression test if desired:

CREATE OR REPLACE FUNCTION tst(anyelement) returns anyarray as '
begin
  if $1 is of (int2, int4, int8, float4, float8, numeric) then
    return array[$1 * 2];
  elsif $1 is of (text) then
    return array[$1 || $1];
  else
    return array[$1];
  end if;
end;
' language 'plpgsql';

create table plpgsql(f1 int, f2 float8, f3 text, f4 oid);
insert into plpgsql values(1, 1.1, 'a', 1);
insert into plpgsql values(2, 2.2, 'b', 2);
regression=# SELECT tst(f1), tst(f2), tst(f3), tst(f4) from plpgsql;
  tst |  tst  | tst  | tst
-----+-------+------+-----
  {2} | {2.2} | {aa} | {1}
  {4} | {4.4} | {bb} | {2}
(2 rows)

If there are no objections, please apply.

Thanks,

Joe
Index: src/pl/plpgsql/src/pl_comp.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/pl_comp.c,v
retrieving revision 1.58
diff -c -r1.58 pl_comp.c
*** src/pl/plpgsql/src/pl_comp.c    5 May 2003 16:46:27 -0000    1.58
--- src/pl/plpgsql/src/pl_comp.c    30 Jun 2003 05:36:10 -0000
***************
*** 108,114 ****
   * ----------
   */
  PLpgSQL_function *
! plpgsql_compile(Oid fn_oid, int functype)
  {
      int            parse_rc;
      HeapTuple    procTup;
--- 108,116 ----
   * ----------
   */
  PLpgSQL_function *
! plpgsql_compile(Oid fn_oid,
!                 int functype,
!                 FunctionCallInfo fcinfo)
  {
      int            parse_rc;
      HeapTuple    procTup;
***************
*** 123,128 ****
--- 125,131 ----
      int            i;
      int            arg_varnos[FUNC_MAX_ARGS];
      ErrorContextCallback plerrcontext;
+     Oid            rettypeid;

      /*
       * Lookup the pg_proc tuple by Oid
***************
*** 185,223 ****
          case T_FUNCTION:

              /*
               * Normal function has a defined returntype
               */
!             function->fn_rettype = procStruct->prorettype;
              function->fn_retset = procStruct->proretset;

              /*
               * Lookup the functions return type
               */
              typeTup = SearchSysCache(TYPEOID,
!                                 ObjectIdGetDatum(procStruct->prorettype),
                                       0, 0, 0);
              if (!HeapTupleIsValid(typeTup))
                  elog(ERROR, "cache lookup for return type %u failed",
!                      procStruct->prorettype);
              typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

              /* Disallow pseudotype result, except VOID or RECORD */
              if (typeStruct->typtype == 'p')
              {
!                 if (procStruct->prorettype == VOIDOID ||
!                     procStruct->prorettype == RECORDOID)
                       /* okay */ ;
!                 else if (procStruct->prorettype == TRIGGEROID)
                      elog(ERROR, "plpgsql functions cannot return type %s"
                           "\n\texcept when used as triggers",
!                          format_type_be(procStruct->prorettype));
                  else
                      elog(ERROR, "plpgsql functions cannot return type %s",
!                          format_type_be(procStruct->prorettype));
              }

              if (typeStruct->typrelid != InvalidOid ||
!                 procStruct->prorettype == RECORDOID)
                  function->fn_retistuple = true;
              else
              {
--- 188,241 ----
          case T_FUNCTION:

              /*
+              * Check for a polymorphic returntype. If found, use the actual
+              * returntype from the caller's FuncExpr node, if we
+              * have one.
+              */
+             if (procStruct->prorettype == ANYARRAYOID ||
+                 procStruct->prorettype == ANYELEMENTOID)
+             {
+                 rettypeid = get_fn_expr_rettype(fcinfo);
+                 if (!OidIsValid(rettypeid))
+                     rettypeid = procStruct->prorettype;
+             }
+             else
+                 rettypeid = procStruct->prorettype;
+
+             /*
               * Normal function has a defined returntype
               */
!             function->fn_rettype = rettypeid;
              function->fn_retset = procStruct->proretset;

              /*
               * Lookup the functions return type
               */
              typeTup = SearchSysCache(TYPEOID,
!                                 ObjectIdGetDatum(rettypeid),
                                       0, 0, 0);
              if (!HeapTupleIsValid(typeTup))
                  elog(ERROR, "cache lookup for return type %u failed",
!                      rettypeid);
              typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

              /* Disallow pseudotype result, except VOID or RECORD */
              if (typeStruct->typtype == 'p')
              {
!                 if (rettypeid == VOIDOID ||
!                     rettypeid == RECORDOID)
                       /* okay */ ;
!                 else if (rettypeid == TRIGGEROID)
                      elog(ERROR, "plpgsql functions cannot return type %s"
                           "\n\texcept when used as triggers",
!                          format_type_be(rettypeid));
                  else
                      elog(ERROR, "plpgsql functions cannot return type %s",
!                          format_type_be(rettypeid));
              }

              if (typeStruct->typrelid != InvalidOid ||
!                 rettypeid == RECORDOID)
                  function->fn_retistuple = true;
              else
              {
***************
*** 234,257 ****
              for (i = 0; i < procStruct->pronargs; i++)
              {
                  char        buf[32];

                  snprintf(buf, sizeof(buf), "$%d", i + 1);        /* name for variable */

                  /*
                   * Get the parameters type
                   */
                  typeTup = SearchSysCache(TYPEOID,
!                             ObjectIdGetDatum(procStruct->proargtypes[i]),
                                           0, 0, 0);
                  if (!HeapTupleIsValid(typeTup))
                      elog(ERROR, "cache lookup for argument type %u failed",
!                          procStruct->proargtypes[i]);
                  typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

                  /* Disallow pseudotype argument */
                  if (typeStruct->typtype == 'p')
                      elog(ERROR, "plpgsql functions cannot take type %s",
!                          format_type_be(procStruct->proargtypes[i]));

                  if (typeStruct->typrelid != InvalidOid)
                  {
--- 252,291 ----
              for (i = 0; i < procStruct->pronargs; i++)
              {
                  char        buf[32];
+                 Oid            argtypeid = InvalidOid;

                  snprintf(buf, sizeof(buf), "$%d", i + 1);        /* name for variable */

                  /*
+                  * Check for polymorphic arguments. If found, use the actual
+                  * parameter type from the caller's FuncExpr node, if we
+                  * have one.
+                  */
+                 if (procStruct->proargtypes[i] == ANYARRAYOID ||
+                     procStruct->proargtypes[i] == ANYELEMENTOID)
+                 {
+                     argtypeid = get_fn_expr_argtype(fcinfo, i);
+                     if (!OidIsValid(argtypeid))
+                         argtypeid = procStruct->proargtypes[i];
+                 }
+                 else
+                     argtypeid = procStruct->proargtypes[i];
+
+                 /*
                   * Get the parameters type
                   */
                  typeTup = SearchSysCache(TYPEOID,
!                             ObjectIdGetDatum(argtypeid),
                                           0, 0, 0);
                  if (!HeapTupleIsValid(typeTup))
                      elog(ERROR, "cache lookup for argument type %u failed",
!                          argtypeid);
                  typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

                  /* Disallow pseudotype argument */
                  if (typeStruct->typtype == 'p')
                      elog(ERROR, "plpgsql functions cannot take type %s",
!                          format_type_be(argtypeid));

                  if (typeStruct->typrelid != InvalidOid)
                  {
Index: src/pl/plpgsql/src/pl_handler.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/pl_handler.c,v
retrieving revision 1.12
diff -c -r1.12 pl_handler.c
*** src/pl/plpgsql/src/pl_handler.c    30 Aug 2002 00:28:41 -0000    1.12
--- src/pl/plpgsql/src/pl_handler.c    30 Jun 2003 05:17:03 -0000
***************
*** 51,57 ****
  static PLpgSQL_function *compiled_functions = NULL;


! static bool func_up_to_date(PLpgSQL_function * func);


  /* ----------
--- 51,57 ----
  static PLpgSQL_function *compiled_functions = NULL;


! static bool func_up_to_date(PLpgSQL_function * func, FunctionCallInfo fcinfo);


  /* ----------
***************
*** 90,96 ****
          /*
           * But is the function still up to date?
           */
!         if (!func_up_to_date(func))
              func = NULL;
      }

--- 90,96 ----
          /*
           * But is the function still up to date?
           */
!         if (!func_up_to_date(func, fcinfo))
              func = NULL;
      }

***************
*** 101,107 ****
           */
          for (func = compiled_functions; func != NULL; func = func->next)
          {
!             if (funcOid == func->fn_oid && func_up_to_date(func))
                  break;
          }

--- 101,107 ----
           */
          for (func = compiled_functions; func != NULL; func = func->next)
          {
!             if (funcOid == func->fn_oid && func_up_to_date(func, fcinfo))
                  break;
          }

***************
*** 111,117 ****
          if (func == NULL)
          {
              func = plpgsql_compile(funcOid,
!                                    isTrigger ? T_TRIGGER : T_FUNCTION);
              func->next = compiled_functions;
              compiled_functions = func;
          }
--- 111,118 ----
          if (func == NULL)
          {
              func = plpgsql_compile(funcOid,
!                                    isTrigger ? T_TRIGGER : T_FUNCTION,
!                                    fcinfo);
              func->next = compiled_functions;
              compiled_functions = func;
          }
***************
*** 146,154 ****
   * Check to see if a compiled function is still up-to-date.  This
   * is needed because CREATE OR REPLACE FUNCTION can modify the
   * function's pg_proc entry without changing its OID.
   */
  static bool
! func_up_to_date(PLpgSQL_function * func)
  {
      HeapTuple    procTup;
      bool        result;
--- 147,158 ----
   * Check to see if a compiled function is still up-to-date.  This
   * is needed because CREATE OR REPLACE FUNCTION can modify the
   * function's pg_proc entry without changing its OID.
+  * A compiled function can also be out-of-date if it accepts or
+  * returns polymorphic types, because these may change beneath
+  * us.
   */
  static bool
! func_up_to_date(PLpgSQL_function * func, FunctionCallInfo fcinfo)
  {
      HeapTuple    procTup;
      bool        result;
***************
*** 162,167 ****
--- 166,203 ----

      result = (func->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
                func->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
+
+     /* no sense in checking if result is already false */
+     if (result)
+     {
+         Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+         /* check for change in return type if applicable */
+         if (procStruct->prorettype == ANYARRAYOID ||
+             procStruct->prorettype == ANYELEMENTOID)
+             result = (func->fn_rettype == get_fn_expr_rettype(fcinfo));
+
+         /* check for change in any arguments' type */
+         if (result)
+         {
+             int    i;
+
+             for (i = 0; i < procStruct->pronargs; i++)
+             {
+                 if (procStruct->proargtypes[i] == ANYARRAYOID ||
+                     procStruct->proargtypes[i] == ANYELEMENTOID)
+                 {
+                     int                j = func->fn_argvarnos[i];
+                     PLpgSQL_var       *var = (PLpgSQL_var *) func->datums[j];
+                     Oid                vartypeid = var->datatype->typoid;
+
+                     result = (vartypeid == get_fn_expr_argtype(fcinfo, i));
+                     if (!result)
+                         break;
+                 }
+             }
+         }
+     }

      ReleaseSysCache(procTup);

Index: src/pl/plpgsql/src/plpgsql.h
===================================================================
RCS file: /opt/src/cvs/pgsql-server/src/pl/plpgsql/src/plpgsql.h,v
retrieving revision 1.36
diff -c -r1.36 plpgsql.h
*** src/pl/plpgsql/src/plpgsql.h    5 May 2003 16:46:28 -0000    1.36
--- src/pl/plpgsql/src/plpgsql.h    30 Jun 2003 04:10:57 -0000
***************
*** 588,594 ****
   * Functions in pl_comp.c
   * ----------
   */
! extern PLpgSQL_function *plpgsql_compile(Oid fn_oid, int functype);
  extern int    plpgsql_parse_word(char *word);
  extern int    plpgsql_parse_dblword(char *word);
  extern int    plpgsql_parse_tripword(char *word);
--- 588,596 ----
   * Functions in pl_comp.c
   * ----------
   */
! extern PLpgSQL_function *plpgsql_compile(Oid fn_oid,
!                                          int functype,
!                                          FunctionCallInfo fcinfo);
  extern int    plpgsql_parse_word(char *word);
  extern int    plpgsql_parse_dblword(char *word);
  extern int    plpgsql_parse_tripword(char *word);

pgsql-patches by date:

Previous
From: Srikanth M
Date:
Subject: Urgent : Regarding Code Submission
Next
From: sumit
Date:
Subject: Patch for adding DATACUBE operator