mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-12 15:39:35 +08:00
Allow PL/python to return composite types and result sets
Sven Suursoho
This commit is contained in:
parent
b1620c538d
commit
819f22a302
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.30 2006/05/26 19:23:09 adunstan Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.31 2006/09/02 12:30:01 momjian Exp $ -->
|
||||||
|
|
||||||
<chapter id="plpython">
|
<chapter id="plpython">
|
||||||
<title>PL/Python - Python Procedural Language</title>
|
<title>PL/Python - Python Procedural Language</title>
|
||||||
@ -46,28 +46,211 @@
|
|||||||
<title>PL/Python Functions</title>
|
<title>PL/Python Functions</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Functions in PL/Python are declared via the usual <xref
|
Functions in PL/Python are declared via the standard <xref
|
||||||
linkend="sql-createfunction" endterm="sql-createfunction-title">
|
linkend="sql-createfunction" endterm="sql-createfunction-title">
|
||||||
syntax. For example:
|
syntax:
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE FUNCTION myfunc(text) RETURNS text
|
CREATE FUNCTION <replaceable>funcname</replaceable> (<replaceable>argument-list</replaceable>)
|
||||||
AS 'return args[0]'
|
RETURNS <replaceable>return-type</replaceable>
|
||||||
LANGUAGE plpythonu;
|
AS $$
|
||||||
|
# PL/Python function body
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The body of a function is simply a Python script. When the function
|
||||||
|
is called, all unnamed arguments are passed as elements to the array
|
||||||
|
<varname>args[]</varname> and named arguments as ordinary variables to the
|
||||||
|
Python script. The result is returned from the Python code in the usual way,
|
||||||
|
with <literal>return</literal> or <literal>yield</literal> (in case of
|
||||||
|
a resultset statement).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
For example, a function to return the greater of two integers can be
|
||||||
|
defined as:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION pymax (a integer, b integer)
|
||||||
|
RETURNS integer
|
||||||
|
AS $$
|
||||||
|
if a > b:
|
||||||
|
return a
|
||||||
|
return b
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
The Python code that is given as the body of the function definition
|
The Python code that is given as the body of the function definition
|
||||||
gets transformed into a Python function.
|
is transformed into a Python function. For example, the above results in
|
||||||
For example, the above results in
|
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
def __plpython_procedure_myfunc_23456():
|
def __plpython_procedure_pymax_23456():
|
||||||
return args[0]
|
if a > b:
|
||||||
|
return a
|
||||||
|
return b
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
assuming that 23456 is the OID assigned to the function by
|
assuming that 23456 is the OID assigned to the function by
|
||||||
<productname>PostgreSQL</productname>.
|
<productname>PostgreSQL</productname>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <productname>PostgreSQL</> function parameters are available in
|
||||||
|
the global <varname>args</varname> list. In the
|
||||||
|
<function>pymax</function> example, <varname>args[0]</varname> contains
|
||||||
|
whatever was passed in as the first argument and
|
||||||
|
<varname>args[1]</varname> contains the second argument's value. Alternatively,
|
||||||
|
one can use named parameters as shown in the example above. This greatly simplifies
|
||||||
|
the reading and writing of <application>PL/Python</application> code.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If an SQL null value<indexterm><primary>null value</primary><secondary
|
||||||
|
sortas="PL/Python">PL/Python</secondary></indexterm> is passed to a
|
||||||
|
function, the argument value will appear as <symbol>None</symbol> in
|
||||||
|
Python. The above function definition will return the wrong answer for null
|
||||||
|
inputs. We could add <literal>STRICT</literal> to the function definition
|
||||||
|
to make <productname>PostgreSQL</productname> do something more reasonable:
|
||||||
|
if a null value is passed, the function will not be called at all,
|
||||||
|
but will just return a null result automatically. Alternatively,
|
||||||
|
we could check for null inputs in the function body:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION pymax (a integer, b integer)
|
||||||
|
RETURNS integer
|
||||||
|
AS $$
|
||||||
|
if (a is None) or (b is None):
|
||||||
|
return None
|
||||||
|
if a > b:
|
||||||
|
return a
|
||||||
|
return b
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
As shown above, to return an SQL null value from a PL/Python
|
||||||
|
function, return the value <symbol>None</symbol>. This can be done whether the
|
||||||
|
function is strict or not.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Composite-type arguments are passed to the function as Python mappings. The
|
||||||
|
element names of the mapping are the attribute names of the composite type.
|
||||||
|
If an attribute in the passed row has the null value, it has the value
|
||||||
|
<symbol>None</symbol> in the mapping. Here is an example:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE TABLE employee (
|
||||||
|
name text,
|
||||||
|
salary integer,
|
||||||
|
age integer
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE FUNCTION overpaid (e employee)
|
||||||
|
RETURNS boolean
|
||||||
|
AS $$
|
||||||
|
if e["salary"] > 200000:
|
||||||
|
return True
|
||||||
|
if (e["age"] < 30) and (e["salary"] > 100000):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
There are multiple ways to return row or composite types from a Python
|
||||||
|
scripts. In following examples we assume to have:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE TABLE named_value (
|
||||||
|
name text,
|
||||||
|
value integer
|
||||||
|
);
|
||||||
|
</programlisting>
|
||||||
|
or
|
||||||
|
<programlisting>
|
||||||
|
CREATE TYPE named_value AS (
|
||||||
|
name text,
|
||||||
|
value integer
|
||||||
|
);
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Sequence types (tuple or list), but not <literal>set</literal> (because
|
||||||
|
it is not indexable)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Returned sequence objects must have the same number of items as
|
||||||
|
composite types have fields. Item with index 0 is assigned to the first field
|
||||||
|
of the composite type, 1 to second and so on. For example:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION make_pair (name text, value integer)
|
||||||
|
RETURNS named_value
|
||||||
|
AS $$
|
||||||
|
return [ name, value ]
|
||||||
|
# or alternatively, as tuple: return ( name, value )
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
To return SQL null in any column, insert <symbol>None</symbol> at
|
||||||
|
the corresponding position.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>Mapping (dictionary)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Value for a composite type's column is retrieved from the mapping with
|
||||||
|
the column name as key. Example:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION make_pair (name text, value integer)
|
||||||
|
RETURNS named_value
|
||||||
|
AS $$
|
||||||
|
return { "name": name, "value": value }
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
Additional dictionary key/value pairs are ignored. Missing keys are
|
||||||
|
treated as errors, i.e. to return an SQL null value for any column, insert
|
||||||
|
<symbol>None</symbol> with the corresponding column name as the key.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>Object (any object providing method <literal>__getattr__</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Example:
|
||||||
|
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION make_pair (name text, value integer)
|
||||||
|
RETURNS named_value
|
||||||
|
AS $$
|
||||||
|
class named_value:
|
||||||
|
def __init__ (self, n, v):
|
||||||
|
self.name = n
|
||||||
|
self.value = v
|
||||||
|
return named_value(name, value)
|
||||||
|
|
||||||
|
# or simply
|
||||||
|
class nv: pass
|
||||||
|
nv.name = name
|
||||||
|
nv.value = value
|
||||||
|
return nv
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If you do not provide a return value, Python returns the default
|
If you do not provide a return value, Python returns the default
|
||||||
<symbol>None</symbol>. <application>PL/Python</application> translates
|
<symbol>None</symbol>. <application>PL/Python</application> translates
|
||||||
@ -77,13 +260,100 @@ def __plpython_procedure_myfunc_23456():
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The <productname>PostgreSQL</> function parameters are available in
|
A <application>PL/Python</application> function can also return sets of
|
||||||
the global <varname>args</varname> list. In the
|
scalar or composite types. There are serveral ways to achieve this because
|
||||||
<function>myfunc</function> example, <varname>args[0]</> contains
|
the returned object is internally turned into an iterator. For following
|
||||||
whatever was passed in as the text argument. For
|
examples, let's assume to have composite type:
|
||||||
<literal>myfunc2(text, integer)</literal>, <varname>args[0]</>
|
|
||||||
would contain the <type>text</type> argument and
|
<programlisting>
|
||||||
<varname>args[1]</varname> the <type>integer</type> argument.
|
CREATE TYPE greeting AS (
|
||||||
|
how text,
|
||||||
|
who text
|
||||||
|
);
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
Currently known iterable types are:
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>Sequence types (tuple, list, set)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION greet (how text)
|
||||||
|
RETURNS SETOF greeting
|
||||||
|
AS $$
|
||||||
|
# return tuple containing lists as composite types
|
||||||
|
# all other combinations work also
|
||||||
|
return ( [ how, "World" ], [ how, "PostgreSQL" ], [ how, "PL/Python" ] )
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>Iterator (any object providing <symbol>__iter__</symbol> and
|
||||||
|
<symbol>next</symbol> methods)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION greet (how text)
|
||||||
|
RETURNS SETOF greeting
|
||||||
|
AS $$
|
||||||
|
class producer:
|
||||||
|
def __init__ (self, how, who):
|
||||||
|
self.how = how
|
||||||
|
self.who = who
|
||||||
|
self.ndx = -1
|
||||||
|
|
||||||
|
def __iter__ (self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next (self):
|
||||||
|
self.ndx += 1
|
||||||
|
if self.ndx == len(self.who):
|
||||||
|
raise StopIteration
|
||||||
|
return ( self.how, self.who[self.ndx] )
|
||||||
|
|
||||||
|
return producer(how, [ "World", "PostgreSQL", "PL/Python" ])
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>Generator (<literal>yield</literal>)</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<programlisting>
|
||||||
|
CREATE FUNCTION greet (how text)
|
||||||
|
RETURNS SETOF greeting
|
||||||
|
AS $$
|
||||||
|
for who in [ "World", "PostgreSQL", "PL/Python" ]:
|
||||||
|
yield ( how, who )
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<warning>
|
||||||
|
<para>
|
||||||
|
Currently, due to Python
|
||||||
|
<ulink url="http://sourceforge.net/tracker/index.php?func=detail&aid=1483133&group_id=5470&atid=105470">bug #1483133</ulink>,
|
||||||
|
some debug versions of Python 2.4
|
||||||
|
(configured and compiled with option <literal>--with-pydebug</literal>)
|
||||||
|
are known to crash the <productname>PostgreSQL</productname> server.
|
||||||
|
Unpatched versions of Fedora 4 contain this bug.
|
||||||
|
It does not happen in production version of Python or on patched
|
||||||
|
versions of Fedora 4.
|
||||||
|
</para>
|
||||||
|
</warning>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
Whenever new iterable types are added to Python language,
|
||||||
|
<application>PL/Python</application> is ready to use it.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -55,27 +55,27 @@ except Exception, ex:
|
|||||||
return "failed, that wasn''t supposed to happen"
|
return "failed, that wasn''t supposed to happen"
|
||||||
return "succeeded, as expected"'
|
return "succeeded, as expected"'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
CREATE FUNCTION import_test_one(text) RETURNS text
|
CREATE FUNCTION import_test_one(p text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'import sha
|
'import sha
|
||||||
digest = sha.new(args[0])
|
digest = sha.new(p)
|
||||||
return digest.hexdigest()'
|
return digest.hexdigest()'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
CREATE FUNCTION import_test_two(users) RETURNS text
|
CREATE FUNCTION import_test_two(u users) RETURNS text
|
||||||
AS
|
AS
|
||||||
'import sha
|
'import sha
|
||||||
plain = args[0]["fname"] + args[0]["lname"]
|
plain = u["fname"] + u["lname"]
|
||||||
digest = sha.new(plain);
|
digest = sha.new(plain);
|
||||||
return "sha hash of " + plain + " is " + digest.hexdigest()'
|
return "sha hash of " + plain + " is " + digest.hexdigest()'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
|
CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'keys = args[0].keys()
|
'keys = u.keys()
|
||||||
keys.sort()
|
keys.sort()
|
||||||
out = []
|
out = []
|
||||||
for key in keys:
|
for key in keys:
|
||||||
out.append("%s: %s" % (key, args[0][key]))
|
out.append("%s: %s" % (key, u[key]))
|
||||||
words = args[1] + " " + args[2] + " => {" + ", ".join(out) + "}"
|
words = a1 + " " + a2 + " => {" + ", ".join(out) + "}"
|
||||||
return words'
|
return words'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
-- these triggers are dedicated to HPHC of RI who
|
-- these triggers are dedicated to HPHC of RI who
|
||||||
@ -174,40 +174,40 @@ DROP TRIGGER show_trigger_data_trig on trigger_test;
|
|||||||
DROP FUNCTION trigger_data();
|
DROP FUNCTION trigger_data();
|
||||||
-- nested calls
|
-- nested calls
|
||||||
--
|
--
|
||||||
CREATE FUNCTION nested_call_one(text) RETURNS text
|
CREATE FUNCTION nested_call_one(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'q = "SELECT nested_call_two(''%s'')" % args[0]
|
'q = "SELECT nested_call_two(''%s'')" % a
|
||||||
r = plpy.execute(q)
|
r = plpy.execute(q)
|
||||||
return r[0]'
|
return r[0]'
|
||||||
LANGUAGE plpythonu ;
|
LANGUAGE plpythonu ;
|
||||||
CREATE FUNCTION nested_call_two(text) RETURNS text
|
CREATE FUNCTION nested_call_two(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'q = "SELECT nested_call_three(''%s'')" % args[0]
|
'q = "SELECT nested_call_three(''%s'')" % a
|
||||||
r = plpy.execute(q)
|
r = plpy.execute(q)
|
||||||
return r[0]'
|
return r[0]'
|
||||||
LANGUAGE plpythonu ;
|
LANGUAGE plpythonu ;
|
||||||
CREATE FUNCTION nested_call_three(text) RETURNS text
|
CREATE FUNCTION nested_call_three(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'return args[0]'
|
'return a'
|
||||||
LANGUAGE plpythonu ;
|
LANGUAGE plpythonu ;
|
||||||
-- some spi stuff
|
-- some spi stuff
|
||||||
CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
|
CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("myplan"):
|
'if not SD.has_key("myplan"):
|
||||||
q = "SELECT count(*) FROM users WHERE lname = $1"
|
q = "SELECT count(*) FROM users WHERE lname = $1"
|
||||||
SD["myplan"] = plpy.prepare(q, [ "text" ])
|
SD["myplan"] = plpy.prepare(q, [ "text" ])
|
||||||
try:
|
try:
|
||||||
rv = plpy.execute(SD["myplan"], [args[0]])
|
rv = plpy.execute(SD["myplan"], [a])
|
||||||
return "there are " + str(rv[0]["count"]) + " " + str(args[0]) + "s"
|
return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
|
||||||
except Exception, ex:
|
except Exception, ex:
|
||||||
plpy.error(str(ex))
|
plpy.error(str(ex))
|
||||||
return None
|
return None
|
||||||
'
|
'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
|
CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("myplan"):
|
'if not SD.has_key("myplan"):
|
||||||
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
|
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a
|
||||||
SD["myplan"] = plpy.prepare(q)
|
SD["myplan"] = plpy.prepare(q)
|
||||||
try:
|
try:
|
||||||
rv = plpy.execute(SD["myplan"])
|
rv = plpy.execute(SD["myplan"])
|
||||||
@ -223,12 +223,12 @@ return None
|
|||||||
CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
|
CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
|
||||||
/* a typo
|
/* a typo
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
|
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
q = "SELECT fname FROM users WHERE lname = $1"
|
q = "SELECT fname FROM users WHERE lname = $1"
|
||||||
SD["plan"] = plpy.prepare(q, [ "test" ])
|
SD["plan"] = plpy.prepare(q, [ "test" ])
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -237,7 +237,7 @@ return None
|
|||||||
/* for what it's worth catch the exception generated by
|
/* for what it's worth catch the exception generated by
|
||||||
* the typo, and return None
|
* the typo, and return None
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION invalid_type_caught(text) RETURNS text
|
CREATE FUNCTION invalid_type_caught(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
q = "SELECT fname FROM users WHERE lname = $1"
|
q = "SELECT fname FROM users WHERE lname = $1"
|
||||||
@ -246,7 +246,7 @@ CREATE FUNCTION invalid_type_caught(text) RETURNS text
|
|||||||
except plpy.SPIError, ex:
|
except plpy.SPIError, ex:
|
||||||
plpy.notice(str(ex))
|
plpy.notice(str(ex))
|
||||||
return None
|
return None
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -255,7 +255,7 @@ return None
|
|||||||
/* for what it's worth catch the exception generated by
|
/* for what it's worth catch the exception generated by
|
||||||
* the typo, and reraise it as a plain error
|
* the typo, and reraise it as a plain error
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION invalid_type_reraised(text) RETURNS text
|
CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
q = "SELECT fname FROM users WHERE lname = $1"
|
q = "SELECT fname FROM users WHERE lname = $1"
|
||||||
@ -263,7 +263,7 @@ CREATE FUNCTION invalid_type_reraised(text) RETURNS text
|
|||||||
SD["plan"] = plpy.prepare(q, [ "test" ])
|
SD["plan"] = plpy.prepare(q, [ "test" ])
|
||||||
except plpy.SPIError, ex:
|
except plpy.SPIError, ex:
|
||||||
plpy.error(str(ex))
|
plpy.error(str(ex))
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -271,11 +271,11 @@ return None
|
|||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
/* no typo no messing about
|
/* no typo no messing about
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION valid_type(text) RETURNS text
|
CREATE FUNCTION valid_type(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
|
SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -300,13 +300,13 @@ CREATE FUNCTION exception_index_invalid_nested() RETURNS text
|
|||||||
'rv = plpy.execute("SELECT test5(''foo'')")
|
'rv = plpy.execute("SELECT test5(''foo'')")
|
||||||
return rv[0]'
|
return rv[0]'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
CREATE FUNCTION join_sequences(sequences) RETURNS text
|
CREATE FUNCTION join_sequences(s sequences) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not args[0]["multipart"]:
|
'if not s["multipart"]:
|
||||||
return args[0]["sequence"]
|
return s["sequence"]
|
||||||
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
|
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"]
|
||||||
rv = plpy.execute(q)
|
rv = plpy.execute(q)
|
||||||
seq = args[0]["sequence"]
|
seq = s["sequence"]
|
||||||
for r in rv:
|
for r in rv:
|
||||||
seq = seq + r["sequence"]
|
seq = seq + r["sequence"]
|
||||||
return seq
|
return seq
|
||||||
@ -357,3 +357,83 @@ $$ LANGUAGE plpythonu;
|
|||||||
CREATE FUNCTION test_return_none() RETURNS int AS $$
|
CREATE FUNCTION test_return_none() RETURNS int AS $$
|
||||||
None
|
None
|
||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
|
--
|
||||||
|
-- Test named parameters
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$
|
||||||
|
assert a0 == args[0]
|
||||||
|
assert a1 == args[1]
|
||||||
|
return True
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$
|
||||||
|
assert u == args[0]
|
||||||
|
return str(u)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
-- use deliberately wrong parameter names
|
||||||
|
CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$
|
||||||
|
try:
|
||||||
|
assert a1 == args[0]
|
||||||
|
return False
|
||||||
|
except NameError, e:
|
||||||
|
assert e.args[0].find("a1") > -1
|
||||||
|
return True
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
--
|
||||||
|
-- Test returning SETOF
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
|
||||||
|
return [ content ]*count
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$
|
||||||
|
t = ()
|
||||||
|
for i in xrange(count):
|
||||||
|
t += ( content, )
|
||||||
|
return t
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$
|
||||||
|
class producer:
|
||||||
|
def __init__ (self, icount, icontent):
|
||||||
|
self.icontent = icontent
|
||||||
|
self.icount = icount
|
||||||
|
def __iter__ (self):
|
||||||
|
return self
|
||||||
|
def next (self):
|
||||||
|
if self.icount == 0:
|
||||||
|
raise StopIteration
|
||||||
|
self.icount -= 1
|
||||||
|
return self.icontent
|
||||||
|
return producer(count, content)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
--
|
||||||
|
-- Test returning tuples
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
|
||||||
|
if retnull:
|
||||||
|
return None
|
||||||
|
if typ == 'dict':
|
||||||
|
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
|
||||||
|
elif typ == 'tuple':
|
||||||
|
return ( first, second )
|
||||||
|
elif typ == 'list':
|
||||||
|
return [ first, second ]
|
||||||
|
elif typ == 'obj':
|
||||||
|
class type_record: pass
|
||||||
|
type_record.first = first
|
||||||
|
type_record.second = second
|
||||||
|
return type_record
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$
|
||||||
|
if retnull:
|
||||||
|
return None
|
||||||
|
if typ == 'dict':
|
||||||
|
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
|
||||||
|
elif typ == 'tuple':
|
||||||
|
return ( first, second )
|
||||||
|
elif typ == 'list':
|
||||||
|
return [ first, second ]
|
||||||
|
elif typ == 'obj':
|
||||||
|
class type_record: pass
|
||||||
|
type_record.first = first
|
||||||
|
type_record.second = second
|
||||||
|
return type_record
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
@ -44,3 +44,11 @@ CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
|
|||||||
CREATE TABLE unicode_test (
|
CREATE TABLE unicode_test (
|
||||||
testvalue text NOT NULL
|
testvalue text NOT NULL
|
||||||
);
|
);
|
||||||
|
CREATE TABLE table_record (
|
||||||
|
first text,
|
||||||
|
second int4
|
||||||
|
) ;
|
||||||
|
CREATE TYPE type_record AS (
|
||||||
|
first text,
|
||||||
|
second int4
|
||||||
|
) ;
|
||||||
|
@ -198,3 +198,344 @@ SELECT test_return_none(), test_return_none() IS NULL AS "is null";
|
|||||||
| t
|
| t
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
-- Test for functions with named parameters
|
||||||
|
SELECT test_param_names1(1,'text');
|
||||||
|
test_param_names1
|
||||||
|
-------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT test_param_names2(users) from users;
|
||||||
|
test_param_names2
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
{'lname': 'doe', 'username': 'j_doe', 'userid': 1, 'fname': 'jane'}
|
||||||
|
{'lname': 'doe', 'username': 'johnd', 'userid': 2, 'fname': 'john'}
|
||||||
|
{'lname': 'doe', 'username': 'w_doe', 'userid': 3, 'fname': 'willem'}
|
||||||
|
{'lname': 'smith', 'username': 'slash', 'userid': 4, 'fname': 'rick'}
|
||||||
|
{'lname': 'smith', 'username': 'w_smith', 'userid': 5, 'fname': 'willem'}
|
||||||
|
{'lname': 'darwin', 'username': 'beagle', 'userid': 6, 'fname': 'charles'}
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
|
SELECT test_param_names3(1);
|
||||||
|
test_param_names3
|
||||||
|
-------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Test set returning functions
|
||||||
|
SELECT test_setof_as_list(0, 'list');
|
||||||
|
test_setof_as_list
|
||||||
|
--------------------
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_list(1, 'list');
|
||||||
|
test_setof_as_list
|
||||||
|
--------------------
|
||||||
|
list
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT test_setof_as_list(2, 'list');
|
||||||
|
test_setof_as_list
|
||||||
|
--------------------
|
||||||
|
list
|
||||||
|
list
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_list(2, null);
|
||||||
|
test_setof_as_list
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_tuple(0, 'tuple');
|
||||||
|
test_setof_as_tuple
|
||||||
|
---------------------
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_tuple(1, 'tuple');
|
||||||
|
test_setof_as_tuple
|
||||||
|
---------------------
|
||||||
|
tuple
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT test_setof_as_tuple(2, 'tuple');
|
||||||
|
test_setof_as_tuple
|
||||||
|
---------------------
|
||||||
|
tuple
|
||||||
|
tuple
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_tuple(2, null);
|
||||||
|
test_setof_as_tuple
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_iterator(0, 'list');
|
||||||
|
test_setof_as_iterator
|
||||||
|
------------------------
|
||||||
|
(0 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_iterator(1, 'list');
|
||||||
|
test_setof_as_iterator
|
||||||
|
------------------------
|
||||||
|
list
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT test_setof_as_iterator(2, 'list');
|
||||||
|
test_setof_as_iterator
|
||||||
|
------------------------
|
||||||
|
list
|
||||||
|
list
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SELECT test_setof_as_iterator(2, null);
|
||||||
|
test_setof_as_iterator
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- Test tuple returning functions
|
||||||
|
SELECT * FROM test_table_record_as('dict', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('dict', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('dict', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('dict', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('dict', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('tuple', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('tuple', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('tuple', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('tuple', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('tuple', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('list', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('list', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('list', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('list', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('list', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('obj', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('obj', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('obj', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('obj', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('obj', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('dict', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('dict', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('dict', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('dict', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('dict', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('tuple', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('tuple', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('tuple', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('tuple', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('tuple', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('list', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('list', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('list', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('list', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('list', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('obj', null, null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('obj', 'one', null, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
one |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('obj', null, 2, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
| 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('obj', 'three', 3, false);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
three | 3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('obj', null, null, true);
|
||||||
|
first | second
|
||||||
|
-------+--------
|
||||||
|
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* plpython.c - python as a procedural language for PostgreSQL
|
* plpython.c - python as a procedural language for PostgreSQL
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.86 2006/08/27 23:47:58 tgl Exp $
|
* $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.87 2006/09/02 12:30:01 momjian Exp $
|
||||||
*
|
*
|
||||||
*********************************************************************
|
*********************************************************************
|
||||||
*/
|
*/
|
||||||
@ -30,6 +30,7 @@
|
|||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
#include "executor/spi.h"
|
#include "executor/spi.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "fmgr.h"
|
#include "fmgr.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
@ -121,6 +122,9 @@ typedef struct PLyProcedure
|
|||||||
bool fn_readonly;
|
bool fn_readonly;
|
||||||
PLyTypeInfo result; /* also used to store info for trigger tuple
|
PLyTypeInfo result; /* also used to store info for trigger tuple
|
||||||
* type */
|
* type */
|
||||||
|
bool is_setof; /* true, if procedure returns result set */
|
||||||
|
PyObject *setof; /* contents of result set. */
|
||||||
|
char **argnames; /* Argument names */
|
||||||
PLyTypeInfo args[FUNC_MAX_ARGS];
|
PLyTypeInfo args[FUNC_MAX_ARGS];
|
||||||
int nargs;
|
int nargs;
|
||||||
PyObject *code; /* compiled procedure code */
|
PyObject *code; /* compiled procedure code */
|
||||||
@ -196,6 +200,7 @@ static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *);
|
|||||||
static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *);
|
static HeapTuple PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *);
|
||||||
|
|
||||||
static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *);
|
static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *);
|
||||||
|
static void PLy_function_delete_args(PLyProcedure *);
|
||||||
static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *,
|
static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *,
|
||||||
HeapTuple *);
|
HeapTuple *);
|
||||||
static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
|
static HeapTuple PLy_modify_tuple(PLyProcedure *, PyObject *,
|
||||||
@ -231,6 +236,9 @@ static PyObject *PLyInt_FromString(const char *);
|
|||||||
static PyObject *PLyLong_FromString(const char *);
|
static PyObject *PLyLong_FromString(const char *);
|
||||||
static PyObject *PLyString_FromString(const char *);
|
static PyObject *PLyString_FromString(const char *);
|
||||||
|
|
||||||
|
static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
|
||||||
|
static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
|
||||||
|
static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Currently active plpython function
|
* Currently active plpython function
|
||||||
@ -748,11 +756,17 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
|
|||||||
|
|
||||||
PG_TRY();
|
PG_TRY();
|
||||||
{
|
{
|
||||||
plargs = PLy_function_build_args(fcinfo, proc);
|
if (!proc->is_setof || proc->setof == NULL)
|
||||||
plrv = PLy_procedure_call(proc, "args", plargs);
|
{
|
||||||
|
/* Simple type returning function or first time for SETOF function */
|
||||||
Assert(plrv != NULL);
|
plargs = PLy_function_build_args(fcinfo, proc);
|
||||||
Assert(!PLy_error_in_progress);
|
plrv = PLy_procedure_call(proc, "args", plargs);
|
||||||
|
if (!proc->is_setof)
|
||||||
|
/* SETOF function parameters will be deleted when last row is returned */
|
||||||
|
PLy_function_delete_args(proc);
|
||||||
|
Assert(plrv != NULL);
|
||||||
|
Assert(!PLy_error_in_progress);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disconnect from SPI manager and then create the return values datum
|
* Disconnect from SPI manager and then create the return values datum
|
||||||
@ -763,6 +777,67 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
|
|||||||
if (SPI_finish() != SPI_OK_FINISH)
|
if (SPI_finish() != SPI_OK_FINISH)
|
||||||
elog(ERROR, "SPI_finish failed");
|
elog(ERROR, "SPI_finish failed");
|
||||||
|
|
||||||
|
if (proc->is_setof)
|
||||||
|
{
|
||||||
|
bool has_error = false;
|
||||||
|
ReturnSetInfo *rsi = (ReturnSetInfo *)fcinfo->resultinfo;
|
||||||
|
|
||||||
|
if (proc->setof == NULL)
|
||||||
|
{
|
||||||
|
/* first time -- do checks and setup */
|
||||||
|
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
|
||||||
|
(rsi->allowedModes & SFRM_ValuePerCall) == 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("only value per call is allowed")));
|
||||||
|
}
|
||||||
|
rsi->returnMode = SFRM_ValuePerCall;
|
||||||
|
|
||||||
|
/* Make iterator out of returned object */
|
||||||
|
proc->setof = PyObject_GetIter(plrv);
|
||||||
|
Py_DECREF(plrv);
|
||||||
|
plrv = NULL;
|
||||||
|
|
||||||
|
if (proc->setof == NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
|
errmsg("returned object can not be iterated"),
|
||||||
|
errdetail("SETOF must be returned as iterable object")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fetch next from iterator */
|
||||||
|
plrv = PyIter_Next(proc->setof);
|
||||||
|
if (plrv)
|
||||||
|
rsi->isDone = ExprMultipleResult;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rsi->isDone = ExprEndResult;
|
||||||
|
has_error = PyErr_Occurred() != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rsi->isDone == ExprEndResult)
|
||||||
|
{
|
||||||
|
/* Iterator is exhausted or error happened */
|
||||||
|
Py_DECREF(proc->setof);
|
||||||
|
proc->setof = NULL;
|
||||||
|
|
||||||
|
Py_XDECREF(plargs);
|
||||||
|
Py_XDECREF(plrv);
|
||||||
|
Py_XDECREF(plrv_so);
|
||||||
|
|
||||||
|
PLy_function_delete_args(proc);
|
||||||
|
|
||||||
|
if (has_error)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATA_EXCEPTION),
|
||||||
|
errmsg("error fetching next item from iterator")));
|
||||||
|
|
||||||
|
fcinfo->isnull = true;
|
||||||
|
return (Datum)NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the function is declared to return void, the Python
|
* If the function is declared to return void, the Python
|
||||||
* return value must be None. For void-returning functions, we
|
* return value must be None. For void-returning functions, we
|
||||||
@ -784,10 +859,39 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
|
|||||||
else if (plrv == Py_None)
|
else if (plrv == Py_None)
|
||||||
{
|
{
|
||||||
fcinfo->isnull = true;
|
fcinfo->isnull = true;
|
||||||
rv = InputFunctionCall(&proc->result.out.d.typfunc,
|
if (proc->result.is_rowtype < 1)
|
||||||
NULL,
|
rv = InputFunctionCall(&proc->result.out.d.typfunc,
|
||||||
proc->result.out.d.typioparam,
|
NULL,
|
||||||
-1);
|
proc->result.out.d.typioparam,
|
||||||
|
-1);
|
||||||
|
else
|
||||||
|
/* Tuple as None */
|
||||||
|
rv = (Datum) NULL;
|
||||||
|
}
|
||||||
|
else if (proc->result.is_rowtype >= 1)
|
||||||
|
{
|
||||||
|
HeapTuple tuple = NULL;
|
||||||
|
|
||||||
|
if (PySequence_Check(plrv))
|
||||||
|
/* composite type as sequence (tuple, list etc) */
|
||||||
|
tuple = PLySequence_ToTuple(&proc->result, plrv);
|
||||||
|
else if (PyMapping_Check(plrv))
|
||||||
|
/* composite type as mapping (currently only dict) */
|
||||||
|
tuple = PLyMapping_ToTuple(&proc->result, plrv);
|
||||||
|
else
|
||||||
|
/* returned as smth, must provide method __getattr__(name) */
|
||||||
|
tuple = PLyObject_ToTuple(&proc->result, plrv);
|
||||||
|
|
||||||
|
if (tuple != NULL)
|
||||||
|
{
|
||||||
|
fcinfo->isnull = false;
|
||||||
|
rv = HeapTupleGetDatum(tuple);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fcinfo->isnull = true;
|
||||||
|
rv = (Datum) NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -912,10 +1016,10 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc)
|
|||||||
arg = Py_None;
|
arg = Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (PyList_SetItem(args, i, arg) == -1 ||
|
||||||
* FIXME -- error check this
|
(proc->argnames &&
|
||||||
*/
|
PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1))
|
||||||
PyList_SetItem(args, i, arg);
|
PLy_elog(ERROR, "problem setting up arguments for \"%s\"", proc->proname);
|
||||||
arg = NULL;
|
arg = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -932,6 +1036,19 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
PLy_function_delete_args(PLyProcedure *proc)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!proc->argnames)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < proc->nargs; i++)
|
||||||
|
PyDict_DelItemString(proc->globals, proc->argnames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PLyProcedure functions
|
* PLyProcedure functions
|
||||||
*/
|
*/
|
||||||
@ -1002,6 +1119,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
|
|||||||
bool isnull;
|
bool isnull;
|
||||||
int i,
|
int i,
|
||||||
rv;
|
rv;
|
||||||
|
Datum argnames;
|
||||||
|
Datum *elems;
|
||||||
|
int nelems;
|
||||||
|
|
||||||
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
|
procStruct = (Form_pg_proc) GETSTRUCT(procTup);
|
||||||
|
|
||||||
@ -1033,6 +1153,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
|
|||||||
proc->nargs = 0;
|
proc->nargs = 0;
|
||||||
proc->code = proc->statics = NULL;
|
proc->code = proc->statics = NULL;
|
||||||
proc->globals = proc->me = NULL;
|
proc->globals = proc->me = NULL;
|
||||||
|
proc->is_setof = procStruct->proretset;
|
||||||
|
proc->setof = NULL;
|
||||||
|
proc->argnames = NULL;
|
||||||
|
|
||||||
PG_TRY();
|
PG_TRY();
|
||||||
{
|
{
|
||||||
@ -1069,9 +1192,11 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rvTypeStruct->typtype == 'c')
|
if (rvTypeStruct->typtype == 'c')
|
||||||
ereport(ERROR,
|
{
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
/* Tuple: set up later, during first call to PLy_function_handler */
|
||||||
errmsg("plpython functions cannot return tuples yet")));
|
proc->result.out.d.typoid = procStruct->prorettype;
|
||||||
|
proc->result.is_rowtype = 2;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
PLy_output_datum_func(&proc->result, rvTypeTup);
|
PLy_output_datum_func(&proc->result, rvTypeTup);
|
||||||
|
|
||||||
@ -1094,6 +1219,20 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
|
|||||||
* arguments.
|
* arguments.
|
||||||
*/
|
*/
|
||||||
proc->nargs = fcinfo->nargs;
|
proc->nargs = fcinfo->nargs;
|
||||||
|
if (proc->nargs)
|
||||||
|
{
|
||||||
|
argnames = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, &isnull);
|
||||||
|
if (!isnull)
|
||||||
|
{
|
||||||
|
deconstruct_array(DatumGetArrayTypeP(argnames), TEXTOID, -1, false, 'i',
|
||||||
|
&elems, NULL, &nelems);
|
||||||
|
if (nelems != proc->nargs)
|
||||||
|
elog(ERROR,
|
||||||
|
"proargnames must have the same number of elements "
|
||||||
|
"as the function has arguments");
|
||||||
|
proc->argnames = (char **) PLy_malloc(sizeof(char *)*proc->nargs);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (i = 0; i < fcinfo->nargs; i++)
|
for (i = 0; i < fcinfo->nargs; i++)
|
||||||
{
|
{
|
||||||
HeapTuple argTypeTup;
|
HeapTuple argTypeTup;
|
||||||
@ -1122,8 +1261,11 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
|
|||||||
proc->args[i].is_rowtype = 2; /* still need to set I/O funcs */
|
proc->args[i].is_rowtype = 2; /* still need to set I/O funcs */
|
||||||
|
|
||||||
ReleaseSysCache(argTypeTup);
|
ReleaseSysCache(argTypeTup);
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Fetch argument name */
|
||||||
|
if (proc->argnames)
|
||||||
|
proc->argnames[i] = PLy_strdup(DatumGetCString(DirectFunctionCall1(textout, elems[i])));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get the text of the function.
|
* get the text of the function.
|
||||||
@ -1259,6 +1401,7 @@ PLy_procedure_delete(PLyProcedure * proc)
|
|||||||
if (proc->pyname)
|
if (proc->pyname)
|
||||||
PLy_free(proc->pyname);
|
PLy_free(proc->pyname);
|
||||||
for (i = 0; i < proc->nargs; i++)
|
for (i = 0; i < proc->nargs; i++)
|
||||||
|
{
|
||||||
if (proc->args[i].is_rowtype == 1)
|
if (proc->args[i].is_rowtype == 1)
|
||||||
{
|
{
|
||||||
if (proc->args[i].in.r.atts)
|
if (proc->args[i].in.r.atts)
|
||||||
@ -1266,6 +1409,11 @@ PLy_procedure_delete(PLyProcedure * proc)
|
|||||||
if (proc->args[i].out.r.atts)
|
if (proc->args[i].out.r.atts)
|
||||||
PLy_free(proc->args[i].out.r.atts);
|
PLy_free(proc->args[i].out.r.atts);
|
||||||
}
|
}
|
||||||
|
if (proc->argnames && proc->argnames[i])
|
||||||
|
PLy_free(proc->argnames[i]);
|
||||||
|
}
|
||||||
|
if (proc->argnames)
|
||||||
|
PLy_free(proc->argnames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* conversion functions. remember output from python is
|
/* conversion functions. remember output from python is
|
||||||
@ -1524,6 +1672,247 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc)
|
|||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static HeapTuple
|
||||||
|
PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
|
||||||
|
{
|
||||||
|
TupleDesc desc;
|
||||||
|
HeapTuple tuple;
|
||||||
|
Datum *values;
|
||||||
|
char *nulls;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
Assert(PyMapping_Check(mapping));
|
||||||
|
|
||||||
|
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
|
||||||
|
if (info->is_rowtype == 2)
|
||||||
|
PLy_output_tuple_funcs(info, desc);
|
||||||
|
Assert(info->is_rowtype == 1);
|
||||||
|
|
||||||
|
/* Build tuple */
|
||||||
|
values = palloc(sizeof(Datum)*desc->natts);
|
||||||
|
nulls = palloc(sizeof(char)*desc->natts);
|
||||||
|
for (i = 0; i < desc->natts; ++i)
|
||||||
|
{
|
||||||
|
char *key;
|
||||||
|
PyObject *value,
|
||||||
|
*so;
|
||||||
|
|
||||||
|
key = NameStr(desc->attrs[i]->attname);
|
||||||
|
value = so = NULL;
|
||||||
|
PG_TRY();
|
||||||
|
{
|
||||||
|
value = PyMapping_GetItemString(mapping, key);
|
||||||
|
if (value == Py_None)
|
||||||
|
{
|
||||||
|
values[i] = (Datum) NULL;
|
||||||
|
nulls[i] = 'n';
|
||||||
|
}
|
||||||
|
else if (value)
|
||||||
|
{
|
||||||
|
char *valuestr;
|
||||||
|
|
||||||
|
so = PyObject_Str(value);
|
||||||
|
if (so == NULL)
|
||||||
|
PLy_elog(ERROR, "can't convert mapping type");
|
||||||
|
valuestr = PyString_AsString(so);
|
||||||
|
|
||||||
|
values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
|
||||||
|
, valuestr
|
||||||
|
, info->out.r.atts[i].typioparam
|
||||||
|
, -1);
|
||||||
|
Py_DECREF(so);
|
||||||
|
so = NULL;
|
||||||
|
nulls[i] = ' ';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
|
errmsg("no mapping found with key \"%s\"", key),
|
||||||
|
errhint("to return null in specific column, "
|
||||||
|
"add value None to map with key named after column")));
|
||||||
|
|
||||||
|
Py_XDECREF(value);
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
PG_CATCH();
|
||||||
|
{
|
||||||
|
Py_XDECREF(so);
|
||||||
|
Py_XDECREF(value);
|
||||||
|
PG_RE_THROW();
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple = heap_formtuple(desc, values, nulls);
|
||||||
|
ReleaseTupleDesc(desc);
|
||||||
|
pfree(values);
|
||||||
|
pfree(nulls);
|
||||||
|
|
||||||
|
return tuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static HeapTuple
|
||||||
|
PLySequence_ToTuple(PLyTypeInfo *info, PyObject *sequence)
|
||||||
|
{
|
||||||
|
TupleDesc desc;
|
||||||
|
HeapTuple tuple;
|
||||||
|
Datum *values;
|
||||||
|
char *nulls;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
Assert(PySequence_Check(sequence));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check that sequence length is exactly same as PG tuple's. We actually
|
||||||
|
* can ignore exceeding items or assume missing ones as null but to
|
||||||
|
* avoid plpython developer's errors we are strict here
|
||||||
|
*/
|
||||||
|
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
|
||||||
|
if (PySequence_Length(sequence) != desc->natts)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||||
|
errmsg("returned sequence's length must be same as tuple's length")));
|
||||||
|
|
||||||
|
if (info->is_rowtype == 2)
|
||||||
|
PLy_output_tuple_funcs(info, desc);
|
||||||
|
Assert(info->is_rowtype == 1);
|
||||||
|
|
||||||
|
/* Build tuple */
|
||||||
|
values = palloc(sizeof(Datum)*desc->natts);
|
||||||
|
nulls = palloc(sizeof(char)*desc->natts);
|
||||||
|
for (i = 0; i < desc->natts; ++i)
|
||||||
|
{
|
||||||
|
PyObject *value,
|
||||||
|
*so;
|
||||||
|
|
||||||
|
value = so = NULL;
|
||||||
|
PG_TRY();
|
||||||
|
{
|
||||||
|
value = PySequence_GetItem(sequence, i);
|
||||||
|
Assert(value);
|
||||||
|
if (value == Py_None)
|
||||||
|
{
|
||||||
|
values[i] = (Datum) NULL;
|
||||||
|
nulls[i] = 'n';
|
||||||
|
}
|
||||||
|
else if (value)
|
||||||
|
{
|
||||||
|
char *valuestr;
|
||||||
|
|
||||||
|
so = PyObject_Str(value);
|
||||||
|
if (so == NULL)
|
||||||
|
PLy_elog(ERROR, "can't convert sequence type");
|
||||||
|
valuestr = PyString_AsString(so);
|
||||||
|
values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
|
||||||
|
, valuestr
|
||||||
|
, info->out.r.atts[i].typioparam
|
||||||
|
, -1);
|
||||||
|
Py_DECREF(so);
|
||||||
|
so = NULL;
|
||||||
|
nulls[i] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(value);
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
PG_CATCH();
|
||||||
|
{
|
||||||
|
Py_XDECREF(so);
|
||||||
|
Py_XDECREF(value);
|
||||||
|
PG_RE_THROW();
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple = heap_formtuple(desc, values, nulls);
|
||||||
|
ReleaseTupleDesc(desc);
|
||||||
|
pfree(values);
|
||||||
|
pfree(nulls);
|
||||||
|
|
||||||
|
return tuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static HeapTuple
|
||||||
|
PLyObject_ToTuple(PLyTypeInfo *info, PyObject *object)
|
||||||
|
{
|
||||||
|
TupleDesc desc;
|
||||||
|
HeapTuple tuple;
|
||||||
|
Datum *values;
|
||||||
|
char *nulls;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
|
||||||
|
if (info->is_rowtype == 2)
|
||||||
|
PLy_output_tuple_funcs(info, desc);
|
||||||
|
Assert(info->is_rowtype == 1);
|
||||||
|
|
||||||
|
/* Build tuple */
|
||||||
|
values = palloc(sizeof(Datum)*desc->natts);
|
||||||
|
nulls = palloc(sizeof(char)*desc->natts);
|
||||||
|
for (i = 0; i < desc->natts; ++i)
|
||||||
|
{
|
||||||
|
char *key;
|
||||||
|
PyObject *value,
|
||||||
|
*so;
|
||||||
|
|
||||||
|
key = NameStr(desc->attrs[i]->attname);
|
||||||
|
value = so = NULL;
|
||||||
|
PG_TRY();
|
||||||
|
{
|
||||||
|
value = PyObject_GetAttrString(object, key);
|
||||||
|
if (value == Py_None)
|
||||||
|
{
|
||||||
|
values[i] = (Datum) NULL;
|
||||||
|
nulls[i] = 'n';
|
||||||
|
}
|
||||||
|
else if (value)
|
||||||
|
{
|
||||||
|
char *valuestr;
|
||||||
|
|
||||||
|
so = PyObject_Str(value);
|
||||||
|
if (so == NULL)
|
||||||
|
PLy_elog(ERROR, "can't convert object type");
|
||||||
|
valuestr = PyString_AsString(so);
|
||||||
|
values[i] = InputFunctionCall(&info->out.r.atts[i].typfunc
|
||||||
|
, valuestr
|
||||||
|
, info->out.r.atts[i].typioparam
|
||||||
|
, -1);
|
||||||
|
Py_DECREF(so);
|
||||||
|
so = NULL;
|
||||||
|
nulls[i] = ' ';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
|
errmsg("no attribute named \"%s\"", key),
|
||||||
|
errhint("to return null in specific column, "
|
||||||
|
"let returned object to have attribute named "
|
||||||
|
"after column with value None")));
|
||||||
|
|
||||||
|
Py_XDECREF(value);
|
||||||
|
value = NULL;
|
||||||
|
}
|
||||||
|
PG_CATCH();
|
||||||
|
{
|
||||||
|
Py_XDECREF(so);
|
||||||
|
Py_XDECREF(value);
|
||||||
|
PG_RE_THROW();
|
||||||
|
}
|
||||||
|
PG_END_TRY();
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple = heap_formtuple(desc, values, nulls);
|
||||||
|
ReleaseTupleDesc(desc);
|
||||||
|
pfree(values);
|
||||||
|
pfree(nulls);
|
||||||
|
|
||||||
|
return tuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* initialization, some python variables function declared here */
|
/* initialization, some python variables function declared here */
|
||||||
|
|
||||||
/* interface to postgresql elog */
|
/* interface to postgresql elog */
|
||||||
|
@ -65,29 +65,29 @@ except Exception, ex:
|
|||||||
return "succeeded, as expected"'
|
return "succeeded, as expected"'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
|
|
||||||
CREATE FUNCTION import_test_one(text) RETURNS text
|
CREATE FUNCTION import_test_one(p text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'import sha
|
'import sha
|
||||||
digest = sha.new(args[0])
|
digest = sha.new(p)
|
||||||
return digest.hexdigest()'
|
return digest.hexdigest()'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
|
|
||||||
CREATE FUNCTION import_test_two(users) RETURNS text
|
CREATE FUNCTION import_test_two(u users) RETURNS text
|
||||||
AS
|
AS
|
||||||
'import sha
|
'import sha
|
||||||
plain = args[0]["fname"] + args[0]["lname"]
|
plain = u["fname"] + u["lname"]
|
||||||
digest = sha.new(plain);
|
digest = sha.new(plain);
|
||||||
return "sha hash of " + plain + " is " + digest.hexdigest()'
|
return "sha hash of " + plain + " is " + digest.hexdigest()'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
|
|
||||||
CREATE FUNCTION argument_test_one(users, text, text) RETURNS text
|
CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'keys = args[0].keys()
|
'keys = u.keys()
|
||||||
keys.sort()
|
keys.sort()
|
||||||
out = []
|
out = []
|
||||||
for key in keys:
|
for key in keys:
|
||||||
out.append("%s: %s" % (key, args[0][key]))
|
out.append("%s: %s" % (key, u[key]))
|
||||||
words = args[1] + " " + args[2] + " => {" + ", ".join(out) + "}"
|
words = a1 + " " + a2 + " => {" + ", ".join(out) + "}"
|
||||||
return words'
|
return words'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
|
|
||||||
@ -176,45 +176,45 @@ DROP FUNCTION trigger_data();
|
|||||||
-- nested calls
|
-- nested calls
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE FUNCTION nested_call_one(text) RETURNS text
|
CREATE FUNCTION nested_call_one(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'q = "SELECT nested_call_two(''%s'')" % args[0]
|
'q = "SELECT nested_call_two(''%s'')" % a
|
||||||
r = plpy.execute(q)
|
r = plpy.execute(q)
|
||||||
return r[0]'
|
return r[0]'
|
||||||
LANGUAGE plpythonu ;
|
LANGUAGE plpythonu ;
|
||||||
|
|
||||||
CREATE FUNCTION nested_call_two(text) RETURNS text
|
CREATE FUNCTION nested_call_two(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'q = "SELECT nested_call_three(''%s'')" % args[0]
|
'q = "SELECT nested_call_three(''%s'')" % a
|
||||||
r = plpy.execute(q)
|
r = plpy.execute(q)
|
||||||
return r[0]'
|
return r[0]'
|
||||||
LANGUAGE plpythonu ;
|
LANGUAGE plpythonu ;
|
||||||
|
|
||||||
CREATE FUNCTION nested_call_three(text) RETURNS text
|
CREATE FUNCTION nested_call_three(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'return args[0]'
|
'return a'
|
||||||
LANGUAGE plpythonu ;
|
LANGUAGE plpythonu ;
|
||||||
|
|
||||||
-- some spi stuff
|
-- some spi stuff
|
||||||
|
|
||||||
CREATE FUNCTION spi_prepared_plan_test_one(text) RETURNS text
|
CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("myplan"):
|
'if not SD.has_key("myplan"):
|
||||||
q = "SELECT count(*) FROM users WHERE lname = $1"
|
q = "SELECT count(*) FROM users WHERE lname = $1"
|
||||||
SD["myplan"] = plpy.prepare(q, [ "text" ])
|
SD["myplan"] = plpy.prepare(q, [ "text" ])
|
||||||
try:
|
try:
|
||||||
rv = plpy.execute(SD["myplan"], [args[0]])
|
rv = plpy.execute(SD["myplan"], [a])
|
||||||
return "there are " + str(rv[0]["count"]) + " " + str(args[0]) + "s"
|
return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
|
||||||
except Exception, ex:
|
except Exception, ex:
|
||||||
plpy.error(str(ex))
|
plpy.error(str(ex))
|
||||||
return None
|
return None
|
||||||
'
|
'
|
||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
|
|
||||||
CREATE FUNCTION spi_prepared_plan_test_nested(text) RETURNS text
|
CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("myplan"):
|
'if not SD.has_key("myplan"):
|
||||||
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % args[0]
|
q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a
|
||||||
SD["myplan"] = plpy.prepare(q)
|
SD["myplan"] = plpy.prepare(q)
|
||||||
try:
|
try:
|
||||||
rv = plpy.execute(SD["myplan"])
|
rv = plpy.execute(SD["myplan"])
|
||||||
@ -233,12 +233,12 @@ CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu;
|
|||||||
|
|
||||||
/* a typo
|
/* a typo
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION invalid_type_uncaught(text) RETURNS text
|
CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
q = "SELECT fname FROM users WHERE lname = $1"
|
q = "SELECT fname FROM users WHERE lname = $1"
|
||||||
SD["plan"] = plpy.prepare(q, [ "test" ])
|
SD["plan"] = plpy.prepare(q, [ "test" ])
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -248,7 +248,7 @@ return None
|
|||||||
/* for what it's worth catch the exception generated by
|
/* for what it's worth catch the exception generated by
|
||||||
* the typo, and return None
|
* the typo, and return None
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION invalid_type_caught(text) RETURNS text
|
CREATE FUNCTION invalid_type_caught(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
q = "SELECT fname FROM users WHERE lname = $1"
|
q = "SELECT fname FROM users WHERE lname = $1"
|
||||||
@ -257,7 +257,7 @@ CREATE FUNCTION invalid_type_caught(text) RETURNS text
|
|||||||
except plpy.SPIError, ex:
|
except plpy.SPIError, ex:
|
||||||
plpy.notice(str(ex))
|
plpy.notice(str(ex))
|
||||||
return None
|
return None
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -267,7 +267,7 @@ return None
|
|||||||
/* for what it's worth catch the exception generated by
|
/* for what it's worth catch the exception generated by
|
||||||
* the typo, and reraise it as a plain error
|
* the typo, and reraise it as a plain error
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION invalid_type_reraised(text) RETURNS text
|
CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
q = "SELECT fname FROM users WHERE lname = $1"
|
q = "SELECT fname FROM users WHERE lname = $1"
|
||||||
@ -275,7 +275,7 @@ CREATE FUNCTION invalid_type_reraised(text) RETURNS text
|
|||||||
SD["plan"] = plpy.prepare(q, [ "test" ])
|
SD["plan"] = plpy.prepare(q, [ "test" ])
|
||||||
except plpy.SPIError, ex:
|
except plpy.SPIError, ex:
|
||||||
plpy.error(str(ex))
|
plpy.error(str(ex))
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -285,11 +285,11 @@ return None
|
|||||||
|
|
||||||
/* no typo no messing about
|
/* no typo no messing about
|
||||||
*/
|
*/
|
||||||
CREATE FUNCTION valid_type(text) RETURNS text
|
CREATE FUNCTION valid_type(a text) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not SD.has_key("plan"):
|
'if not SD.has_key("plan"):
|
||||||
SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
|
SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
|
||||||
rv = plpy.execute(SD["plan"], [ args[0] ])
|
rv = plpy.execute(SD["plan"], [ a ])
|
||||||
if len(rv):
|
if len(rv):
|
||||||
return rv[0]["fname"]
|
return rv[0]["fname"]
|
||||||
return None
|
return None
|
||||||
@ -318,13 +318,13 @@ return rv[0]'
|
|||||||
LANGUAGE plpythonu;
|
LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
|
||||||
CREATE FUNCTION join_sequences(sequences) RETURNS text
|
CREATE FUNCTION join_sequences(s sequences) RETURNS text
|
||||||
AS
|
AS
|
||||||
'if not args[0]["multipart"]:
|
'if not s["multipart"]:
|
||||||
return args[0]["sequence"]
|
return s["sequence"]
|
||||||
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % args[0]["pid"]
|
q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"]
|
||||||
rv = plpy.execute(q)
|
rv = plpy.execute(q)
|
||||||
seq = args[0]["sequence"]
|
seq = s["sequence"]
|
||||||
for r in rv:
|
for r in rv:
|
||||||
seq = seq + r["sequence"]
|
seq = seq + r["sequence"]
|
||||||
return seq
|
return seq
|
||||||
@ -389,3 +389,95 @@ $$ LANGUAGE plpythonu;
|
|||||||
CREATE FUNCTION test_return_none() RETURNS int AS $$
|
CREATE FUNCTION test_return_none() RETURNS int AS $$
|
||||||
None
|
None
|
||||||
$$ LANGUAGE plpythonu;
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test named parameters
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$
|
||||||
|
assert a0 == args[0]
|
||||||
|
assert a1 == args[1]
|
||||||
|
return True
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$
|
||||||
|
assert u == args[0]
|
||||||
|
return str(u)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
-- use deliberately wrong parameter names
|
||||||
|
CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$
|
||||||
|
try:
|
||||||
|
assert a1 == args[0]
|
||||||
|
return False
|
||||||
|
except NameError, e:
|
||||||
|
assert e.args[0].find("a1") > -1
|
||||||
|
return True
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test returning SETOF
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
|
||||||
|
return [ content ]*count
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$
|
||||||
|
t = ()
|
||||||
|
for i in xrange(count):
|
||||||
|
t += ( content, )
|
||||||
|
return t
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$
|
||||||
|
class producer:
|
||||||
|
def __init__ (self, icount, icontent):
|
||||||
|
self.icontent = icontent
|
||||||
|
self.icount = icount
|
||||||
|
def __iter__ (self):
|
||||||
|
return self
|
||||||
|
def next (self):
|
||||||
|
if self.icount == 0:
|
||||||
|
raise StopIteration
|
||||||
|
self.icount -= 1
|
||||||
|
return self.icontent
|
||||||
|
return producer(count, content)
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test returning tuples
|
||||||
|
--
|
||||||
|
CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
|
||||||
|
if retnull:
|
||||||
|
return None
|
||||||
|
if typ == 'dict':
|
||||||
|
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
|
||||||
|
elif typ == 'tuple':
|
||||||
|
return ( first, second )
|
||||||
|
elif typ == 'list':
|
||||||
|
return [ first, second ]
|
||||||
|
elif typ == 'obj':
|
||||||
|
class type_record: pass
|
||||||
|
type_record.first = first
|
||||||
|
type_record.second = second
|
||||||
|
return type_record
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$
|
||||||
|
if retnull:
|
||||||
|
return None
|
||||||
|
if typ == 'dict':
|
||||||
|
return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
|
||||||
|
elif typ == 'tuple':
|
||||||
|
return ( first, second )
|
||||||
|
elif typ == 'list':
|
||||||
|
return [ first, second ]
|
||||||
|
elif typ == 'obj':
|
||||||
|
class type_record: pass
|
||||||
|
type_record.first = first
|
||||||
|
type_record.second = second
|
||||||
|
return type_record
|
||||||
|
$$ LANGUAGE plpythonu;
|
||||||
|
|
||||||
|
@ -42,3 +42,13 @@ CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
|
|||||||
CREATE TABLE unicode_test (
|
CREATE TABLE unicode_test (
|
||||||
testvalue text NOT NULL
|
testvalue text NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE table_record (
|
||||||
|
first text,
|
||||||
|
second int4
|
||||||
|
) ;
|
||||||
|
|
||||||
|
CREATE TYPE type_record AS (
|
||||||
|
first text,
|
||||||
|
second int4
|
||||||
|
) ;
|
||||||
|
@ -73,3 +73,73 @@ SELECT newline_crlf();
|
|||||||
SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
|
SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
|
||||||
SELECT test_void_func2(); -- should fail
|
SELECT test_void_func2(); -- should fail
|
||||||
SELECT test_return_none(), test_return_none() IS NULL AS "is null";
|
SELECT test_return_none(), test_return_none() IS NULL AS "is null";
|
||||||
|
|
||||||
|
-- Test for functions with named parameters
|
||||||
|
SELECT test_param_names1(1,'text');
|
||||||
|
SELECT test_param_names2(users) from users;
|
||||||
|
SELECT test_param_names3(1);
|
||||||
|
|
||||||
|
-- Test set returning functions
|
||||||
|
SELECT test_setof_as_list(0, 'list');
|
||||||
|
SELECT test_setof_as_list(1, 'list');
|
||||||
|
SELECT test_setof_as_list(2, 'list');
|
||||||
|
SELECT test_setof_as_list(2, null);
|
||||||
|
|
||||||
|
SELECT test_setof_as_tuple(0, 'tuple');
|
||||||
|
SELECT test_setof_as_tuple(1, 'tuple');
|
||||||
|
SELECT test_setof_as_tuple(2, 'tuple');
|
||||||
|
SELECT test_setof_as_tuple(2, null);
|
||||||
|
|
||||||
|
SELECT test_setof_as_iterator(0, 'list');
|
||||||
|
SELECT test_setof_as_iterator(1, 'list');
|
||||||
|
SELECT test_setof_as_iterator(2, 'list');
|
||||||
|
SELECT test_setof_as_iterator(2, null);
|
||||||
|
|
||||||
|
-- Test tuple returning functions
|
||||||
|
SELECT * FROM test_table_record_as('dict', null, null, false);
|
||||||
|
SELECT * FROM test_table_record_as('dict', 'one', null, false);
|
||||||
|
SELECT * FROM test_table_record_as('dict', null, 2, false);
|
||||||
|
SELECT * FROM test_table_record_as('dict', 'three', 3, false);
|
||||||
|
SELECT * FROM test_table_record_as('dict', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('tuple', null, null, false);
|
||||||
|
SELECT * FROM test_table_record_as('tuple', 'one', null, false);
|
||||||
|
SELECT * FROM test_table_record_as('tuple', null, 2, false);
|
||||||
|
SELECT * FROM test_table_record_as('tuple', 'three', 3, false);
|
||||||
|
SELECT * FROM test_table_record_as('tuple', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('list', null, null, false);
|
||||||
|
SELECT * FROM test_table_record_as('list', 'one', null, false);
|
||||||
|
SELECT * FROM test_table_record_as('list', null, 2, false);
|
||||||
|
SELECT * FROM test_table_record_as('list', 'three', 3, false);
|
||||||
|
SELECT * FROM test_table_record_as('list', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_table_record_as('obj', null, null, false);
|
||||||
|
SELECT * FROM test_table_record_as('obj', 'one', null, false);
|
||||||
|
SELECT * FROM test_table_record_as('obj', null, 2, false);
|
||||||
|
SELECT * FROM test_table_record_as('obj', 'three', 3, false);
|
||||||
|
SELECT * FROM test_table_record_as('obj', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('dict', null, null, false);
|
||||||
|
SELECT * FROM test_type_record_as('dict', 'one', null, false);
|
||||||
|
SELECT * FROM test_type_record_as('dict', null, 2, false);
|
||||||
|
SELECT * FROM test_type_record_as('dict', 'three', 3, false);
|
||||||
|
SELECT * FROM test_type_record_as('dict', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('tuple', null, null, false);
|
||||||
|
SELECT * FROM test_type_record_as('tuple', 'one', null, false);
|
||||||
|
SELECT * FROM test_type_record_as('tuple', null, 2, false);
|
||||||
|
SELECT * FROM test_type_record_as('tuple', 'three', 3, false);
|
||||||
|
SELECT * FROM test_type_record_as('tuple', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('list', null, null, false);
|
||||||
|
SELECT * FROM test_type_record_as('list', 'one', null, false);
|
||||||
|
SELECT * FROM test_type_record_as('list', null, 2, false);
|
||||||
|
SELECT * FROM test_type_record_as('list', 'three', 3, false);
|
||||||
|
SELECT * FROM test_type_record_as('list', null, null, true);
|
||||||
|
|
||||||
|
SELECT * FROM test_type_record_as('obj', null, null, false);
|
||||||
|
SELECT * FROM test_type_record_as('obj', 'one', null, false);
|
||||||
|
SELECT * FROM test_type_record_as('obj', null, 2, false);
|
||||||
|
SELECT * FROM test_type_record_as('obj', 'three', 3, false);
|
||||||
|
SELECT * FROM test_type_record_as('obj', null, null, true);
|
||||||
|
Loading…
Reference in New Issue
Block a user