Thread: 2 line patch to allow plpythonu functions to return void ...

2 line patch to allow plpythonu functions to return void ...

From
James Robinson
Date:
Shamelessly cloned from the parallel code in pltcl, an exception for
void in denying pseudotypes being returned. Pl/tcl didn't reference
VOIDOID anywhere else, so ... .

Allowed following trivial test function to succeed:

create or replace function set_gd(int) returns void as
$$
    GD['global_count'] =  args[0]
$$  language plpythonu volatile;





----
James Robinson
Socialserve.com


Attachment

Re: 2 line patch to allow plpythonu functions to return void ...

From
Tom Lane
Date:
James Robinson <jlrobins@socialserve.com> writes:
> Shamelessly cloned from the parallel code in pltcl, an exception for
> void in denying pseudotypes being returned. Pl/tcl didn't reference
> VOIDOID anywhere else, so ... .

This sort of thing normally requires more thought than just removing
the safety check.  What happens when the python code does/doesn't return
a value, in both cases (declared return type void or not)?

            regards, tom lane

Re: 2 line patch to allow plpythonu functions to return void ...

From
"Harald Armin Massa"
Date:
Tom,

This sort of thing normally requires more thought than just removing
the safety check.  What happens when the python code does/doesn't return
a value, in both cases (declared return type void or not)?

python functions are specified to return "None", if no return is given. I recommend to also see a plpython function as a Python function, and return None if no return is specified.



--
GHUM Harald Massa
persuadere et programmare
Harald Armin Massa
Reinsburgstraße 202b
70197 Stuttgart
0173/9409607
-
When I visit a mosque, I show my respect by taking off my shoes. I follow the customs, just as I do in a church, synagogue or other holy place. But if a believer demands that I, as a nonbeliever, observe his taboos in the public domain, he is not asking for my respect, but for my submission. And that is incompatible with a secular democracy.

Re: 2 line patch to allow plpythonu functions to return void ...

From
James Robinson
Date:
On Feb 25, 2006, at 12:10 PM, Tom Lane wrote:

> James Robinson <jlrobins@socialserve.com> writes:
>> Shamelessly cloned from the parallel code in pltcl, an exception for
>> void in denying pseudotypes being returned. Pl/tcl didn't reference
>> VOIDOID anywhere else, so ... .
>
> This sort of thing normally requires more thought than just removing
> the safety check.  What happens when the python code does/doesn't
> return
> a value, in both cases (declared return type void or not)?
>
>             regards, tom lane

Yes of course.

Here's some permutations of declared void functions explictly
returning a value or not, with the closest thing to void in Python
being None [ which is currently mapped to SQL NULL ] ...

create or replace function void_ret_notvoid() returns void as
$$
    return 12
$$  language plpythonu;

-- return value '' comes decorated with oid 2278, which seems to be
VOIDOID. The 12 integer gets discarded. Ugly, yes.

create or replace function void_ret_none() returns void as
$$
    return None
$$  language plpythonu;

-- Once again, returned oid to client is 2278, and likewise for the
subsequent one....

select void_ret_none() is null;

create or replace function void_ret_falloff_none() returns void as
$$
    x = 12 + 4
$$  language plpythonu;


-- This one returns oid 23, with value NULL.

create or replace function notvoid_ret_none() returns int as
$$
    return None
$$  language plpythonu;


Now, the python language semantics are that if a function does not
explictly return a value, None is implictly returned:

