|
|
|
|
@ -1,7 +1,7 @@ |
|
|
|
|
/**********************************************************************
|
|
|
|
|
* 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 "commands/trigger.h" |
|
|
|
|
#include "executor/spi.h" |
|
|
|
|
#include "funcapi.h" |
|
|
|
|
#include "fmgr.h" |
|
|
|
|
#include "nodes/makefuncs.h" |
|
|
|
|
#include "parser/parse_type.h" |
|
|
|
|
@ -121,6 +122,9 @@ typedef struct PLyProcedure |
|
|
|
|
bool fn_readonly; |
|
|
|
|
PLyTypeInfo result; /* also used to store info for trigger tuple
|
|
|
|
|
* 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]; |
|
|
|
|
int nargs; |
|
|
|
|
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 PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *); |
|
|
|
|
static void PLy_function_delete_args(PLyProcedure *); |
|
|
|
|
static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *, |
|
|
|
|
HeapTuple *); |
|
|
|
|
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 *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 |
|
|
|
|
@ -748,11 +756,17 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) |
|
|
|
|
|
|
|
|
|
PG_TRY(); |
|
|
|
|
{ |
|
|
|
|
plargs = PLy_function_build_args(fcinfo, proc); |
|
|
|
|
plrv = PLy_procedure_call(proc, "args", plargs); |
|
|
|
|
|
|
|
|
|
Assert(plrv != NULL); |
|
|
|
|
Assert(!PLy_error_in_progress); |
|
|
|
|
if (!proc->is_setof || proc->setof == NULL) |
|
|
|
|
{ |
|
|
|
|
/* Simple type returning function or first time for SETOF function */ |
|
|
|
|
plargs = PLy_function_build_args(fcinfo, proc); |
|
|
|
|
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 |
|
|
|
|
@ -763,6 +777,67 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc) |
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH) |
|
|
|
|
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 |
|
|
|
|
* 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) |
|
|
|
|
{ |
|
|
|
|
fcinfo->isnull = true; |
|
|
|
|
rv = InputFunctionCall(&proc->result.out.d.typfunc, |
|
|
|
|
NULL, |
|
|
|
|
proc->result.out.d.typioparam, |
|
|
|
|
-1); |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
fcinfo->isnull = false; |
|
|
|
|
rv = HeapTupleGetDatum(tuple); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
fcinfo->isnull = true; |
|
|
|
|
rv = (Datum) NULL; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
@ -912,10 +1016,10 @@ PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc) |
|
|
|
|
arg = Py_None; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* FIXME -- error check this |
|
|
|
|
*/ |
|
|
|
|
PyList_SetItem(args, i, arg); |
|
|
|
|
if (PyList_SetItem(args, i, arg) == -1 || |
|
|
|
|
(proc->argnames && |
|
|
|
|
PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1)) |
|
|
|
|
PLy_elog(ERROR, "problem setting up arguments for \"%s\"", proc->proname); |
|
|
|
|
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 |
|
|
|
|
*/ |
|
|
|
|
@ -1002,6 +1119,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, |
|
|
|
|
bool isnull; |
|
|
|
|
int i, |
|
|
|
|
rv; |
|
|
|
|
Datum argnames; |
|
|
|
|
Datum *elems; |
|
|
|
|
int nelems; |
|
|
|
|
|
|
|
|
|
procStruct = (Form_pg_proc) GETSTRUCT(procTup); |
|
|
|
|
|
|
|
|
|
@ -1033,6 +1153,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, |
|
|
|
|
proc->nargs = 0; |
|
|
|
|
proc->code = proc->statics = NULL; |
|
|
|
|
proc->globals = proc->me = NULL; |
|
|
|
|
proc->is_setof = procStruct->proretset; |
|
|
|
|
proc->setof = NULL; |
|
|
|
|
proc->argnames = NULL; |
|
|
|
|
|
|
|
|
|
PG_TRY(); |
|
|
|
|
{ |
|
|
|
|
@ -1069,9 +1192,11 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (rvTypeStruct->typtype == 'c') |
|
|
|
|
ereport(ERROR, |
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
|
|
|
|
errmsg("plpython functions cannot return tuples yet"))); |
|
|
|
|
{ |
|
|
|
|
/* Tuple: set up later, during first call to PLy_function_handler */ |
|
|
|
|
proc->result.out.d.typoid = procStruct->prorettype; |
|
|
|
|
proc->result.is_rowtype = 2; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
PLy_output_datum_func(&proc->result, rvTypeTup); |
|
|
|
|
|
|
|
|
|
@ -1094,6 +1219,20 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, |
|
|
|
|
* arguments. |
|
|
|
|
*/ |
|
|
|
|
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++) |
|
|
|
|
{ |
|
|
|
|
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 */ |
|
|
|
|
|
|
|
|
|
ReleaseSysCache(argTypeTup); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Fetch argument name */ |
|
|
|
|
if (proc->argnames) |
|
|
|
|
proc->argnames[i] = PLy_strdup(DatumGetCString(DirectFunctionCall1(textout, elems[i]))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* get the text of the function. |
|
|
|
|
@ -1259,6 +1401,7 @@ PLy_procedure_delete(PLyProcedure * proc) |
|
|
|
|
if (proc->pyname) |
|
|
|
|
PLy_free(proc->pyname); |
|
|
|
|
for (i = 0; i < proc->nargs; i++) |
|
|
|
|
{ |
|
|
|
|
if (proc->args[i].is_rowtype == 1) |
|
|
|
|
{ |
|
|
|
|
if (proc->args[i].in.r.atts) |
|
|
|
|
@ -1266,6 +1409,11 @@ PLy_procedure_delete(PLyProcedure * proc) |
|
|
|
|
if (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
|
|
|
|
|
@ -1524,6 +1672,247 @@ PLyDict_FromTuple(PLyTypeInfo * info, HeapTuple tuple, TupleDesc desc) |
|
|
|
|
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 */ |
|
|
|
|
|
|
|
|
|
/* interface to postgresql elog */ |
|
|
|
|
|