Index: plpython.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/plpython.c,v retrieving revision 1.120 diff -c -r1.120 plpython.c *** plpython.c 3 Apr 2009 16:59:42 -0000 1.120 --- plpython.c 26 May 2009 22:58:52 -0000 *************** *** 78,84 **** * objects. */ ! typedef PyObject *(*PLyDatumToObFunc) (const char *); typedef struct PLyDatumToOb { --- 78,85 ---- * objects. */ ! struct PLyDatumToOb; ! typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb*, Datum); typedef struct PLyDatumToOb { *************** *** 104,111 **** --- 105,120 ---- /* convert PyObject to a Postgresql Datum or tuple. * output from Python */ + + struct PLyObToDatum; + struct PLyProcedure; + typedef Datum (*PLyObToDatumFunc) (struct PLyProcedure*, + struct PLyObToDatum*, + PyObject *, bool *isnull); + typedef struct PLyObToDatum { + PLyObToDatumFunc func; FmgrInfo typfunc; /* The type's input function */ Oid typoid; /* The OID of the type */ Oid typioparam; *************** *** 255,270 **** static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc); /* conversion functions */ 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 HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, PyObject *); ! static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, PyObject *); ! static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, PyObject *); /* * Currently active plpython function --- 264,295 ---- 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_FromText(PLyDatumToOb *arg, Datum d); + static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d); + static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); ! ! static Datum PLyObject_ToVoid(PLyProcedure *, PLyObToDatum *, ! PyObject *, bool *isnull); ! static Datum PLyObject_ToBool(PLyProcedure *, PLyObToDatum *, ! PyObject *, bool *isnull); ! static Datum PLyObject_ToBytea(PLyProcedure *, PLyObToDatum *, ! PyObject *, bool *isnull); ! static Datum PLyObject_ToText(PLyProcedure *, PLyObToDatum *, ! PyObject *, bool *isnull); ! static Datum PLyObject_ToDatum(PLyProcedure *, PLyObToDatum *, ! PyObject *, bool *isnull); ! ! static HeapTuple PLyMapping_ToTuple(PLyProcedure *, PyObject *); ! static HeapTuple PLySequence_ToTuple(PLyProcedure *, PyObject *); ! static HeapTuple PLyObject_ToTuple(PLyProcedure *, PyObject *); /* * Currently active plpython function *************** *** 507,514 **** for (i = 0; i < natts; i++) { - char *src; - platt = PyList_GetItem(plkeys, i); if (!PyString_Check(platt)) ereport(ERROR, --- 532,537 ---- *************** *** 533,564 **** modvalues[i] = (Datum) 0; modnulls[i] = 'n'; } ! else if (plval != Py_None) { ! plstr = PyObject_Str(plval); ! if (!plstr) ! PLy_elog(ERROR, "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row", ! proc->proname); ! 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); ! modnulls[i] = ' '; ! ! Py_DECREF(plstr); ! plstr = NULL; ! } ! else ! { ! modvalues[i] = ! InputFunctionCall(&proc->result.out.r.atts[atti].typfunc, ! NULL, ! proc->result.out.r.atts[atti].typioparam, ! tupdesc->attrs[atti]->atttypmod); ! modnulls[i] = 'n'; } Py_DECREF(plval); --- 556,565 ---- modvalues[i] = (Datum) 0; modnulls[i] = 'n'; } ! else { ! PLyObToDatum *att = &proc->result.out.r.atts[atti]; ! modvalues[i] = (att->func) (proc, att, plval, &modnulls[i]); } Py_DECREF(plval); *************** *** 784,791 **** Datum rv; PyObject *volatile plargs = NULL; PyObject *volatile plrv = NULL; - PyObject *volatile plrv_so = NULL; - char *plrv_sc; PG_TRY(); { --- 785,790 ---- *************** *** 862,868 **** Py_XDECREF(plargs); Py_XDECREF(plrv); - Py_XDECREF(plrv_so); PLy_function_delete_args(proc); --- 861,866 ---- *************** *** 876,922 **** } } ! /* ! * If the function is declared to return void, the Python return value ! * must be None. For void-returning functions, we also treat a None ! * return value as a special "void datum" rather than NULL (as is the ! * case for non-void-returning functions). ! */ ! if (proc->result.out.d.typoid == VOIDOID) ! { ! if (plrv != Py_None) ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("PL/Python function with return type \"void\" did not return None"))); ! ! fcinfo->isnull = false; ! rv = (Datum) 0; ! } ! else if (plrv == Py_None) ! { ! fcinfo->isnull = true; ! if (proc->result.is_rowtype < 1) ! rv = InputFunctionCall(&proc->result.out.d.typfunc, ! NULL, ! 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) { --- 874,895 ---- } } ! /* Convert python return value into postgres datatypes */ ! if (proc->result.is_rowtype >= 1) { HeapTuple tuple = NULL; ! if (plrv == Py_None) ! tuple = NULL; ! else if (PySequence_Check(plrv)) /* composite type as sequence (tuple, list etc) */ ! tuple = PLySequence_ToTuple(proc, plrv); else if (PyMapping_Check(plrv)) /* composite type as mapping (currently only dict) */ ! tuple = PLyMapping_ToTuple(proc, plrv); else /* returned as smth, must provide method __getattr__(name) */ ! tuple = PLyObject_ToTuple(proc, plrv); if (tuple != NULL) { *************** *** 931,952 **** } else { ! fcinfo->isnull = false; ! plrv_so = PyObject_Str(plrv); ! if (!plrv_so) ! PLy_elog(ERROR, "could not create string representation of Python object in PL/Python function \"%s\" while creating return value", proc->proname); ! plrv_sc = PyString_AsString(plrv_so); ! rv = InputFunctionCall(&proc->result.out.d.typfunc, ! plrv_sc, ! proc->result.out.d.typioparam, ! -1); } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); - Py_XDECREF(plrv_so); PG_RE_THROW(); } --- 904,919 ---- } else { ! rv = (proc->result.out.d.func) (proc, ! &proc->result.out.d, ! plrv, ! &fcinfo->isnull); } } PG_CATCH(); { Py_XDECREF(plargs); Py_XDECREF(plrv); PG_RE_THROW(); } *************** *** 954,960 **** Py_XDECREF(plargs); Py_DECREF(plrv); - Py_XDECREF(plrv_so); return rv; } --- 921,926 ---- *************** *** 1037,1048 **** 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); } } --- 1003,1010 ---- arg = NULL; else { ! arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d), ! fcinfo->arg[i]); } } *************** *** 1593,1598 **** --- 1555,1589 ---- arg->typoid = HeapTupleGetOid(typeTup); arg->typioparam = getTypeIOParam(typeTup); arg->typbyval = typeStruct->typbyval; + + /* Determine which kind of Python object we will convert to */ + switch (arg->typoid) + { + case VOIDOID: + arg->func = PLyObject_ToVoid; + break; + case BOOLOID: + arg->func = PLyObject_ToBool; + break; + case BYTEAOID: + arg->func = PLyObject_ToBytea; + break; + case BPCHAROID: + case VARCHAROID: + case TEXTOID: + arg->func = PLyObject_ToText; + break; + + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + case INT2OID: + case INT4OID: + case INT8OID: + default: + arg->func = PLyObject_ToDatum; + break; + } } static void *************** *** 1619,1644 **** switch (typeOid) { case BOOLOID: ! arg->func = PLyBool_FromString; break; case FLOAT4OID: case FLOAT8OID: case NUMERICOID: ! arg->func = PLyFloat_FromString; break; case INT2OID: case INT4OID: ! arg->func = PLyInt_FromString; break; case INT8OID: ! arg->func = PLyLong_FromString; break; default: ! arg->func = PLyString_FromString; break; } } static void PLy_typeinfo_init(PLyTypeInfo * arg) { --- 1610,1648 ---- switch (typeOid) { case BOOLOID: ! arg->func = PLyBool_FromBool; break; case FLOAT4OID: + arg->func = PLyFloat_FromFloat4; + break; case FLOAT8OID: + arg->func = PLyFloat_FromFloat8; + break; case NUMERICOID: ! arg->func = PLyFloat_FromNumeric; break; case INT2OID: + arg->func = PLyInt_FromInt16; + break; case INT4OID: ! arg->func = PLyInt_FromInt32; break; case INT8OID: ! arg->func = PLyLong_FromInt64; ! break; ! case BPCHAROID: ! case VARCHAROID: ! case TEXTOID: ! case BYTEAOID: ! arg->func = PLyString_FromText; break; default: ! arg->func = PLyString_FromDatum; break; } } + static void PLy_typeinfo_init(PLyTypeInfo * arg) { *************** *** 1660,1716 **** } } - /* assumes that a bool is always returned as a 't' or 'f' */ static PyObject * ! PLyBool_FromString(const char *src) { /* * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for * generating SQL from trigger functions, but those are only supported in * Python >= 2.3, and we support older versions. * http://docs.python.org/api/boolObjects.html */ ! if (src[0] == 't') return PyBool_FromLong(1); ! return PyBool_FromLong(0); } static PyObject * ! PLyFloat_FromString(const char *src) { ! double v; ! char *eptr; ! errno = 0; ! v = strtod(src, &eptr); ! if (*eptr != '\0' || errno) ! return NULL; ! return PyFloat_FromDouble(v); } static PyObject * ! PLyInt_FromString(const char *src) { ! long v; ! char *eptr; ! errno = 0; ! v = strtol(src, &eptr, 0); ! if (*eptr != '\0' || errno) ! return NULL; ! return PyInt_FromLong(v); } static PyObject * ! PLyLong_FromString(const char *src) { ! return PyLong_FromString((char *) src, NULL, 0); } static PyObject * ! PLyString_FromString(const char *src) { ! return PyString_FromString(src); } static PyObject * --- 1664,1758 ---- } } static PyObject * ! PLyBool_FromBool(PLyDatumToOb *arg, Datum d) { + bool x = DatumGetBool(d); + arg = 0; /* unused */ + /* * We would like to use Py_RETURN_TRUE and Py_RETURN_FALSE here for * generating SQL from trigger functions, but those are only supported in * Python >= 2.3, and we support older versions. * http://docs.python.org/api/boolObjects.html */ ! if (x) return PyBool_FromLong(1); ! else ! return PyBool_FromLong(0); } static PyObject * ! PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d) { ! arg = 0; /* unused */ ! return PyFloat_FromDouble(DatumGetFloat4(d)); ! } ! static PyObject * ! PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d) ! { ! arg = 0; /* unused */ ! return PyFloat_FromDouble(DatumGetFloat8(d)); } static PyObject * ! PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d) { ! /* ! * 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); ! arg = 0; /* unused */ ! return PyFloat_FromDouble(x); ! } ! static PyObject * ! PLyInt_FromInt16(PLyDatumToOb *arg, Datum d) ! { ! arg = 0; /* unused */ ! return PyInt_FromLong(DatumGetInt16(d)); } static PyObject * ! PLyInt_FromInt32(PLyDatumToOb *arg, Datum d) { ! arg = 0; /* unused */ ! return PyInt_FromLong(DatumGetInt32(d)); } static PyObject * ! PLyLong_FromInt64(PLyDatumToOb *arg, Datum d) { ! arg = 0; /* unused */ ! ! /* 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_FromText(PLyDatumToOb *arg, Datum d) ! { ! text *txt = DatumGetTextP(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 * *************** *** 1730,1737 **** { for (i = 0; i < info->in.r.natts; i++) { ! char *key, ! *vsrc; Datum vattr; bool is_null; PyObject *value; --- 1772,1778 ---- { for (i = 0; i < info->in.r.natts; i++) { ! char *key; Datum vattr; bool is_null; PyObject *value; *************** *** 1746,1759 **** 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); PyDict_SetItemString(dict, key, value); Py_DECREF(value); } --- 1787,1793 ---- PyDict_SetItemString(dict, key, Py_None); else { ! value = (info->in.r.atts[i].func) (&info->in.r.atts[i], vattr); PyDict_SetItemString(dict, key, value); Py_DECREF(value); } *************** *** 1769,1777 **** return dict; } static HeapTuple ! PLyMapping_ToTuple(PLyTypeInfo * info, PyObject * mapping) { TupleDesc desc; HeapTuple tuple; --- 1803,2017 ---- return dict; } + static Datum + PLyObject_ToVoid(PLyProcedure *proc, + PLyObToDatum *arg, + PyObject *plrv, + bool *isnull) + { + /* + * If the function is declared to return void, the Python return value must + * be None. For void-returning functions, we also treat a None return value + * as a special "void datum" rather than NULL (as is the case for the + * non-void-returning functions). + */ + if (plrv != Py_None) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python function with return type \"void\" did not " + "return None"))); + + *isnull = false; + return (Datum) 0; + } + + static Datum + PLyObject_ToBool(PLyProcedure *proc, + PLyObToDatum *arg, + PyObject *plrv, + bool *isnull) + { + bool rv; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + + rv = PyObject_IsTrue(plrv); + *isnull = false; + return BoolGetDatum(rv); + } + + + static Datum + PLyObject_ToBytea(PLyProcedure *proc, + PLyObToDatum *arg, + PyObject *plrv, + bool *isnull) + { + PyObject *volatile plrv_so = NULL; + Datum rv; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + + plrv_so = PyObject_Str(plrv); + if (!plrv_so) + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not create string representation of Python " + "object in PL/Python function \"%s\" while creating " + "return value", proc->proname))); + } + + PG_TRY(); + { + char *plrv_sc = PyString_AsString(plrv_so); + size_t len = PyString_Size(plrv_so); + size_t size = len + VARHDRSZ; + bytea *result = (bytea*) 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); + + *isnull = false; + return rv; + } + + static Datum + PLyObject_ToText(PLyProcedure *proc, + PLyObToDatum *arg, + PyObject *plrv, + bool *isnull) + { + PyObject *volatile plrv_so = NULL; + Datum rv; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + + plrv_so = PyObject_Str(plrv); + if (!plrv_so) + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not create string representation of Python " + "object in PL/Python function \"%s\" while creating " + "return value", proc->proname))); + } + + PG_TRY(); + { + char *plrv_sc = PyString_AsString(plrv_so); + size_t len = PyString_Size(plrv_so); + size_t size = len + VARHDRSZ; + text *result; + + if (strlen(plrv_sc) != (size_t) len) + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python function \"%s\" could not convert " + "Python object into text: expected string without " + "null bytes", proc->proname))); + } + + result = (bytea*) 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); + + *isnull = false; + return rv; + } + + /* + * Generic conversion function: + * - Cast PyObject to cstring and cstring into postgres type. + */ + static Datum + PLyObject_ToDatum(PLyProcedure *proc, + PLyObToDatum *arg, + PyObject *plrv, + bool *isnull) + { + PyObject *volatile plrv_so = NULL; + Datum rv; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + + plrv_so = PyObject_Str(plrv); + if (!plrv_so) + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not create string representation of Python " + "object in PL/Python function \"%s\" while creating " + "return value", proc->proname))); + } + + PG_TRY(); + { + char *plrv_sc = PyString_AsString(plrv_so); + size_t len = PyString_Size(plrv_so); + + if (strlen(plrv_sc) != (size_t) len) + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python function \"%s\" could not convert " + "Python object into cstring: expected string without " + "null bytes", proc->proname))); + } + 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); + + *isnull = false; + return rv; + } static HeapTuple ! PLyMapping_ToTuple(PLyProcedure *proc, PyObject *mapping) { TupleDesc desc; HeapTuple tuple; *************** *** 1781,1840 **** 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(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { ! char *key; ! PyObject *volatile value, ! *volatile 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] = true; - } - 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; - nulls[i] = false; - } - else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("key \"%s\" not found in mapping", key), errhint("To return null in a column, " ! "add the value None to the mapping with the key named after the column."))); Py_XDECREF(value); value = NULL; } PG_CATCH(); { - Py_XDECREF(so); Py_XDECREF(value); PG_RE_THROW(); } --- 2021,2062 ---- Assert(PyMapping_Check(mapping)); ! desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, -1); ! if (proc->result.is_rowtype == 2) ! PLy_output_tuple_funcs(&proc->result, desc); ! Assert(proc->result.is_rowtype == 1); /* Build tuple */ values = palloc(sizeof(Datum) * desc->natts); nulls = palloc(sizeof(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { ! char *key; ! PLyObToDatum *att; ! PyObject *volatile value; + att = &proc->result.out.r.atts[i]; key = NameStr(desc->attrs[i]->attname); ! value = NULL; PG_TRY(); { value = PyMapping_GetItemString(mapping, key); ! if (!value) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("key \"%s\" not found in mapping", key), errhint("To return null in a column, " ! "add the value None to the mapping with the " ! "key named after the column."))); ! } ! values[i] = (att->func) (proc, att, value, &nulls[i]); Py_XDECREF(value); value = NULL; } PG_CATCH(); { Py_XDECREF(value); PG_RE_THROW(); } *************** *** 1851,1857 **** static HeapTuple ! PLySequence_ToTuple(PLyTypeInfo * info, PyObject * sequence) { TupleDesc desc; HeapTuple tuple; --- 2073,2079 ---- static HeapTuple ! PLySequence_ToTuple(PLyProcedure *proc, PyObject *sequence) { TupleDesc desc; HeapTuple tuple; *************** *** 1866,1922 **** * 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("length of returned sequence did not match number of columns in row"))); ! 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(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { ! PyObject *volatile value, ! *volatile so; ! value = so = NULL; PG_TRY(); { value = PySequence_GetItem(sequence, i); Assert(value); ! if (value == Py_None) ! { ! values[i] = (Datum) NULL; ! nulls[i] = true; ! } ! 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; ! nulls[i] = false; ! } Py_XDECREF(value); value = NULL; } PG_CATCH(); { - Py_XDECREF(so); Py_XDECREF(value); PG_RE_THROW(); } --- 2088,2124 ---- * 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(proc->result.out.d.typoid, -1); if (PySequence_Length(sequence) != desc->natts) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("length of returned sequence did not match number of columns in row"))); ! if (proc->result.is_rowtype == 2) ! PLy_output_tuple_funcs(&proc->result, desc); ! Assert(proc->result.is_rowtype == 1); /* Build tuple */ values = palloc(sizeof(Datum) * desc->natts); nulls = palloc(sizeof(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { ! PLyObToDatum *att; ! PyObject *volatile value; ! att = &proc->result.out.r.atts[i]; ! value = NULL; PG_TRY(); { value = PySequence_GetItem(sequence, i); Assert(value); ! values[i] = (att->func) (proc, att, value, &nulls[i]); Py_XDECREF(value); value = NULL; } PG_CATCH(); { Py_XDECREF(value); PG_RE_THROW(); } *************** *** 1933,1939 **** static HeapTuple ! PLyObject_ToTuple(PLyTypeInfo * info, PyObject * object) { TupleDesc desc; HeapTuple tuple; --- 2135,2141 ---- static HeapTuple ! PLyObject_ToTuple(PLyProcedure *proc, PyObject *object) { TupleDesc desc; HeapTuple tuple; *************** *** 1941,1962 **** bool *nulls; volatile 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(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { ! char *key; ! PyObject *volatile value, ! *volatile so; key = NameStr(desc->attrs[i]->attname); ! value = so = NULL; PG_TRY(); { value = PyObject_GetAttrString(object, key); --- 2143,2165 ---- bool *nulls; volatile int i; ! desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, -1); ! if (proc->result.is_rowtype == 2) ! PLy_output_tuple_funcs(&proc->result, desc); ! Assert(proc->result.is_rowtype == 1); /* Build tuple */ values = palloc(sizeof(Datum) * desc->natts); nulls = palloc(sizeof(bool) * desc->natts); for (i = 0; i < desc->natts; ++i) { ! char *key; ! PLyObToDatum *att; ! PyObject *volatile value; + att = &proc->result.out.r.atts[i]; key = NameStr(desc->attrs[i]->attname); ! value = NULL; PG_TRY(); { value = PyObject_GetAttrString(object, key); *************** *** 1965,2000 **** values[i] = (Datum) NULL; nulls[i] = true; } ! 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; - nulls[i] = false; - } - else ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("attribute \"%s\" does not exist in Python object", key), errhint("To return null in a column, " ! "let the returned object have an attribute named " ! "after column with value None."))); Py_XDECREF(value); value = NULL; } PG_CATCH(); { - Py_XDECREF(so); Py_XDECREF(value); PG_RE_THROW(); } --- 2168,2190 ---- values[i] = (Datum) NULL; nulls[i] = true; } ! else if (!value) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("key \"%s\" not found in object", key), errhint("To return null in a column, " ! "add the value None to the mapping with the " ! "key named after the column."))); ! } ! else ! values[i] = (att->func) (proc, att, value, &nulls[i]); Py_XDECREF(value); value = NULL; } PG_CATCH(); { Py_XDECREF(value); PG_RE_THROW(); } Index: expected/plpython_function.out =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_function.out,v retrieving revision 1.12 diff -c -r1.12 plpython_function.out *** expected/plpython_function.out 3 Apr 2009 16:59:42 -0000 1.12 --- expected/plpython_function.out 26 May 2009 22:58:52 -0000 *************** *** 450,452 **** --- 450,470 ---- CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; + CREATE FUNCTION test_type_conversion_bool(x bool) returns bool AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_char(x char) returns char AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_int2(x int2) returns int2 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_int4(x int4) returns int4 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_int8(x int8) returns int8 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_float4(x float4) returns float4 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_float8(x float8) returns float8 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_numeric(x numeric) returns numeric AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_text(x text) returns text AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_bytea(x bytea) returns bytea AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_marshal() returns bytea AS $$ + import marshal + return marshal.dumps('hello world') + $$ language plpythonu; + CREATE FUNCTION test_type_unmarshal(x bytea) returns text AS $$ + import marshal + return marshal.loads(x) + $$ language plpythonu; Index: expected/plpython_test.out =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/expected/plpython_test.out,v retrieving revision 1.8 diff -c -r1.8 plpython_test.out *** expected/plpython_test.out 3 Apr 2009 16:59:42 -0000 1.8 --- expected/plpython_test.out 26 May 2009 22:58:52 -0000 *************** *** 559,561 **** --- 559,693 ---- test_in_inout (1 row) + SELECT * FROM test_type_conversion_bool(true); + test_type_conversion_bool + --------------------------- + t + (1 row) + + SELECT * FROM test_type_conversion_bool(false); + test_type_conversion_bool + --------------------------- + f + (1 row) + + SELECT * FROM test_type_conversion_bool(null); + test_type_conversion_bool + --------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_char('a'); + test_type_conversion_char + --------------------------- + a + (1 row) + + SELECT * FROM test_type_conversion_char(null); + test_type_conversion_char + --------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_int2(100::int2); + test_type_conversion_int2 + --------------------------- + 100 + (1 row) + + SELECT * FROM test_type_conversion_int2(null); + test_type_conversion_int2 + --------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_int4(100); + test_type_conversion_int4 + --------------------------- + 100 + (1 row) + + SELECT * FROM test_type_conversion_int4(null); + test_type_conversion_int4 + --------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_int8(100); + test_type_conversion_int8 + --------------------------- + 100 + (1 row) + + SELECT * FROM test_type_conversion_int8(null); + test_type_conversion_int8 + --------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_float4(100); + test_type_conversion_float4 + ----------------------------- + 100 + (1 row) + + SELECT * FROM test_type_conversion_float4(null); + test_type_conversion_float4 + ----------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_float8(100); + test_type_conversion_float8 + ----------------------------- + 100 + (1 row) + + SELECT * FROM test_type_conversion_float8(null); + test_type_conversion_float8 + ----------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_numeric(100); + test_type_conversion_numeric + ------------------------------ + 100.0 + (1 row) + + SELECT * FROM test_type_conversion_numeric(null); + test_type_conversion_numeric + ------------------------------ + + (1 row) + + SELECT * FROM test_type_conversion_text('hello world'); + test_type_conversion_text + --------------------------- + hello world + (1 row) + + SELECT * FROM test_type_conversion_text(null); + test_type_conversion_text + --------------------------- + + (1 row) + + SELECT * FROM test_type_conversion_bytea('hello world'); + test_type_conversion_bytea + ---------------------------- + hello world + (1 row) + + SELECT * FROM test_type_conversion_bytea(null); + test_type_conversion_bytea + ---------------------------- + + (1 row) + + SELECT test_type_unmarshal(x) FROM test_type_marshal() x; + test_type_unmarshal + --------------------- + hello world + (1 row) + Index: sql/plpython_function.sql =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_function.sql,v retrieving revision 1.12 diff -c -r1.12 plpython_function.sql *** sql/plpython_function.sql 3 Apr 2009 16:59:43 -0000 1.12 --- sql/plpython_function.sql 26 May 2009 22:58:52 -0000 *************** *** 497,499 **** --- 497,518 ---- CREATE FUNCTION test_inout_params(first inout text) AS $$ return first + '_inout'; $$ LANGUAGE plpythonu; + + CREATE FUNCTION test_type_conversion_bool(x bool) returns bool AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_char(x char) returns char AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_int2(x int2) returns int2 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_int4(x int4) returns int4 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_int8(x int8) returns int8 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_float4(x float4) returns float4 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_float8(x float8) returns float8 AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_numeric(x numeric) returns numeric AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_text(x text) returns text AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_conversion_bytea(x bytea) returns bytea AS $$ return x $$ language plpythonu; + CREATE FUNCTION test_type_marshal() returns bytea AS $$ + import marshal + return marshal.dumps('hello world') + $$ language plpythonu; + CREATE FUNCTION test_type_unmarshal(x bytea) returns text AS $$ + import marshal + return marshal.loads(x) + $$ language plpythonu; Index: sql/plpython_test.sql =================================================================== RCS file: /projects/cvsroot/pgsql/src/pl/plpython/sql/plpython_test.sql,v retrieving revision 1.5 diff -c -r1.5 plpython_test.sql *** sql/plpython_test.sql 3 Apr 2009 16:59:43 -0000 1.5 --- sql/plpython_test.sql 26 May 2009 22:58:52 -0000 *************** *** 149,151 **** --- 149,176 ---- -- this doesn't work yet :-( SELECT * FROM test_in_out_params_multi('test_in'); SELECT * FROM test_inout_params('test_in'); + + SELECT * FROM test_type_conversion_bool(true); + SELECT * FROM test_type_conversion_bool(false); + SELECT * FROM test_type_conversion_bool(null); + SELECT * FROM test_type_conversion_char('a'); + SELECT * FROM test_type_conversion_char(null); + SELECT * FROM test_type_conversion_int2(100::int2); + SELECT * FROM test_type_conversion_int2(null); + SELECT * FROM test_type_conversion_int4(100); + SELECT * FROM test_type_conversion_int4(null); + SELECT * FROM test_type_conversion_int8(100); + SELECT * FROM test_type_conversion_int8(null); + SELECT * FROM test_type_conversion_float4(100); + SELECT * FROM test_type_conversion_float4(null); + SELECT * FROM test_type_conversion_float8(100); + SELECT * FROM test_type_conversion_float8(null); + SELECT * FROM test_type_conversion_numeric(100); + SELECT * FROM test_type_conversion_numeric(null); + SELECT * FROM test_type_conversion_text('hello world'); + SELECT * FROM test_type_conversion_text(null); + SELECT * FROM test_type_conversion_bytea('hello world'); + SELECT * FROM test_type_conversion_bytea(null); + SELECT test_type_unmarshal(x) FROM test_type_marshal() x; + +