jlrobins:~ jlrobins$ python
Python 2.4.1 (#2, Mar 31 2005, 00:05:10)
[GCC 3.3 20030304 (Apple Computer, Inc. build 1666)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
 >>> def falloff():
...     x = 12
...
 >>> val = falloff()
 >>> val is None
True

So there's a bit of a mismatch between Python and SQL semantics since
Python's None is already being used to represent NULL [ of whatever
datatype the pl function was described to return at the SQL level ]
in plpython.

The ugliest case above is certainly the first one -- function
declared to be void explicitly returning a not-None value. That
should probably cause an SQL-level error. Can't really test it a
function compile time, since Python variables are not typed, only
what they reference are.

The intent was to have to keep from having to declare a bogus return
type for a a procedure that returns no meaningful results at all --
such as one which does nothing but inserts or updates or what have you.

I suspect that all of the above functions returned VOIDOID decorating
the return result due to machinery higher-up than plpython -- the
postgres typing system itself, so those cases are probably silly
examples, other than showing that it doesn't immediately crash. Which
you probably would rather be shown a much higher confidence proof
that the system is still correct aside from not immediately going
down in flames.

I'll go back to lurking and reading -- is the plpgsql source the best
model for reading up on procedure language implementation? Thanks.

----
James Robinson
Socialserve.com


Re: 2 line patch to allow plpythonu functions to return

From
Neil Conway
Date:
Tom Lane wrote:
> This sort of thing normally requires more thought than just removing
> the safety check.  What happens when the python code does/doesn't return
> a value, in both cases (declared return type void or not)?

Attached is a more complete patch:

- if the function is declared to return void, we only accept "None" as
the return value from Python. Returning anything else produces an error.

- if the function is declared to return void and Python returns "None",
we return this to PG as a void datum (*not* NULL)

- if the function is not declared to return void and Python returns
"None", we return this to PG as a NULL datum

One minor inconsistency is that PL/PgSQL (for example) actually
disallows "RETURN expr;" in void-returning functions: only "RETURN;" can
be used. In PL/Python we don't place any restrictions on the syntax of
the function: "return expr" is allowed in void-returning functions so
long as `expr' evaluates to None. I don't think this is a major problem,
though.

The error message for the first case isn't quite right, and I need to
update the "expected" regression tests and possibly the documentation.
But otherwise I'll apply this patch to HEAD tomorrow, barring any
objections.

-Neil
Index: src/pl/plpython/plpython.c
===================================================================
RCS file: /Users/neilc/postgres/cvs_root/pgsql/src/pl/plpython/plpython.c,v
retrieving revision 1.71
diff -c -r1.71 plpython.c
*** src/pl/plpython/plpython.c    20 Feb 2006 20:10:37 -0000    1.71
--- src/pl/plpython/plpython.c    26 Feb 2006 23:22:27 -0000
***************
*** 91,97 ****
   */
  typedef struct PLyObToDatum
  {
!     FmgrInfo    typfunc;
      Oid            typioparam;
      bool        typbyval;
  }    PLyObToDatum;
--- 91,98 ----
   */
  typedef struct PLyObToDatum
  {
!     FmgrInfo    typfunc;        /* The type's input function */
!     Oid            typoid;            /* The OID of the type */
      Oid            typioparam;
      bool        typbyval;
  }    PLyObToDatum;
***************
*** 138,144 ****
      int            nargs;
      PyObject   *code;            /* compiled procedure code */
      PyObject   *statics;        /* data saved across calls, local scope */
!     PyObject   *globals;        /* data saved across calls, global score */
      PyObject   *me;                /* PyCObject containing pointer to this
                                   * PLyProcedure */
  }    PLyProcedure;
--- 139,145 ----
      int            nargs;
      PyObject   *code;            /* compiled procedure code */
      PyObject   *statics;        /* data saved across calls, local scope */
!     PyObject   *globals;        /* data saved across calls, global scope */
      PyObject   *me;                /* PyCObject containing pointer to this
                                   * PLyProcedure */
  }    PLyProcedure;
***************
*** 757,765 ****
              elog(ERROR, "SPI_finish failed");

          /*
!          * convert the python PyObject to a postgresql Datum
           */
!         if (plrv == Py_None)
          {
              fcinfo->isnull = true;
              rv = PointerGetDatum(NULL);
--- 758,781 ----
              elog(ERROR, "SPI_finish failed");

          /*
!          * If the function is declared to return void, the Python
!          * return value must be None. For void-returning functions, we
!          * also treat a None return value as a special "void datum"
!          * rather than NULL (as is the case for non-void-returning
!          * functions).
           */
!         if (proc->result.out.d.typoid == VOIDOID)
!         {
!             if (plrv != Py_None)
!                 ereport(ERROR,
!                         (errcode(ERRCODE_DATA_EXCEPTION),
!                          errmsg("unexpected return value from plpython procedure"),
!                          errdetail("function returns void")));
!
!             fcinfo->isnull = false;
!             rv = (Datum) 0;
!         }
!         else if (plrv == Py_None)
          {
              fcinfo->isnull = true;
              rv = PointerGetDatum(NULL);
***************
*** 1031,1038 ****
                       procStruct->prorettype);
              rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);

!             /* Disallow pseudotype result */
!             if (rvTypeStruct->typtype == 'p')
              {
                  if (procStruct->prorettype == TRIGGEROID)
                      ereport(ERROR,
--- 1047,1055 ----
                       procStruct->prorettype);
              rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);

!             /* Disallow pseudotype result, except for void */
!             if (rvTypeStruct->typtype == 'p' &&
!                 procStruct->prorettype != VOIDOID)
              {
                  if (procStruct->prorettype == TRIGGEROID)
                      ereport(ERROR,
***************
*** 1329,1334 ****
--- 1346,1352 ----
      Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);

      perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
+     arg->typoid = HeapTupleGetOid(typeTup);
      arg->typioparam = getTypeIOParam(typeTup);
      arg->typbyval = typeStruct->typbyval;
  }
Index: src/pl/plpython/sql/plpython_function.sql
===================================================================
RCS file: /Users/neilc/postgres/cvs_root/pgsql/src/pl/plpython/sql/plpython_function.sql,v
retrieving revision 1.3
diff -c -r1.3 plpython_function.sql
*** src/pl/plpython/sql/plpython_function.sql    10 Jul 2005 04:56:55 -0000    1.3
--- src/pl/plpython/sql/plpython_function.sql    26 Feb 2006 23:27:49 -0000
***************
*** 341,343 ****
--- 341,358 ----
  rv = plpy.execute(plan, u"\\x80", 1)
  return rv[0]["testvalue1"]
  ' LANGUAGE plpythonu;
+
+ -- Tests for functions that return void
+
+ CREATE FUNCTION test_void_func1() RETURNS void AS $$
+ x = 10
+ $$ LANGUAGE plpythonu;
+
+ -- illegal: can't return non-None value in void-returning func
+ CREATE FUNCTION test_void_func2() RETURNS void AS $$
+ return 10
+ $$ LANGUAGE plpythonu;
+
+ CREATE FUNCTION test_return_none() RETURNS int AS $$
+ None
+ $$ LANGUAGE plpythonu;
\ No newline at end of file
Index: src/pl/plpython/sql/plpython_test.sql
===================================================================
RCS file: /Users/neilc/postgres/cvs_root/pgsql/src/pl/plpython/sql/plpython_test.sql,v
retrieving revision 1.1
diff -c -r1.1 plpython_test.sql
*** src/pl/plpython/sql/plpython_test.sql    14 May 2005 17:55:21 -0000    1.1
--- src/pl/plpython/sql/plpython_test.sql    26 Feb 2006 23:29:38 -0000
***************
*** 68,70 ****
--- 68,75 ----
  SELECT newline_lf();
  SELECT newline_cr();
  SELECT newline_crlf();
+
+ -- Tests for functions returning void
+ SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
+ SELECT test_void_func2(); -- should fail
+ SELECT test_return_none(), test_return_none() IS NULL AS "is null";
\ No newline at end of file

Re: 2 line patch to allow plpythonu functions to return

From
Neil Conway
Date:
On Sun, 2006-02-26 at 18:40 -0500, Neil Conway wrote:
> Tom Lane wrote:
> > This sort of thing normally requires more thought than just removing
> > the safety check.  What happens when the python code does/doesn't return
> > a value, in both cases (declared return type void or not)?
>
> Attached is a more complete patch: [...]

Applied to HEAD. I'm still not quite satisfied with the error message:

  ereport(ERROR,
          (errcode(ERRCODE_DATATYPE_MISMATCH),
           errmsg("unexpected return value from plpython procedure"),
           errdetail("void-returning functions must return \"None\"")));

Suggestions for something better?

-Neil



Re: 2 line patch to allow plpythonu functions to return

From
Tom Lane
Date:
Neil Conway <neilc@samurai.com> writes:
> Applied to HEAD. I'm still not quite satisfied with the error message:

>   ereport(ERROR,
>           (errcode(ERRCODE_DATATYPE_MISMATCH),
>            errmsg("unexpected return value from plpython procedure"),
>            errdetail("void-returning functions must return \"None\"")));

> Suggestions for something better?

Yeah, "unexpected" doesn't seem the mot juste here.  Perhaps "invalid"
or "incorrect" (our message style guidelines seem to suggest "invalid"
as a good word in such cases).  Also, say "function" not "procedure"
because that's what it is in SQL terms.

Also, the errdetail doesn't follow the guideline that says detail
messages should be full sentences with proper capitalization and
punctuation.  Perhaps

    Functions returning type "void" must return "None".

            regards, tom lane

Re: 2 line patch to allow plpythonu functions to return

From
Neil Conway
Date:
On Tue, 2006-02-28 at 15:26 -0500, Tom Lane wrote:
> Yeah, "unexpected" doesn't seem the mot juste here. [...]

All good points -- thanks for the suggestions. I've applied the attached
patch to HEAD.

-Neil


Attachment