(1 row)
+-- test various other ways to express Booleans in Python
+CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$
+# numbers
+if n == 0:
+ ret = 0
+elif n == 1:
+ ret = 5
+# strings
+elif n == 2:
+ ret = ''
+elif n == 3:
+ ret = 'fa' # true in Python, false in PostgreSQL
+# containers
+elif n == 4:
+ ret = []
+elif n == 5:
+ ret = [0]
+plpy.info(ret, not not ret)
+return ret
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_bool_other(0);
+INFO: (0, False)
+CONTEXT: PL/Python function "test_type_conversion_bool_other"
+ test_type_conversion_bool_other
+---------------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(1);
+INFO: (5, True)
+CONTEXT: PL/Python function "test_type_conversion_bool_other"
+ test_type_conversion_bool_other
+---------------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(2);
+INFO: ('', False)
+CONTEXT: PL/Python function "test_type_conversion_bool_other"
+ test_type_conversion_bool_other
+---------------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(3);
+INFO: ('fa', True)
+CONTEXT: PL/Python function "test_type_conversion_bool_other"
+ test_type_conversion_bool_other
+---------------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(4);
+INFO: ([], False)
+CONTEXT: PL/Python function "test_type_conversion_bool_other"
+ test_type_conversion_bool_other
+---------------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(5);
+INFO: ([0], True)
+CONTEXT: PL/Python function "test_type_conversion_bool_other"
+ test_type_conversion_bool_other
+---------------------------------
+ t
+(1 row)
+
CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$
plpy.info(x, type(x))
return x
return x
$$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_bytea('hello world');
-INFO: ('\\x68656c6c6f20776f726c64', <type 'str'>)
+INFO: ('hello world', <type 'str'>)
CONTEXT: PL/Python function "test_type_conversion_bytea"
test_type_conversion_bytea
----------------------------
\x68656c6c6f20776f726c64
(1 row)
+SELECT * FROM test_type_conversion_bytea(E'null\\000byte');
+INFO: ('null\x00byte', <type 'str'>)
+CONTEXT: PL/Python function "test_type_conversion_bytea"
+ test_type_conversion_bytea
+----------------------------
+ \x6e756c6c0062797465
+(1 row)
+
SELECT * FROM test_type_conversion_bytea(null);
INFO: (None, <type 'NoneType'>)
CONTEXT: PL/Python function "test_type_conversion_bytea"
except ValueError, e:
return 'FAILED: ' + str(e)
$$ LANGUAGE plpythonu;
-/* This will currently fail because the bytea datum is presented to
- Python as a string in bytea-encoding, which Python doesn't understand. */
SELECT test_type_unmarshal(x) FROM test_type_marshal() x;
- test_type_unmarshal
---------------------------
- FAILED: bad marshal data
+ test_type_unmarshal
+---------------------
+ hello world
(1 row)
--
-- Domains
--
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+return y
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_booltrue(true, true);
+ test_type_conversion_booltrue
+-------------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_booltrue(false, true);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+SELECT * FROM test_type_conversion_booltrue(true, false);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_booltrue"
CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$
plpy.info(x, type(x))
1
(1 row)
+CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL);
+CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$
+return y
+$$ LANGUAGE plpythonu;
+SELECT * FROM test_type_conversion_nnint(10, 20);
+ test_type_conversion_nnint
+----------------------------
+ 20
+(1 row)
+
+SELECT * FROM test_type_conversion_nnint(null, 20);
+ERROR: value for domain nnint violates check constraint "nnint_check"
+SELECT * FROM test_type_conversion_nnint(10, null);
+ERROR: value for domain nnint violates check constraint "nnint_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_nnint"
CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL);
CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$
plpy.info(x, type(x))
return y
$$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold');
-INFO: ('\\x68656c6c6f20776f6c64', <type 'str'>)
+INFO: ('hello wold', <type 'str'>)
CONTEXT: PL/Python function "test_type_conversion_bytea10"
test_type_conversion_bytea10
------------------------------
SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold');
ERROR: value for domain bytea10 violates check constraint "bytea10_check"
SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world');
-INFO: ('\\x68656c6c6f20776f7264', <type 'str'>)
+INFO: ('hello word', <type 'str'>)
CONTEXT: PL/Python function "test_type_conversion_bytea10"
ERROR: value for domain bytea10 violates check constraint "bytea10_check"
CONTEXT: while creating return value
SELECT * FROM test_type_conversion_bytea10(null, 'hello word');
ERROR: value for domain bytea10 violates check constraint "bytea10_check"
SELECT * FROM test_type_conversion_bytea10('hello word', null);
-INFO: ('\\x68656c6c6f20776f7264', <type 'str'>)
+INFO: ('hello word', <type 'str'>)
CONTEXT: PL/Python function "test_type_conversion_bytea10"
ERROR: value for domain bytea10 violates check constraint "bytea10_check"
CONTEXT: while creating return value
/**********************************************************************
* plpython.c - python as a procedural language for PostgreSQL
*
- * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.127 2009/08/25 12:44:59 petere Exp $
+ * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.128 2009/09/09 19:00:09 petere Exp $
*
*********************************************************************
*/
* objects.
*/
-typedef PyObject *(*PLyDatumToObFunc) (const char *);
+struct PLyDatumToOb;
+typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb*, Datum);
typedef struct PLyDatumToOb
{
/* convert PyObject to a Postgresql Datum or tuple.
* output from Python
*/
+
+struct PLyObToDatum;
+struct PLyTypeInfo;
+typedef Datum (*PLyObToDatumFunc) (struct PLyTypeInfo*,
+ struct PLyObToDatum*,
+ PyObject *);
+
typedef struct PLyObToDatum
{
+ PLyObToDatumFunc func;
FmgrInfo typfunc; /* The type's input function */
Oid typoid; /* The OID of the type */
Oid typioparam;
{
PLyTypeInput in;
PLyTypeOutput out;
- int is_rowtype;
-
/*
- * is_rowtype can be: -1 not known yet (initial state) 0 scalar datatype
- * 1 rowtype 2 rowtype, but I/O functions not set up yet
+ * is_rowtype can be: -1 = not known yet (initial state); 0 = scalar datatype;
+ * 1 = rowtype; 2 = rowtype, but I/O functions not set up yet
*/
+ int is_rowtype;
} PLyTypeInfo;
static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
/* conversion functions */
+static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyInt_FromInt16(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyString_FromBytea(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
+
static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
-static PyObject *PLyBool_FromString(const char *);
-static PyObject *PLyFloat_FromString(const char *);
-static PyObject *PLyInt_FromString(const char *);
-static PyObject *PLyLong_FromString(const char *);
-static PyObject *PLyString_FromString(const char *);
+
+static Datum PLyObject_ToBool(PLyTypeInfo *, PLyObToDatum *,
+ PyObject *);
+static Datum PLyObject_ToBytea(PLyTypeInfo *, PLyObToDatum *,
+ PyObject *);
+static Datum PLyObject_ToDatum(PLyTypeInfo *, PLyObToDatum *,
+ PyObject *);
static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *);
static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *);
for (i = 0; i < natts; i++)
{
- char *src;
-
platt = PyList_GetItem(plkeys, i);
if (!PyString_Check(platt))
ereport(ERROR,
}
else if (plval != Py_None)
{
- plstr = PyObject_Str(plval);
- if (!plstr)
- PLy_elog(ERROR, "could not create string representation of Python object");
- src = PyString_AsString(plstr);
-
- modvalues[i] =
- InputFunctionCall(&proc->result.out.r.atts[atti].typfunc,
- src,
- proc->result.out.r.atts[atti].typioparam,
- tupdesc->attrs[atti]->atttypmod);
+ PLyObToDatum *att = &proc->result.out.r.atts[atti];
+ modvalues[i] = (att->func) (&proc->result, att, plval);
modnulls[i] = ' ';
-
- Py_DECREF(plstr);
- plstr = NULL;
}
else
{
Datum rv;
PyObject *volatile plargs = NULL;
PyObject *volatile plrv = NULL;
- PyObject *volatile plrv_so = NULL;
- char *plrv_sc;
ErrorContextCallback plerrcontext;
PG_TRY();
Py_XDECREF(plargs);
Py_XDECREF(plrv);
- Py_XDECREF(plrv_so);
PLy_function_delete_args(proc);
else
{
fcinfo->isnull = false;
- plrv_so = PyObject_Str(plrv);
- if (!plrv_so)
- PLy_elog(ERROR, "could not create string representation of Python object");
- plrv_sc = PyString_AsString(plrv_so);
- rv = InputFunctionCall(&proc->result.out.d.typfunc,
- plrv_sc,
- proc->result.out.d.typioparam,
- -1);
+ rv = (proc->result.out.d.func) (&proc->result,
+ &proc->result.out.d,
+ plrv);
}
}
PG_CATCH();
{
Py_XDECREF(plargs);
Py_XDECREF(plrv);
- Py_XDECREF(plrv_so);
PG_RE_THROW();
}
Py_XDECREF(plargs);
Py_DECREF(plrv);
- Py_XDECREF(plrv_so);
return rv;
}
arg = NULL;
else
{
- char *ct;
-
- ct = OutputFunctionCall(&(proc->args[i].in.d.typfunc),
- fcinfo->arg[i]);
- arg = (proc->args[i].in.d.func) (ct);
- pfree(ct);
+ arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d),
+ fcinfo->arg[i]);
}
}
arg->typoid = HeapTupleGetOid(typeTup);
arg->typioparam = getTypeIOParam(typeTup);
arg->typbyval = typeStruct->typbyval;
+
+ /*
+ * Select a conversion function to convert Python objects to
+ * PostgreSQL datums. Most data types can go through the generic
+ * function.
+ */
+ switch (getBaseType(arg->typoid))
+ {
+ case BOOLOID:
+ arg->func = PLyObject_ToBool;
+ break;
+ case BYTEAOID:
+ arg->func = PLyObject_ToBytea;
+ break;
+ default:
+ arg->func = PLyObject_ToDatum;
+ break;
+ }
}
static void
switch (getBaseType(typeOid))
{
case BOOLOID:
- arg->func = PLyBool_FromString;
+ arg->func = PLyBool_FromBool;
break;
case FLOAT4OID:
+ arg->func = PLyFloat_FromFloat4;
+ break;
case FLOAT8OID:
+ arg->func = PLyFloat_FromFloat8;
+ break;
case NUMERICOID:
- arg->func = PLyFloat_FromString;
+ arg->func = PLyFloat_FromNumeric;
break;
case INT2OID:
+ arg->func = PLyInt_FromInt16;
+ break;
case INT4OID:
- arg->func = PLyInt_FromString;
+ arg->func = PLyInt_FromInt32;
break;
case INT8OID:
- arg->func = PLyLong_FromString;
+ arg->func = PLyLong_FromInt64;
+ break;
+ case BYTEAOID:
+ arg->func = PLyString_FromBytea;
break;
default:
- arg->func = PLyString_FromString;
+ arg->func = PLyString_FromDatum;
break;
}
}
}
}
-/* assumes that a bool is always returned as a 't' or 'f' */
static PyObject *
-PLyBool_FromString(const char *src)
+PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
{
/*
* We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for
* Python >= 2.3, and we support older versions.
* http://docs.python.org/api/boolObjects.html
*/
- if (src[0] == 't')
+ if (DatumGetBool(d))
return PyBool_FromLong(1);
return PyBool_FromLong(0);
}
static PyObject *
-PLyFloat_FromString(const char *src)
+PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d)
{
- double v;
- char *eptr;
+ return PyFloat_FromDouble(DatumGetFloat4(d));
+}
- errno = 0;
- v = strtod(src, &eptr);
- if (*eptr != '\0' || errno)
- return NULL;
- return PyFloat_FromDouble(v);
+static PyObject *
+PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
+{
+ return PyFloat_FromDouble(DatumGetFloat8(d));
}
static PyObject *
-PLyInt_FromString(const char *src)
+PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d)
{
- long v;
- char *eptr;
+ /*
+ * Numeric is cast to a PyFloat:
+ * This results in a loss of precision
+ * Would it be better to cast to PyString?
+ */
+ Datum f = DirectFunctionCall1(numeric_float8, d);
+ double x = DatumGetFloat8(f);
+ return PyFloat_FromDouble(x);
+}
- errno = 0;
- v = strtol(src, &eptr, 0);
- if (*eptr != '\0' || errno)
- return NULL;
- return PyInt_FromLong(v);
+static PyObject *
+PLyInt_FromInt16(PLyDatumToOb *arg, Datum d)
+{
+ return PyInt_FromLong(DatumGetInt16(d));
}
static PyObject *
-PLyLong_FromString(const char *src)
+PLyInt_FromInt32(PLyDatumToOb *arg, Datum d)
{
- return PyLong_FromString((char *) src, NULL, 0);
+ return PyInt_FromLong(DatumGetInt32(d));
}
static PyObject *
-PLyString_FromString(const char *src)
+PLyLong_FromInt64(PLyDatumToOb *arg, Datum d)
{
- return PyString_FromString(src);
+ /* on 32 bit platforms "long" may be too small */
+ if (sizeof(int64) > sizeof(long))
+ return PyLong_FromLongLong(DatumGetInt64(d));
+ else
+ return PyLong_FromLong(DatumGetInt64(d));
+}
+
+static PyObject *
+PLyString_FromBytea(PLyDatumToOb *arg, Datum d)
+{
+ text *txt = DatumGetByteaP(d);
+ char *str = VARDATA(txt);
+ size_t size = VARSIZE(txt) - VARHDRSZ;
+
+ return PyString_FromStringAndSize(str, size);
+}
+
+static PyObject *
+PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
+{
+ char *x = OutputFunctionCall(&arg->typfunc, d);
+ PyObject *r = PyString_FromString(x);
+ pfree(x);
+ return r;
}
static PyObject *
{
for (i = 0; i < info->in.r.natts; i++)
{
- char *key,
- *vsrc;
+ char *key;
Datum vattr;
bool is_null;
PyObject *value;
PyDict_SetItemString(dict, key, Py_None);
else
{
- vsrc = OutputFunctionCall(&info->in.r.atts[i].typfunc,
- vattr);
-
- /*
- * no exceptions allowed
- */
- value = info->in.r.atts[i].func(vsrc);
- pfree(vsrc);
+ value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr);
PyDict_SetItemString(dict, key, value);
Py_DECREF(value);
}
return dict;
}
+/*
+ * Convert a Python object to a PostgreSQL bool datum. This can't go
+ * through the generic conversion function, because Python attaches a
+ * Boolean value to everything, more things than the PostgreSQL bool
+ * type can parse.
+ */
+static Datum
+PLyObject_ToBool(PLyTypeInfo *info,
+ PLyObToDatum *arg,
+ PyObject *plrv)
+{
+ Datum rv;
+
+ Assert(plrv != Py_None);
+ rv = BoolGetDatum(PyObject_IsTrue(plrv));
+
+ if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
+ domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
+
+ return rv;
+}
+
+/*
+ * Convert a Python object to a PostgreSQL bytea datum. This doesn't
+ * go through the generic conversion function to circumvent problems
+ * with embedded nulls. And it's faster this way.
+ */
+static Datum
+PLyObject_ToBytea(PLyTypeInfo *info,
+ PLyObToDatum *arg,
+ PyObject *plrv)
+{
+ PyObject *volatile plrv_so = NULL;
+ Datum rv;
+
+ Assert(plrv != Py_None);
+
+ plrv_so = PyObject_Str(plrv);
+ if (!plrv_so)
+ PLy_elog(ERROR, "could not create string representation of Python object");
+
+ PG_TRY();
+ {
+ char *plrv_sc = PyString_AsString(plrv_so);
+ size_t len = PyString_Size(plrv_so);
+ size_t size = len + VARHDRSZ;
+ bytea *result = palloc(size);
+
+ SET_VARSIZE(result, size);
+ memcpy(VARDATA(result), plrv_sc, len);
+ rv = PointerGetDatum(result);
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(plrv_so);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ Py_XDECREF(plrv_so);
+
+ if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN)
+ domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
+
+ return rv;
+}
+
+/*
+ * Generic conversion function: Convert PyObject to cstring and
+ * cstring into PostgreSQL type.
+ */
+static Datum
+PLyObject_ToDatum(PLyTypeInfo *info,
+ PLyObToDatum *arg,
+ PyObject *plrv)
+{
+ PyObject *volatile plrv_so = NULL;
+ Datum rv;
+
+ Assert(plrv != Py_None);
+
+ plrv_so = PyObject_Str(plrv);
+ if (!plrv_so)
+ PLy_elog(ERROR, "could not create string representation of Python object");
+
+ PG_TRY();
+ {
+ char *plrv_sc = PyString_AsString(plrv_so);
+ size_t plen = PyString_Size(plrv_so);
+ size_t slen = strlen(plrv_sc);
+
+ if (slen < plen)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
+ else if (slen > plen)
+ elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
+ rv = InputFunctionCall(&arg->typfunc, plrv_sc, arg->typioparam, -1);
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(plrv_so);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ Py_XDECREF(plrv_so);
+
+ return rv;
+}
static HeapTuple
PLyMapping_ToTuple(PLyTypeInfo *info, PyObject *mapping)
for (i = 0; i < desc->natts; ++i)
{
char *key;
- PyObject *volatile value,
- *volatile so;
+ PyObject *volatile value;
+ PLyObToDatum *att;
key = NameStr(desc->attrs[i]->attname);
- value = so = NULL;
+ value = NULL;
+ att = &info->out.r.atts[i];
PG_TRY();
{
value = PyMapping_GetItemString(mapping, key);
}
else if (value)
{
- char *valuestr;
-
- so = PyObject_Str(value);
- if (so == NULL)
- PLy_elog(ERROR, "could not compute string representation of Python object");
- 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;
+ values[i] = (att->func) (info, att, value);
nulls[i] = false;
}
else
}
PG_CATCH();
{
- Py_XDECREF(so);
Py_XDECREF(value);
PG_RE_THROW();
}
nulls = palloc(sizeof(bool) * desc->natts);
for (i = 0; i < desc->natts; ++i)
{
- PyObject *volatile value,
- *volatile so;
+ PyObject *volatile value;
+ PLyObToDatum *att;
- value = so = NULL;
+ value = NULL;
+ att = &info->out.r.atts[i];
PG_TRY();
{
value = PySequence_GetItem(sequence, i);
}
else if (value)
{
- char *valuestr;
-
- so = PyObject_Str(value);
- if (so == NULL)
- PLy_elog(ERROR, "could not compute string representation of Python object");
- 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;
+ values[i] = (att->func) (info, att, value);
nulls[i] = false;
}
}
PG_CATCH();
{
- Py_XDECREF(so);
Py_XDECREF(value);
PG_RE_THROW();
}
for (i = 0; i < desc->natts; ++i)
{
char *key;
- PyObject *volatile value,
- *volatile so;
+ PyObject *volatile value;
+ PLyObToDatum *att;
key = NameStr(desc->attrs[i]->attname);
- value = so = NULL;
+ value = NULL;
+ att = &info->out.r.atts[i];
PG_TRY();
{
value = PyObject_GetAttrString(object, key);
}
else if (value)
{
- char *valuestr;
-
- so = PyObject_Str(value);
- if (so == NULL)
- PLy_elog(ERROR, "could not compute string representation of Python object");
- 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;
+ values[i] = (att->func) (info, att, value);
nulls[i] = false;
}
else
}
PG_CATCH();
{
- Py_XDECREF(so);
Py_XDECREF(value);
PG_RE_THROW();
}