diff options
Diffstat (limited to 'src/pl/plpython/plpython.c')
-rw-r--r-- | src/pl/plpython/plpython.c | 5439 |
1 files changed, 0 insertions, 5439 deletions
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c deleted file mode 100644 index dce8ff247b6..00000000000 --- a/src/pl/plpython/plpython.c +++ /dev/null @@ -1,5439 +0,0 @@ -/********************************************************************** - * plpython.c - python as a procedural language for PostgreSQL - * - * src/pl/plpython/plpython.c - * - ********************************************************************* - */ - -#include "postgres.h" - -/* system stuff */ -#include <unistd.h> -#include <fcntl.h> - -/* postgreSQL stuff */ -#include "catalog/pg_proc.h" -#include "catalog/pg_type.h" -#include "commands/trigger.h" -#include "executor/spi.h" -#include "funcapi.h" -#include "fmgr.h" -#include "mb/pg_wchar.h" -#include "miscadmin.h" -#include "nodes/makefuncs.h" -#include "parser/parse_type.h" -#include "tcop/tcopprot.h" -#include "access/transam.h" -#include "access/xact.h" -#include "utils/builtins.h" -#include "utils/hsearch.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "utils/rel.h" -#include "utils/syscache.h" -#include "utils/typcache.h" - -/* - * Undefine some things that get (re)defined in the - * Python headers. They aren't used below and we've - * already included all the headers we need, so this - * should be pretty safe. - */ - -#undef _POSIX_C_SOURCE -#undef _XOPEN_SOURCE -#undef HAVE_STRERROR -#undef HAVE_TZNAME - -/* - * Sometimes python carefully scribbles on our *printf macros. - * So we undefine them here and redefine them after it's done its dirty deed. - */ - -#ifdef USE_REPL_SNPRINTF -#undef snprintf -#undef vsnprintf -#endif - -#if defined(_MSC_VER) && defined(_DEBUG) -/* Python uses #pragma to bring in a non-default libpython on VC++ if - * _DEBUG is defined */ -#undef _DEBUG -/* Also hide away errcode, since we load Python.h before postgres.h */ -#define errcode __msvc_errcode -#include <Python.h> -#undef errcode -#define _DEBUG -#elif defined (_MSC_VER) -#define errcode __msvc_errcode -#include <Python.h> -#undef errcode -#else -#include <Python.h> -#endif - -/* - * Py_ssize_t compat for Python <= 2.4 - */ -#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) -typedef int Py_ssize_t; - -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif - -/* - * PyBool_FromLong is supported from 2.3. - */ -#if PY_VERSION_HEX < 0x02030000 -#define PyBool_FromLong(x) PyInt_FromLong(x) -#endif - -/* - * Python 2/3 strings/unicode/bytes handling. Python 2 has strings - * and unicode, Python 3 has strings, which are unicode on the C - * level, and bytes. The porting convention, which is similarly used - * in Python 2.6, is that "Unicode" is always unicode, and "Bytes" are - * bytes in Python 3 and strings in Python 2. Since we keep - * supporting Python 2 and its usual strings, we provide a - * compatibility layer for Python 3 that when asked to convert a C - * string to a Python string it converts the C string from the - * PostgreSQL server encoding to a Python Unicode object. - */ - -#if PY_VERSION_HEX < 0x02060000 -/* This is exactly the compatibility layer that Python 2.6 uses. */ -#define PyBytes_AsString PyString_AsString -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_Size PyString_Size -#define PyObject_Bytes PyObject_Str -#endif - -#if PY_MAJOR_VERSION >= 3 -#define PyString_Check(x) 0 -#define PyString_AsString(x) PLyUnicode_AsString(x) -#define PyString_FromString(x) PLyUnicode_FromString(x) -#endif - -/* - * Python 3 only has long. - */ -#if PY_MAJOR_VERSION >= 3 -#define PyInt_FromLong(x) PyLong_FromLong(x) -#define PyInt_AsLong(x) PyLong_AsLong(x) -#endif - -/* - * PyVarObject_HEAD_INIT was added in Python 2.6. Its use is - * necessary to handle both Python 2 and 3. This replacement - * definition is for Python <=2.5 - */ -#ifndef PyVarObject_HEAD_INIT -#define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, -#endif - -/* Python 3 removed the Py_TPFLAGS_HAVE_ITER flag */ -#if PY_MAJOR_VERSION >= 3 -#define Py_TPFLAGS_HAVE_ITER 0 -#endif - -/* define our text domain for translations */ -#undef TEXTDOMAIN -#define TEXTDOMAIN PG_TEXTDOMAIN("plpython") - -#include <compile.h> -#include <eval.h> - -/* put back our snprintf and vsnprintf */ -#ifdef USE_REPL_SNPRINTF -#ifdef snprintf -#undef snprintf -#endif -#ifdef vsnprintf -#undef vsnprintf -#endif -#ifdef __GNUC__ -#define vsnprintf(...) pg_vsnprintf(__VA_ARGS__) -#define snprintf(...) pg_snprintf(__VA_ARGS__) -#else -#define vsnprintf pg_vsnprintf -#define snprintf pg_snprintf -#endif /* __GNUC__ */ -#endif /* USE_REPL_SNPRINTF */ - -PG_MODULE_MAGIC; - -/* convert Postgresql Datum or tuple into a PyObject. - * input to Python. Tuples are converted to dictionary - * objects. - */ - -struct PLyDatumToOb; -typedef PyObject *(*PLyDatumToObFunc) (struct PLyDatumToOb *, Datum); - -typedef struct PLyDatumToOb -{ - PLyDatumToObFunc func; - FmgrInfo typfunc; /* The type's output function */ - Oid typoid; /* The OID of the type */ - int32 typmod; /* The typmod of the type */ - Oid typioparam; - bool typbyval; - int16 typlen; - char typalign; - struct PLyDatumToOb *elm; -} PLyDatumToOb; - -typedef struct PLyTupleToOb -{ - PLyDatumToOb *atts; - int natts; -} PLyTupleToOb; - -typedef union PLyTypeInput -{ - PLyDatumToOb d; - PLyTupleToOb r; -} PLyTypeInput; - -/* convert PyObject to a Postgresql Datum or tuple. - * output from Python - */ - -struct PLyObToDatum; -typedef Datum (*PLyObToDatumFunc) (struct PLyObToDatum *, int32 typmod, - PyObject *); - -typedef struct PLyObToDatum -{ - PLyObToDatumFunc func; - FmgrInfo typfunc; /* The type's input function */ - Oid typoid; /* The OID of the type */ - int32 typmod; /* The typmod of the type */ - Oid typioparam; - bool typbyval; - int16 typlen; - char typalign; - struct PLyObToDatum *elm; -} PLyObToDatum; - -typedef struct PLyObToTuple -{ - PLyObToDatum *atts; - int natts; -} PLyObToTuple; - -typedef union PLyTypeOutput -{ - PLyObToDatum d; - PLyObToTuple r; -} PLyTypeOutput; - -/* all we need to move Postgresql data to Python objects, - * and vice versa - */ -typedef struct PLyTypeInfo -{ - PLyTypeInput in; - PLyTypeOutput out; - - /* - * 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; - /* used to check if the type has been modified */ - Oid typ_relid; - TransactionId typrel_xmin; - ItemPointerData typrel_tid; -} PLyTypeInfo; - - -/* cached procedure data */ -typedef struct PLyProcedure -{ - char *proname; /* SQL name of procedure */ - char *pyname; /* Python name of procedure */ - TransactionId fn_xmin; - ItemPointerData fn_tid; - 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 *src; /* textual procedure code, after mangling */ - char **argnames; /* Argument names */ - PLyTypeInfo args[FUNC_MAX_ARGS]; - int nargs; - PyObject *code; /* compiled procedure code */ - PyObject *statics; /* data saved across calls, local scope */ - PyObject *globals; /* data saved across calls, global scope */ -} PLyProcedure; - - -/* the procedure cache entry */ -typedef struct PLyProcedureEntry -{ - Oid fn_oid; /* hash key */ - PLyProcedure *proc; -} PLyProcedureEntry; - -/* explicit subtransaction data */ -typedef struct PLySubtransactionData -{ - MemoryContext oldcontext; - ResourceOwner oldowner; -} PLySubtransactionData; - - -/* Python objects */ -typedef struct PLyPlanObject -{ - PyObject_HEAD - SPIPlanPtr plan; - int nargs; - Oid *types; - Datum *values; - PLyTypeInfo *args; -} PLyPlanObject; - -typedef struct PLyResultObject -{ - PyObject_HEAD - /* HeapTuple *tuples; */ - PyObject *nrows; /* number of rows returned by query */ - PyObject *rows; /* data rows, or None if no data returned */ - PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */ -} PLyResultObject; - -typedef struct PLySubtransactionObject -{ - PyObject_HEAD - bool started; - bool exited; -} PLySubtransactionObject; - -typedef struct PLyCursorObject -{ - PyObject_HEAD - char *portalname; - PLyTypeInfo result; - bool closed; -} PLyCursorObject; - -/* A list of all known exceptions, generated from backend/utils/errcodes.txt */ -typedef struct ExceptionMap -{ - char *name; - char *classname; - int sqlstate; -} ExceptionMap; - -static const ExceptionMap exception_map[] = { -#include "spiexceptions.h" - {NULL, NULL, 0} -}; - -/* A hash table mapping sqlstates to exceptions, for speedy lookup */ -static HTAB *PLy_spi_exceptions; - -typedef struct PLyExceptionEntry -{ - int sqlstate; /* hash key, must be first */ - PyObject *exc; /* corresponding exception */ -} PLyExceptionEntry; - - -/* function declarations */ - -#if PY_MAJOR_VERSION >= 3 -/* Use separate names to avoid clash in pg_pltemplate */ -#define plpython_validator plpython3_validator -#define plpython_call_handler plpython3_call_handler -#define plpython_inline_handler plpython3_inline_handler -#endif - -/* exported functions */ -Datum plpython_validator(PG_FUNCTION_ARGS); -Datum plpython_call_handler(PG_FUNCTION_ARGS); -Datum plpython_inline_handler(PG_FUNCTION_ARGS); -void _PG_init(void); - -PG_FUNCTION_INFO_V1(plpython_validator); -PG_FUNCTION_INFO_V1(plpython_call_handler); -PG_FUNCTION_INFO_V1(plpython_inline_handler); - -/* most of the remaining of the declarations, all static */ - -/* - * These should only be called once from _PG_init. Initialize the - * Python interpreter and global data. - */ -static void PLy_init_interp(void); -static void PLy_init_plpy(void); - -/* call PyErr_SetString with a vprint interface and translation support */ -static void -PLy_exception_set(PyObject *, const char *,...) -__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); - -/* same, with pluralized message */ -static void -PLy_exception_set_plural(PyObject *, const char *, const char *, - unsigned long n,...) -__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 5))) -__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 5))); - -/* like PLy_exception_set, but conserve more fields from ErrorData */ -static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata); - -/* Get the innermost python procedure called from the backend */ -static char *PLy_procedure_name(PLyProcedure *); - -/* some utility functions */ -static void -PLy_elog(int, const char *,...) -__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); -static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, char **query, int *position); -static void PLy_traceback(char **, char **, int *); - -static void *PLy_malloc(size_t); -static void *PLy_malloc0(size_t); -static char *PLy_strdup(const char *); -static void PLy_free(void *); - -static PyObject *PLyUnicode_Bytes(PyObject *unicode); -static char *PLyUnicode_AsString(PyObject *unicode); - -#if PY_MAJOR_VERSION >= 3 -static PyObject *PLyUnicode_FromString(const char *s); -#endif - -/* sub handlers for functions and triggers */ -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 *, - TriggerData *, HeapTuple); - -static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *); - -static PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger); - -static PLyProcedure *PLy_procedure_create(HeapTuple procTup, - Oid fn_oid, bool is_trigger); - -static void PLy_procedure_compile(PLyProcedure *, const char *); -static char *PLy_procedure_munge_source(const char *, const char *); -static void PLy_procedure_delete(PLyProcedure *); - -static void PLy_typeinfo_init(PLyTypeInfo *); -static void PLy_typeinfo_dealloc(PLyTypeInfo *); -static void PLy_output_datum_func(PLyTypeInfo *, HeapTuple); -static void PLy_output_datum_func2(PLyObToDatum *, HeapTuple); -static void PLy_input_datum_func(PLyTypeInfo *, Oid, HeapTuple); -static void PLy_input_datum_func2(PLyDatumToOb *, Oid, HeapTuple); -static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc); -static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc); -static void PLy_output_record_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 *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d); -static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d); -static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); - -static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); - -static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *); -static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *); -static Datum PLyObject_ToComposite(PLyObToDatum *, int32, PyObject *); -static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *); -static Datum PLySequence_ToArray(PLyObToDatum *, int32, PyObject *); - -static HeapTuple PLyObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); -static HeapTuple PLyMapping_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); -static HeapTuple PLySequence_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); -static HeapTuple PLyGenericObject_ToTuple(PLyTypeInfo *, TupleDesc, PyObject *); - -/* - * Currently active plpython function - */ -static PLyProcedure *PLy_curr_procedure = NULL; - -/* list of explicit subtransaction data */ -static List *explicit_subtransactions = NIL; - -static PyObject *PLy_interp_globals = NULL; -static PyObject *PLy_interp_safe_globals = NULL; -static HTAB *PLy_procedure_cache = NULL; -static HTAB *PLy_trigger_cache = NULL; - -/* Python exceptions */ -static PyObject *PLy_exc_error = NULL; -static PyObject *PLy_exc_fatal = NULL; -static PyObject *PLy_exc_spi_error = NULL; - -/* some globals for the python module */ -static char PLy_plan_doc[] = { - "Store a PostgreSQL plan" -}; - -static char PLy_result_doc[] = { - "Results of a PostgreSQL query" -}; - -static char PLy_subtransaction_doc[] = { - "PostgreSQL subtransaction context manager" -}; - -static char PLy_cursor_doc[] = { - "Wrapper around a PostgreSQL cursor" -}; - - -/* - * the function definitions - */ - -/* - * This routine is a crock, and so is everyplace that calls it. The problem - * is that the cached form of plpython functions/queries is allocated permanently - * (mostly via malloc()) and never released until backend exit. Subsidiary - * data structures such as fmgr info records therefore must live forever - * as well. A better implementation would store all this stuff in a per- - * function memory context that could be reclaimed at need. In the meantime, - * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever - * it might allocate, and whatever the eventual function might allocate using - * fn_mcxt, will live forever too. - */ -static void -perm_fmgr_info(Oid functionId, FmgrInfo *finfo) -{ - fmgr_info_cxt(functionId, finfo, TopMemoryContext); -} - -static void -plpython_error_callback(void *arg) -{ - if (PLy_curr_procedure) - errcontext("PL/Python function \"%s\"", - PLy_procedure_name(PLy_curr_procedure)); -} - -static void -plpython_inline_error_callback(void *arg) -{ - errcontext("PL/Python anonymous code block"); -} - -static void -plpython_trigger_error_callback(void *arg) -{ - if (PLy_curr_procedure) - errcontext("while modifying trigger row"); -} - -static void -plpython_return_error_callback(void *arg) -{ - if (PLy_curr_procedure) - errcontext("while creating return value"); -} - -static bool -PLy_procedure_is_trigger(Form_pg_proc procStruct) -{ - return (procStruct->prorettype == TRIGGEROID || - (procStruct->prorettype == OPAQUEOID && - procStruct->pronargs == 0)); -} - -Datum -plpython_validator(PG_FUNCTION_ARGS) -{ - Oid funcoid = PG_GETARG_OID(0); - HeapTuple tuple; - Form_pg_proc procStruct; - bool is_trigger; - - if (!check_function_bodies) - { - PG_RETURN_VOID(); - } - - /* Get the new function's pg_proc entry */ - tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for function %u", funcoid); - procStruct = (Form_pg_proc) GETSTRUCT(tuple); - - is_trigger = PLy_procedure_is_trigger(procStruct); - - ReleaseSysCache(tuple); - - PLy_procedure_get(funcoid, is_trigger); - - PG_RETURN_VOID(); -} - -Datum -plpython_call_handler(PG_FUNCTION_ARGS) -{ - Datum retval; - PLyProcedure *save_curr_proc; - ErrorContextCallback plerrcontext; - - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "SPI_connect failed"); - - save_curr_proc = PLy_curr_procedure; - - /* - * Setup error traceback support for ereport() - */ - plerrcontext.callback = plpython_error_callback; - plerrcontext.previous = error_context_stack; - error_context_stack = &plerrcontext; - - PG_TRY(); - { - PLyProcedure *proc; - - if (CALLED_AS_TRIGGER(fcinfo)) - { - HeapTuple trv; - - proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true); - PLy_curr_procedure = proc; - trv = PLy_trigger_handler(fcinfo, proc); - retval = PointerGetDatum(trv); - } - else - { - proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false); - PLy_curr_procedure = proc; - retval = PLy_function_handler(fcinfo, proc); - } - } - PG_CATCH(); - { - PLy_curr_procedure = save_curr_proc; - PyErr_Clear(); - PG_RE_THROW(); - } - PG_END_TRY(); - - /* Pop the error context stack */ - error_context_stack = plerrcontext.previous; - - PLy_curr_procedure = save_curr_proc; - - return retval; -} - -Datum -plpython_inline_handler(PG_FUNCTION_ARGS) -{ - InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); - FunctionCallInfoData fake_fcinfo; - FmgrInfo flinfo; - PLyProcedure *save_curr_proc; - PLyProcedure proc; - ErrorContextCallback plerrcontext; - - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "SPI_connect failed"); - - save_curr_proc = PLy_curr_procedure; - - /* - * Setup error traceback support for ereport() - */ - plerrcontext.callback = plpython_inline_error_callback; - plerrcontext.previous = error_context_stack; - error_context_stack = &plerrcontext; - - MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); - MemSet(&flinfo, 0, sizeof(flinfo)); - fake_fcinfo.flinfo = &flinfo; - flinfo.fn_oid = InvalidOid; - flinfo.fn_mcxt = CurrentMemoryContext; - - MemSet(&proc, 0, sizeof(PLyProcedure)); - proc.pyname = PLy_strdup("__plpython_inline_block"); - proc.result.out.d.typoid = VOIDOID; - - PG_TRY(); - { - PLy_procedure_compile(&proc, codeblock->source_text); - PLy_curr_procedure = &proc; - PLy_function_handler(&fake_fcinfo, &proc); - } - PG_CATCH(); - { - PLy_procedure_delete(&proc); - PLy_curr_procedure = save_curr_proc; - PyErr_Clear(); - PG_RE_THROW(); - } - PG_END_TRY(); - - PLy_procedure_delete(&proc); - - /* Pop the error context stack */ - error_context_stack = plerrcontext.previous; - - PLy_curr_procedure = save_curr_proc; - - PG_RETURN_VOID(); -} - -/* trigger and function sub handlers - * - * the python function is expected to return Py_None if the tuple is - * acceptable and unmodified. Otherwise it should return a PyString - * object who's value is SKIP, or MODIFY. SKIP means don't perform - * this action. MODIFY means the tuple has been modified, so update - * tuple and perform action. SKIP and MODIFY assume the trigger fires - * BEFORE the event and is ROW level. postgres expects the function - * to take no arguments and return an argument of type trigger. - */ -static HeapTuple -PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *proc) -{ - HeapTuple rv = NULL; - PyObject *volatile plargs = NULL; - PyObject *volatile plrv = NULL; - TriggerData *tdata; - - Assert(CALLED_AS_TRIGGER(fcinfo)); - - /* - * Input/output conversion for trigger tuples. Use the result TypeInfo - * variable to store the tuple conversion info. We do this over again on - * each call to cover the possibility that the relation's tupdesc changed - * since the trigger was last called. PLy_input_tuple_funcs and - * PLy_output_tuple_funcs are responsible for not doing repetitive work. - */ - tdata = (TriggerData *) fcinfo->context; - - PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); - PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att); - - PG_TRY(); - { - plargs = PLy_trigger_build_args(fcinfo, proc, &rv); - plrv = PLy_procedure_call(proc, "TD", plargs); - - Assert(plrv != NULL); - - /* - * Disconnect from SPI manager - */ - if (SPI_finish() != SPI_OK_FINISH) - elog(ERROR, "SPI_finish failed"); - - /* - * return of None means we're happy with the tuple - */ - if (plrv != Py_None) - { - char *srv; - - if (PyString_Check(plrv)) - srv = PyString_AsString(plrv); - else if (PyUnicode_Check(plrv)) - srv = PLyUnicode_AsString(plrv); - else - { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("unexpected return value from trigger procedure"), - errdetail("Expected None or a string."))); - srv = NULL; /* keep compiler quiet */ - } - - if (pg_strcasecmp(srv, "SKIP") == 0) - rv = NULL; - else if (pg_strcasecmp(srv, "MODIFY") == 0) - { - TriggerData *tdata = (TriggerData *) fcinfo->context; - - if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) || - TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) - rv = PLy_modify_tuple(proc, plargs, tdata, rv); - else - ereport(WARNING, - (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); - } - else if (pg_strcasecmp(srv, "OK") != 0) - { - /* - * accept "OK" as an alternative to None; otherwise, raise an - * error - */ - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("unexpected return value from trigger procedure"), - errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); - } - } - } - PG_CATCH(); - { - Py_XDECREF(plargs); - Py_XDECREF(plrv); - - PG_RE_THROW(); - } - PG_END_TRY(); - - Py_DECREF(plargs); - Py_DECREF(plrv); - - return rv; -} - -static HeapTuple -PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, - HeapTuple otup) -{ - PyObject *volatile plntup; - PyObject *volatile plkeys; - PyObject *volatile platt; - PyObject *volatile plval; - PyObject *volatile plstr; - HeapTuple rtup; - int natts, - i, - attn, - atti; - int *volatile modattrs; - Datum *volatile modvalues; - char *volatile modnulls; - TupleDesc tupdesc; - ErrorContextCallback plerrcontext; - - plerrcontext.callback = plpython_trigger_error_callback; - plerrcontext.previous = error_context_stack; - error_context_stack = &plerrcontext; - - plntup = plkeys = platt = plval = plstr = NULL; - modattrs = NULL; - modvalues = NULL; - modnulls = NULL; - - PG_TRY(); - { - if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) - ereport(ERROR, - (errmsg("TD[\"new\"] deleted, cannot modify row"))); - if (!PyDict_Check(plntup)) - ereport(ERROR, - (errmsg("TD[\"new\"] is not a dictionary"))); - Py_INCREF(plntup); - - plkeys = PyDict_Keys(plntup); - natts = PyList_Size(plkeys); - - modattrs = (int *) palloc(natts * sizeof(int)); - modvalues = (Datum *) palloc(natts * sizeof(Datum)); - modnulls = (char *) palloc(natts * sizeof(char)); - - tupdesc = tdata->tg_relation->rd_att; - - for (i = 0; i < natts; i++) - { - char *plattstr; - - platt = PyList_GetItem(plkeys, i); - if (PyString_Check(platt)) - plattstr = PyString_AsString(platt); - else if (PyUnicode_Check(platt)) - plattstr = PLyUnicode_AsString(platt); - else - { - ereport(ERROR, - (errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i))); - plattstr = NULL; /* keep compiler quiet */ - } - attn = SPI_fnumber(tupdesc, plattstr); - if (attn == SPI_ERROR_NOATTRIBUTE) - ereport(ERROR, - (errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row", - plattstr))); - atti = attn - 1; - - plval = PyDict_GetItem(plntup, platt); - if (plval == NULL) - elog(FATAL, "Python interpreter is probably corrupted"); - - Py_INCREF(plval); - - modattrs[i] = attn; - - if (tupdesc->attrs[atti]->attisdropped) - { - modvalues[i] = (Datum) 0; - modnulls[i] = 'n'; - } - else if (plval != Py_None) - { - PLyObToDatum *att = &proc->result.out.r.atts[atti]; - - modvalues[i] = (att->func) (att, - tupdesc->attrs[atti]->atttypmod, - plval); - modnulls[i] = ' '; - } - 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); - plval = NULL; - } - - rtup = SPI_modifytuple(tdata->tg_relation, otup, natts, - modattrs, modvalues, modnulls); - if (rtup == NULL) - elog(ERROR, "SPI_modifytuple failed: error %d", SPI_result); - } - PG_CATCH(); - { - Py_XDECREF(plntup); - Py_XDECREF(plkeys); - Py_XDECREF(plval); - Py_XDECREF(plstr); - - if (modnulls) - pfree(modnulls); - if (modvalues) - pfree(modvalues); - if (modattrs) - pfree(modattrs); - - PG_RE_THROW(); - } - PG_END_TRY(); - - Py_DECREF(plntup); - Py_DECREF(plkeys); - - pfree(modattrs); - pfree(modvalues); - pfree(modnulls); - - error_context_stack = plerrcontext.previous; - - return rtup; -} - -static PyObject * -PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv) -{ - TriggerData *tdata = (TriggerData *) fcinfo->context; - PyObject *pltname, - *pltevent, - *pltwhen, - *pltlevel, - *pltrelid, - *plttablename, - *plttableschema; - PyObject *pltargs, - *pytnew, - *pytold; - PyObject *volatile pltdata = NULL; - char *stroid; - - PG_TRY(); - { - pltdata = PyDict_New(); - if (!pltdata) - PLy_elog(ERROR, "could not create new dictionary while building trigger arguments"); - - pltname = PyString_FromString(tdata->tg_trigger->tgname); - PyDict_SetItemString(pltdata, "name", pltname); - Py_DECREF(pltname); - - stroid = DatumGetCString(DirectFunctionCall1(oidout, - ObjectIdGetDatum(tdata->tg_relation->rd_id))); - pltrelid = PyString_FromString(stroid); - PyDict_SetItemString(pltdata, "relid", pltrelid); - Py_DECREF(pltrelid); - pfree(stroid); - - stroid = SPI_getrelname(tdata->tg_relation); - plttablename = PyString_FromString(stroid); - PyDict_SetItemString(pltdata, "table_name", plttablename); - Py_DECREF(plttablename); - pfree(stroid); - - stroid = SPI_getnspname(tdata->tg_relation); - plttableschema = PyString_FromString(stroid); - PyDict_SetItemString(pltdata, "table_schema", plttableschema); - Py_DECREF(plttableschema); - pfree(stroid); - - if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) - pltwhen = PyString_FromString("BEFORE"); - else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) - pltwhen = PyString_FromString("AFTER"); - else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event)) - pltwhen = PyString_FromString("INSTEAD OF"); - else - { - elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event); - pltwhen = NULL; /* keep compiler quiet */ - } - PyDict_SetItemString(pltdata, "when", pltwhen); - Py_DECREF(pltwhen); - - if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) - { - pltlevel = PyString_FromString("ROW"); - PyDict_SetItemString(pltdata, "level", pltlevel); - Py_DECREF(pltlevel); - - if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) - { - pltevent = PyString_FromString("INSERT"); - - PyDict_SetItemString(pltdata, "old", Py_None); - pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, - tdata->tg_relation->rd_att); - PyDict_SetItemString(pltdata, "new", pytnew); - Py_DECREF(pytnew); - *rv = tdata->tg_trigtuple; - } - else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) - { - pltevent = PyString_FromString("DELETE"); - - PyDict_SetItemString(pltdata, "new", Py_None); - pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, - tdata->tg_relation->rd_att); - PyDict_SetItemString(pltdata, "old", pytold); - Py_DECREF(pytold); - *rv = tdata->tg_trigtuple; - } - else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) - { - pltevent = PyString_FromString("UPDATE"); - - pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, - tdata->tg_relation->rd_att); - PyDict_SetItemString(pltdata, "new", pytnew); - Py_DECREF(pytnew); - pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, - tdata->tg_relation->rd_att); - PyDict_SetItemString(pltdata, "old", pytold); - Py_DECREF(pytold); - *rv = tdata->tg_newtuple; - } - else - { - elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); - pltevent = NULL; /* keep compiler quiet */ - } - - PyDict_SetItemString(pltdata, "event", pltevent); - Py_DECREF(pltevent); - } - else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) - { - pltlevel = PyString_FromString("STATEMENT"); - PyDict_SetItemString(pltdata, "level", pltlevel); - Py_DECREF(pltlevel); - - PyDict_SetItemString(pltdata, "old", Py_None); - PyDict_SetItemString(pltdata, "new", Py_None); - *rv = NULL; - - if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) - pltevent = PyString_FromString("INSERT"); - else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) - pltevent = PyString_FromString("DELETE"); - else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) - pltevent = PyString_FromString("UPDATE"); - else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) - pltevent = PyString_FromString("TRUNCATE"); - else - { - elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); - pltevent = NULL; /* keep compiler quiet */ - } - - PyDict_SetItemString(pltdata, "event", pltevent); - Py_DECREF(pltevent); - } - else - elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); - - if (tdata->tg_trigger->tgnargs) - { - /* - * all strings... - */ - int i; - PyObject *pltarg; - - pltargs = PyList_New(tdata->tg_trigger->tgnargs); - for (i = 0; i < tdata->tg_trigger->tgnargs; i++) - { - pltarg = PyString_FromString(tdata->tg_trigger->tgargs[i]); - - /* - * stolen, don't Py_DECREF - */ - PyList_SetItem(pltargs, i, pltarg); - } - } - else - { - Py_INCREF(Py_None); - pltargs = Py_None; - } - PyDict_SetItemString(pltdata, "args", pltargs); - Py_DECREF(pltargs); - } - PG_CATCH(); - { - Py_XDECREF(pltdata); - PG_RE_THROW(); - } - PG_END_TRY(); - - return pltdata; -} - - - -/* function handler and friends */ -static Datum -PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *proc) -{ - Datum rv; - PyObject *volatile plargs = NULL; - PyObject *volatile plrv = NULL; - ErrorContextCallback plerrcontext; - - PG_TRY(); - { - if (!proc->is_setof || proc->setof == NULL) - { - /* - * Simple type returning function or first time for SETOF - * function: actually execute the 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); - } - - /* - * If it returns a set, call the iterator to get the next return item. - * We stay in the SPI context while doing this, because PyIter_Next() - * calls back into Python code which might contain SPI calls. - */ - 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("unsupported set function return mode"), - errdetail("PL/Python set-returning functions only support returning only value per call."))); - } - 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 cannot be iterated"), - errdetail("PL/Python set-returning functions must return an 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); - - PLy_function_delete_args(proc); - - if (has_error) - PLy_elog(ERROR, "error fetching next item from iterator"); - - /* Disconnect from the SPI manager before returning */ - if (SPI_finish() != SPI_OK_FINISH) - elog(ERROR, "SPI_finish failed"); - - fcinfo->isnull = true; - return (Datum) NULL; - } - } - - /* - * Disconnect from SPI manager and then create the return values datum - * (if the input function does a palloc for it this must not be - * allocated in the SPI memory context because SPI_finish would free - * it). - */ - if (SPI_finish() != SPI_OK_FINISH) - elog(ERROR, "SPI_finish failed"); - - plerrcontext.callback = plpython_return_error_callback; - plerrcontext.previous = error_context_stack; - error_context_stack = &plerrcontext; - - /* - * 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) - { - TupleDesc desc; - HeapTuple tuple = NULL; - - /* make sure it's not an unnamed record */ - Assert((proc->result.out.d.typoid == RECORDOID && - proc->result.out.d.typmod != -1) || - (proc->result.out.d.typoid != RECORDOID && - proc->result.out.d.typmod == -1)); - - desc = lookup_rowtype_tupdesc(proc->result.out.d.typoid, - proc->result.out.d.typmod); - - tuple = PLyObject_ToTuple(&proc->result, desc, plrv); - - if (tuple != NULL) - { - fcinfo->isnull = false; - rv = HeapTupleGetDatum(tuple); - } - else - { - fcinfo->isnull = true; - rv = (Datum) NULL; - } - } - else - { - fcinfo->isnull = false; - rv = (proc->result.out.d.func) (&proc->result.out.d, -1, plrv); - } - } - PG_CATCH(); - { - Py_XDECREF(plargs); - Py_XDECREF(plrv); - - /* - * If there was an error the iterator might have not been exhausted - * yet. Set it to NULL so the next invocation of the function will - * start the iteration again. - */ - Py_XDECREF(proc->setof); - proc->setof = NULL; - - PG_RE_THROW(); - } - PG_END_TRY(); - - error_context_stack = plerrcontext.previous; - - Py_XDECREF(plargs); - Py_DECREF(plrv); - - return rv; -} - -/* - * Abort lingering subtransactions that have been explicitly started - * by plpy.subtransaction().start() and not properly closed. - */ -static void -PLy_abort_open_subtransactions(int save_subxact_level) -{ - Assert(save_subxact_level >= 0); - - while (list_length(explicit_subtransactions) > save_subxact_level) - { - PLySubtransactionData *subtransactiondata; - - Assert(explicit_subtransactions != NIL); - - ereport(WARNING, - (errmsg("forcibly aborting a subtransaction that has not been exited"))); - - RollbackAndReleaseCurrentSubTransaction(); - - SPI_restore_connection(); - - subtransactiondata = (PLySubtransactionData *) linitial(explicit_subtransactions); - explicit_subtransactions = list_delete_first(explicit_subtransactions); - - MemoryContextSwitchTo(subtransactiondata->oldcontext); - CurrentResourceOwner = subtransactiondata->oldowner; - PLy_free(subtransactiondata); - } -} - -static PyObject * -PLy_procedure_call(PLyProcedure *proc, char *kargs, PyObject *vargs) -{ - PyObject *rv; - int volatile save_subxact_level = list_length(explicit_subtransactions); - - PyDict_SetItemString(proc->globals, kargs, vargs); - - PG_TRY(); - { -#if PY_VERSION_HEX >= 0x03020000 - rv = PyEval_EvalCode(proc->code, - proc->globals, proc->globals); -#else - rv = PyEval_EvalCode((PyCodeObject *) proc->code, - proc->globals, proc->globals); -#endif - - /* - * Since plpy will only let you close subtransactions that you - * started, you cannot *unnest* subtransactions, only *nest* them - * without closing. - */ - Assert(list_length(explicit_subtransactions) >= save_subxact_level); - } - PG_CATCH(); - { - PLy_abort_open_subtransactions(save_subxact_level); - PG_RE_THROW(); - } - PG_END_TRY(); - - PLy_abort_open_subtransactions(save_subxact_level); - - /* If the Python code returned an error, propagate it */ - if (rv == NULL) - PLy_elog(ERROR, NULL); - - return rv; -} - -static PyObject * -PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) -{ - PyObject *volatile arg = NULL; - PyObject *volatile args = NULL; - int i; - - PG_TRY(); - { - args = PyList_New(proc->nargs); - for (i = 0; i < proc->nargs; i++) - { - if (proc->args[i].is_rowtype > 0) - { - if (fcinfo->argnull[i]) - arg = NULL; - else - { - HeapTupleHeader td; - Oid tupType; - int32 tupTypmod; - TupleDesc tupdesc; - HeapTupleData tmptup; - - td = DatumGetHeapTupleHeader(fcinfo->arg[i]); - /* Extract rowtype info and find a tupdesc */ - tupType = HeapTupleHeaderGetTypeId(td); - tupTypmod = HeapTupleHeaderGetTypMod(td); - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - - /* Set up I/O funcs if not done yet */ - if (proc->args[i].is_rowtype != 1) - PLy_input_tuple_funcs(&(proc->args[i]), tupdesc); - - /* Build a temporary HeapTuple control structure */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(td); - tmptup.t_data = td; - - arg = PLyDict_FromTuple(&(proc->args[i]), &tmptup, tupdesc); - ReleaseTupleDesc(tupdesc); - } - } - else - { - if (fcinfo->argnull[i]) - arg = NULL; - else - { - arg = (proc->args[i].in.d.func) (&(proc->args[i].in.d), - fcinfo->arg[i]); - } - } - - if (arg == NULL) - { - Py_INCREF(Py_None); - arg = Py_None; - } - - if (PyList_SetItem(args, i, arg) == -1) - PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments"); - - if (proc->argnames && proc->argnames[i] && - PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1) - PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments"); - arg = NULL; - } - - /* Set up output conversion for functions returning RECORD */ - if (proc->result.out.d.typoid == RECORDOID) - { - TupleDesc desc; - - if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"))); - - /* cache the output conversion functions */ - PLy_output_record_funcs(&(proc->result), desc); - } - } - PG_CATCH(); - { - Py_XDECREF(arg); - Py_XDECREF(args); - - PG_RE_THROW(); - } - PG_END_TRY(); - - return args; -} - - -static void -PLy_function_delete_args(PLyProcedure *proc) -{ - int i; - - if (!proc->argnames) - return; - - for (i = 0; i < proc->nargs; i++) - if (proc->argnames[i]) - PyDict_DelItemString(proc->globals, proc->argnames[i]); -} - -/* - * Check if our cached information about a datatype is still valid - */ -static bool -PLy_procedure_argument_valid(PLyTypeInfo *arg) -{ - HeapTuple relTup; - bool valid; - - /* Nothing to cache unless type is composite */ - if (arg->is_rowtype != 1) - return true; - - /* - * Zero typ_relid means that we got called on an output argument of a - * function returning a unnamed record type; the info for it can't change. - */ - if (!OidIsValid(arg->typ_relid)) - return true; - - /* Else we should have some cached data */ - Assert(TransactionIdIsValid(arg->typrel_xmin)); - Assert(ItemPointerIsValid(&arg->typrel_tid)); - - /* Get the pg_class tuple for the data type */ - relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); - if (!HeapTupleIsValid(relTup)) - elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); - - /* If it has changed, the cached data is not valid */ - valid = (arg->typrel_xmin == HeapTupleHeaderGetXmin(relTup->t_data) && - ItemPointerEquals(&arg->typrel_tid, &relTup->t_self)); - - ReleaseSysCache(relTup); - - return valid; -} - -/* - * Decide whether a cached PLyProcedure struct is still valid - */ -static bool -PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup) -{ - int i; - bool valid; - - Assert(proc != NULL); - - /* If the pg_proc tuple has changed, it's not valid */ - if (!(proc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && - ItemPointerEquals(&proc->fn_tid, &procTup->t_self))) - return false; - - /* Else check the input argument datatypes */ - valid = true; - for (i = 0; i < proc->nargs; i++) - { - valid = PLy_procedure_argument_valid(&proc->args[i]); - - /* Short-circuit on first changed argument */ - if (!valid) - break; - } - - /* if the output type is composite, it might have changed */ - if (valid) - valid = PLy_procedure_argument_valid(&proc->result); - - return valid; -} - - -/* - * PLyProcedure functions - */ - -/* PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and - * returns a new PLyProcedure. fcinfo is the call info, tgreloid is the - * relation OID when calling a trigger, or InvalidOid (zero) for ordinary - * function calls. - */ -static PLyProcedure * -PLy_procedure_get(Oid fn_oid, bool is_trigger) -{ - HeapTuple procTup; - PLyProcedureEntry *volatile entry; - bool found; - - procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); - if (!HeapTupleIsValid(procTup)) - elog(ERROR, "cache lookup failed for function %u", fn_oid); - - /* Look for the function in the corresponding cache */ - if (is_trigger) - entry = hash_search(PLy_trigger_cache, - &fn_oid, HASH_ENTER, &found); - else - entry = hash_search(PLy_procedure_cache, - &fn_oid, HASH_ENTER, &found); - - PG_TRY(); - { - if (!found) - { - /* Haven't found it, create a new cache entry */ - entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger); - } - else if (!PLy_procedure_valid(entry->proc, procTup)) - { - /* Found it, but it's invalid, free and reuse the cache entry */ - PLy_procedure_delete(entry->proc); - PLy_free(entry->proc); - entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger); - } - /* Found it and it's valid, it's fine to use it */ - } - PG_CATCH(); - { - /* Do not leave an uninitialised entry in the cache */ - if (is_trigger) - hash_search(PLy_trigger_cache, - &fn_oid, HASH_REMOVE, NULL); - else - hash_search(PLy_procedure_cache, - &fn_oid, HASH_REMOVE, NULL); - PG_RE_THROW(); - } - PG_END_TRY(); - - ReleaseSysCache(procTup); - - return entry->proc; -} - -/* - * Create a new PLyProcedure structure - */ -static PLyProcedure * -PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) -{ - char procName[NAMEDATALEN + 256]; - Form_pg_proc procStruct; - PLyProcedure *volatile proc; - char *volatile procSource = NULL; - Datum prosrcdatum; - bool isnull; - int i, - rv; - - procStruct = (Form_pg_proc) GETSTRUCT(procTup); - rv = snprintf(procName, sizeof(procName), - "__plpython_procedure_%s_%u", - NameStr(procStruct->proname), - fn_oid); - if (rv >= sizeof(procName) || rv < 0) - elog(ERROR, "procedure name would overrun buffer"); - - proc = PLy_malloc(sizeof(PLyProcedure)); - proc->proname = PLy_strdup(NameStr(procStruct->proname)); - proc->pyname = PLy_strdup(procName); - proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); - proc->fn_tid = procTup->t_self; - /* Remember if function is STABLE/IMMUTABLE */ - proc->fn_readonly = - (procStruct->provolatile != PROVOLATILE_VOLATILE); - PLy_typeinfo_init(&proc->result); - for (i = 0; i < FUNC_MAX_ARGS; i++) - PLy_typeinfo_init(&proc->args[i]); - proc->nargs = 0; - proc->code = proc->statics = NULL; - proc->globals = NULL; - proc->is_setof = procStruct->proretset; - proc->setof = NULL; - proc->src = NULL; - proc->argnames = NULL; - - PG_TRY(); - { - /* - * get information required for output conversion of the return value, - * but only if this isn't a trigger. - */ - if (!is_trigger) - { - HeapTuple rvTypeTup; - Form_pg_type rvTypeStruct; - - rvTypeTup = SearchSysCache1(TYPEOID, - ObjectIdGetDatum(procStruct->prorettype)); - if (!HeapTupleIsValid(rvTypeTup)) - elog(ERROR, "cache lookup failed for type %u", - procStruct->prorettype); - rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); - - /* Disallow pseudotype result, except for void or record */ - if (rvTypeStruct->typtype == TYPTYPE_PSEUDO) - { - if (procStruct->prorettype == TRIGGEROID) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("trigger functions can only be called as triggers"))); - else if (procStruct->prorettype != VOIDOID && - procStruct->prorettype != RECORDOID) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("PL/Python functions cannot return type %s", - format_type_be(procStruct->prorettype)))); - } - - if (rvTypeStruct->typtype == TYPTYPE_COMPOSITE || - procStruct->prorettype == RECORDOID) - { - /* - * Tuple: set up later, during first call to - * PLy_function_handler - */ - proc->result.out.d.typoid = procStruct->prorettype; - proc->result.out.d.typmod = -1; - proc->result.is_rowtype = 2; - } - else - { - /* do the real work */ - PLy_output_datum_func(&proc->result, rvTypeTup); - } - - ReleaseSysCache(rvTypeTup); - } - - /* - * Now get information required for input conversion of the - * procedure's arguments. Note that we ignore output arguments here. - * If the function returns record, those I/O functions will be set up - * when the function is first called. - */ - if (procStruct->pronargs) - { - Oid *types; - char **names, - *modes; - int i, - pos, - total; - - /* extract argument type info from the pg_proc tuple */ - total = get_func_arg_info(procTup, &types, &names, &modes); - - /* count number of in+inout args into proc->nargs */ - if (modes == NULL) - proc->nargs = total; - else - { - /* proc->nargs was initialized to 0 above */ - for (i = 0; i < total; i++) - { - if (modes[i] != PROARGMODE_OUT && - modes[i] != PROARGMODE_TABLE) - (proc->nargs)++; - } - } - - proc->argnames = (char **) PLy_malloc0(sizeof(char *) * proc->nargs); - for (i = pos = 0; i < total; i++) - { - HeapTuple argTypeTup; - Form_pg_type argTypeStruct; - - if (modes && - (modes[i] == PROARGMODE_OUT || - modes[i] == PROARGMODE_TABLE)) - continue; /* skip OUT arguments */ - - Assert(types[i] == procStruct->proargtypes.values[pos]); - - argTypeTup = SearchSysCache1(TYPEOID, - ObjectIdGetDatum(types[i])); - if (!HeapTupleIsValid(argTypeTup)) - elog(ERROR, "cache lookup failed for type %u", types[i]); - argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); - - /* check argument type is OK, set up I/O function info */ - switch (argTypeStruct->typtype) - { - case TYPTYPE_PSEUDO: - /* Disallow pseudotype argument */ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("PL/Python functions cannot accept type %s", - format_type_be(types[i])))); - break; - case TYPTYPE_COMPOSITE: - /* we'll set IO funcs at first call */ - proc->args[pos].is_rowtype = 2; - break; - default: - PLy_input_datum_func(&(proc->args[pos]), - types[i], - argTypeTup); - break; - } - - /* get argument name */ - proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL; - - ReleaseSysCache(argTypeTup); - - pos++; - } - } - - /* - * get the text of the function. - */ - prosrcdatum = SysCacheGetAttr(PROCOID, procTup, - Anum_pg_proc_prosrc, &isnull); - if (isnull) - elog(ERROR, "null prosrc"); - procSource = TextDatumGetCString(prosrcdatum); - - PLy_procedure_compile(proc, procSource); - - pfree(procSource); - procSource = NULL; - } - PG_CATCH(); - { - PLy_procedure_delete(proc); - if (procSource) - pfree(procSource); - - PG_RE_THROW(); - } - PG_END_TRY(); - - return proc; -} - -/* - * Insert the procedure into the Python interpreter - */ -static void -PLy_procedure_compile(PLyProcedure *proc, const char *src) -{ - PyObject *crv = NULL; - char *msrc; - - proc->globals = PyDict_Copy(PLy_interp_globals); - - /* - * SD is private preserved data between calls. GD is global data shared by - * all functions - */ - proc->statics = PyDict_New(); - PyDict_SetItemString(proc->globals, "SD", proc->statics); - - /* - * insert the function code into the interpreter - */ - msrc = PLy_procedure_munge_source(proc->pyname, src); - /* Save the mangled source for later inclusion in tracebacks */ - proc->src = PLy_strdup(msrc); - crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); - pfree(msrc); - - if (crv != NULL) - { - int clen; - char call[NAMEDATALEN + 256]; - - Py_DECREF(crv); - - /* - * compile a call to the function - */ - clen = snprintf(call, sizeof(call), "%s()", proc->pyname); - if (clen < 0 || clen >= sizeof(call)) - elog(ERROR, "string would overflow buffer"); - proc->code = Py_CompileString(call, "<string>", Py_eval_input); - if (proc->code != NULL) - return; - } - - if (proc->proname) - PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", - proc->proname); - else - PLy_elog(ERROR, "could not compile anonymous PL/Python code block"); -} - -static char * -PLy_procedure_munge_source(const char *name, const char *src) -{ - char *mrc, - *mp; - const char *sp; - size_t mlen, - plen; - - /* - * room for function source and the def statement - */ - mlen = (strlen(src) * 2) + strlen(name) + 16; - - mrc = palloc(mlen); - plen = snprintf(mrc, mlen, "def %s():\n\t", name); - Assert(plen >= 0 && plen < mlen); - - sp = src; - mp = mrc + plen; - - while (*sp != '\0') - { - if (*sp == '\r' && *(sp + 1) == '\n') - sp++; - - if (*sp == '\n' || *sp == '\r') - { - *mp++ = '\n'; - *mp++ = '\t'; - sp++; - } - else - *mp++ = *sp++; - } - *mp++ = '\n'; - *mp++ = '\n'; - *mp = '\0'; - - if (mp > (mrc + mlen)) - elog(FATAL, "buffer overrun in PLy_munge_source"); - - return mrc; -} - -static void -PLy_procedure_delete(PLyProcedure *proc) -{ - int i; - - Py_XDECREF(proc->code); - Py_XDECREF(proc->statics); - Py_XDECREF(proc->globals); - if (proc->proname) - PLy_free(proc->proname); - 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) - PLy_free(proc->args[i].in.r.atts); - 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->src) - PLy_free(proc->src); - if (proc->argnames) - PLy_free(proc->argnames); -} - -/* - * Conversion functions. Remember output from Python is input to - * PostgreSQL, and vice versa. - */ -static void -PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) -{ - int i; - - if (arg->is_rowtype == 0) - elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); - arg->is_rowtype = 1; - - if (arg->in.r.natts != desc->natts) - { - if (arg->in.r.atts) - PLy_free(arg->in.r.atts); - arg->in.r.natts = desc->natts; - arg->in.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); - } - - /* Can this be an unnamed tuple? If not, then an Assert would be enough */ - if (desc->tdtypmod != -1) - elog(ERROR, "received unnamed record type as input"); - - Assert(OidIsValid(desc->tdtypeid)); - - /* - * RECORDOID means we got called to create input functions for a tuple - * fetched by plpy.execute or for an anonymous record type - */ - if (desc->tdtypeid != RECORDOID) - { - HeapTuple relTup; - - /* Get the pg_class tuple corresponding to the type of the input */ - arg->typ_relid = typeidTypeRelid(desc->tdtypeid); - relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); - if (!HeapTupleIsValid(relTup)) - elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); - - /* Remember XMIN and TID for later validation if cache is still OK */ - arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data); - arg->typrel_tid = relTup->t_self; - - ReleaseSysCache(relTup); - } - - for (i = 0; i < desc->natts; i++) - { - HeapTuple typeTup; - - if (desc->attrs[i]->attisdropped) - continue; - - if (arg->in.r.atts[i].typoid == desc->attrs[i]->atttypid) - continue; /* already set up this entry */ - - typeTup = SearchSysCache1(TYPEOID, - ObjectIdGetDatum(desc->attrs[i]->atttypid)); - if (!HeapTupleIsValid(typeTup)) - elog(ERROR, "cache lookup failed for type %u", - desc->attrs[i]->atttypid); - - PLy_input_datum_func2(&(arg->in.r.atts[i]), - desc->attrs[i]->atttypid, - typeTup); - - ReleaseSysCache(typeTup); - } -} - -static void -PLy_output_record_funcs(PLyTypeInfo *arg, TupleDesc desc) -{ - /* - * If the output record functions are already set, we just have to check - * if the record descriptor has not changed - */ - if ((arg->is_rowtype == 1) && - (arg->out.d.typmod != -1) && - (arg->out.d.typmod == desc->tdtypmod)) - return; - - /* bless the record to make it known to the typcache lookup code */ - BlessTupleDesc(desc); - /* save the freshly generated typmod */ - arg->out.d.typmod = desc->tdtypmod; - /* proceed with normal I/O function caching */ - PLy_output_tuple_funcs(arg, desc); - - /* - * it should change is_rowtype to 1, so we won't go through this again - * unless the the output record description changes - */ - Assert(arg->is_rowtype == 1); -} - -static void -PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc) -{ - int i; - - if (arg->is_rowtype == 0) - elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); - arg->is_rowtype = 1; - - if (arg->out.r.natts != desc->natts) - { - if (arg->out.r.atts) - PLy_free(arg->out.r.atts); - arg->out.r.natts = desc->natts; - arg->out.r.atts = PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); - } - - Assert(OidIsValid(desc->tdtypeid)); - - /* - * RECORDOID means we got called to create output functions for an - * anonymous record type - */ - if (desc->tdtypeid != RECORDOID) - { - HeapTuple relTup; - - /* Get the pg_class tuple corresponding to the type of the output */ - arg->typ_relid = typeidTypeRelid(desc->tdtypeid); - relTup = SearchSysCache1(RELOID, ObjectIdGetDatum(arg->typ_relid)); - if (!HeapTupleIsValid(relTup)) - elog(ERROR, "cache lookup failed for relation %u", arg->typ_relid); - - /* Remember XMIN and TID for later validation if cache is still OK */ - arg->typrel_xmin = HeapTupleHeaderGetXmin(relTup->t_data); - arg->typrel_tid = relTup->t_self; - - ReleaseSysCache(relTup); - } - - for (i = 0; i < desc->natts; i++) - { - HeapTuple typeTup; - - if (desc->attrs[i]->attisdropped) - continue; - - if (arg->out.r.atts[i].typoid == desc->attrs[i]->atttypid) - continue; /* already set up this entry */ - - typeTup = SearchSysCache1(TYPEOID, - ObjectIdGetDatum(desc->attrs[i]->atttypid)); - if (!HeapTupleIsValid(typeTup)) - elog(ERROR, "cache lookup failed for type %u", - desc->attrs[i]->atttypid); - - PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); - - ReleaseSysCache(typeTup); - } -} - -static void -PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup) -{ - if (arg->is_rowtype > 0) - elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); - arg->is_rowtype = 0; - PLy_output_datum_func2(&(arg->out.d), typeTup); -} - -static void -PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup) -{ - Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); - Oid element_type; - - perm_fmgr_info(typeStruct->typinput, &arg->typfunc); - arg->typoid = HeapTupleGetOid(typeTup); - arg->typmod = -1; - arg->typioparam = getTypeIOParam(typeTup); - arg->typbyval = typeStruct->typbyval; - - element_type = get_element_type(arg->typoid); - - /* - * Select a conversion function to convert Python objects to PostgreSQL - * datums. Most data types can go through the generic function. - */ - switch (getBaseType(element_type ? element_type : arg->typoid)) - { - case BOOLOID: - arg->func = PLyObject_ToBool; - break; - case BYTEAOID: - arg->func = PLyObject_ToBytea; - break; - default: - arg->func = PLyObject_ToDatum; - break; - } - - /* Composite types need their own input routine, though */ - if (typeStruct->typtype == TYPTYPE_COMPOSITE) - { - arg->func = PLyObject_ToComposite; - } - - if (element_type) - { - char dummy_delim; - Oid funcid; - - if (type_is_rowtype(element_type)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("PL/Python functions cannot return type %s", - format_type_be(arg->typoid)), - errdetail("PL/Python does not support conversion to arrays of row types."))); - - arg->elm = PLy_malloc0(sizeof(*arg->elm)); - arg->elm->func = arg->func; - arg->func = PLySequence_ToArray; - - arg->elm->typoid = element_type; - arg->elm->typmod = -1; - get_type_io_data(element_type, IOFunc_input, - &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim, - &arg->elm->typioparam, &funcid); - perm_fmgr_info(funcid, &arg->elm->typfunc); - } -} - -static void -PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup) -{ - if (arg->is_rowtype > 0) - elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); - arg->is_rowtype = 0; - PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); -} - -static void -PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup) -{ - Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); - Oid element_type = get_element_type(typeOid); - - /* Get the type's conversion information */ - perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); - arg->typoid = HeapTupleGetOid(typeTup); - arg->typmod = -1; - arg->typioparam = getTypeIOParam(typeTup); - arg->typbyval = typeStruct->typbyval; - arg->typlen = typeStruct->typlen; - arg->typalign = typeStruct->typalign; - - /* Determine which kind of Python object we will convert to */ - switch (getBaseType(element_type ? element_type : 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 BYTEAOID: - arg->func = PLyBytes_FromBytea; - break; - default: - arg->func = PLyString_FromDatum; - break; - } - - if (element_type) - { - char dummy_delim; - Oid funcid; - - arg->elm = PLy_malloc0(sizeof(*arg->elm)); - arg->elm->func = arg->func; - arg->func = PLyList_FromArray; - arg->elm->typoid = element_type; - arg->elm->typmod = -1; - get_type_io_data(element_type, IOFunc_output, - &arg->elm->typlen, &arg->elm->typbyval, &arg->elm->typalign, &dummy_delim, - &arg->elm->typioparam, &funcid); - perm_fmgr_info(funcid, &arg->elm->typfunc); - } -} - -static void -PLy_typeinfo_init(PLyTypeInfo *arg) -{ - arg->is_rowtype = -1; - arg->in.r.natts = arg->out.r.natts = 0; - arg->in.r.atts = NULL; - arg->out.r.atts = NULL; - arg->typ_relid = InvalidOid; - arg->typrel_xmin = InvalidTransactionId; - ItemPointerSetInvalid(&arg->typrel_tid); -} - -static void -PLy_typeinfo_dealloc(PLyTypeInfo *arg) -{ - if (arg->is_rowtype == 1) - { - if (arg->in.r.atts) - PLy_free(arg->in.r.atts); - if (arg->out.r.atts) - PLy_free(arg->out.r.atts); - } -} - -static PyObject * -PLyBool_FromBool(PLyDatumToOb *arg, Datum d) -{ - /* - * 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 (DatumGetBool(d)) - return PyBool_FromLong(1); - return PyBool_FromLong(0); -} - -static PyObject * -PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d) -{ - return PyFloat_FromDouble(DatumGetFloat4(d)); -} - -static PyObject * -PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d) -{ - 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); - - return PyFloat_FromDouble(x); -} - -static PyObject * -PLyInt_FromInt16(PLyDatumToOb *arg, Datum d) -{ - return PyInt_FromLong(DatumGetInt16(d)); -} - -static PyObject * -PLyInt_FromInt32(PLyDatumToOb *arg, Datum d) -{ - return PyInt_FromLong(DatumGetInt32(d)); -} - -static PyObject * -PLyLong_FromInt64(PLyDatumToOb *arg, Datum d) -{ - /* 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 * -PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d) -{ - text *txt = DatumGetByteaP(d); - char *str = VARDATA(txt); - size_t size = VARSIZE(txt) - VARHDRSZ; - - return PyBytes_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 * -PLyList_FromArray(PLyDatumToOb *arg, Datum d) -{ - ArrayType *array = DatumGetArrayTypeP(d); - PLyDatumToOb *elm = arg->elm; - PyObject *list; - int length; - int lbound; - int i; - - if (ARR_NDIM(array) == 0) - return PyList_New(0); - - if (ARR_NDIM(array) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert multidimensional array to Python list"), - errdetail("PL/Python only supports one-dimensional arrays."))); - - length = ARR_DIMS(array)[0]; - lbound = ARR_LBOUND(array)[0]; - list = PyList_New(length); - - for (i = 0; i < length; i++) - { - Datum elem; - bool isnull; - int offset; - - offset = lbound + i; - elem = array_ref(array, 1, &offset, arg->typlen, - elm->typlen, elm->typbyval, elm->typalign, - &isnull); - if (isnull) - { - Py_INCREF(Py_None); - PyList_SET_ITEM(list, i, Py_None); - } - else - PyList_SET_ITEM(list, i, elm->func(elm, elem)); - } - - return list; -} - -static PyObject * -PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc) -{ - PyObject *volatile dict; - int i; - - if (info->is_rowtype != 1) - elog(ERROR, "PLyTypeInfo structure describes a datum"); - - dict = PyDict_New(); - if (dict == NULL) - PLy_elog(ERROR, "could not create new dictionary"); - - PG_TRY(); - { - for (i = 0; i < info->in.r.natts; i++) - { - char *key; - Datum vattr; - bool is_null; - PyObject *value; - - if (desc->attrs[i]->attisdropped) - continue; - - key = NameStr(desc->attrs[i]->attname); - vattr = heap_getattr(tuple, (i + 1), desc, &is_null); - - if (is_null || info->in.r.atts[i].func == NULL) - 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); - } - } - } - PG_CATCH(); - { - Py_DECREF(dict); - PG_RE_THROW(); - } - PG_END_TRY(); - - return dict; -} - -/* - * Convert a Python object to a PostgreSQL tuple, using all supported - * conversion methods: tuple as a sequence, as a mapping or as an object that - * has __getattr__ support. - */ -static HeapTuple -PLyObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv) -{ - HeapTuple tuple; - - if (PySequence_Check(plrv)) - /* composite type as sequence (tuple, list etc) */ - tuple = PLySequence_ToTuple(info, desc, plrv); - else if (PyMapping_Check(plrv)) - /* composite type as mapping (currently only dict) */ - tuple = PLyMapping_ToTuple(info, desc, plrv); - else - /* returned as smth, must provide method __getattr__(name) */ - tuple = PLyGenericObject_ToTuple(info, desc, plrv); - - return tuple; -} - -/* - * 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(PLyObToDatum *arg, int32 typmod, 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(PLyObToDatum *arg, int32 typmod, PyObject *plrv) -{ - PyObject *volatile plrv_so = NULL; - Datum rv; - - Assert(plrv != Py_None); - - plrv_so = PyObject_Bytes(plrv); - if (!plrv_so) - PLy_elog(ERROR, "could not create bytes representation of Python object"); - - PG_TRY(); - { - char *plrv_sc = PyBytes_AsString(plrv_so); - size_t len = PyBytes_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; -} - - -/* - * Convert a Python object to a composite type. First look up the type's - * description, then route the Python object through the conversion function - * for obtaining PostgreSQL tuples. - */ -static Datum -PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv) -{ - HeapTuple tuple = NULL; - Datum rv; - PLyTypeInfo info; - TupleDesc desc; - - if (typmod != -1) - elog(ERROR, "received unnamed record type as input"); - - /* Create a dummy PLyTypeInfo */ - MemSet(&info, 0, sizeof(PLyTypeInfo)); - PLy_typeinfo_init(&info); - /* Mark it as needing output routines lookup */ - info.is_rowtype = 2; - - desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); - - /* - * This will set up the dummy PLyTypeInfo's output conversion routines, - * since we left is_rowtype as 2. A future optimisation could be caching - * that info instead of looking it up every time a tuple is returned from - * the function. - */ - tuple = PLyObject_ToTuple(&info, desc, plrv); - - PLy_typeinfo_dealloc(&info); - - if (tuple != NULL) - rv = HeapTupleGetDatum(tuple); - else - rv = (Datum) NULL; - - return rv; -} - - -/* - * Generic conversion function: Convert PyObject to cstring and - * cstring into PostgreSQL type. - */ -static Datum -PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv) -{ - PyObject *volatile plrv_bo = NULL; - Datum rv; - - Assert(plrv != Py_None); - - if (PyUnicode_Check(plrv)) - plrv_bo = PLyUnicode_Bytes(plrv); - else - { -#if PY_MAJOR_VERSION >= 3 - PyObject *s = PyObject_Str(plrv); - - plrv_bo = PLyUnicode_Bytes(s); - Py_XDECREF(s); -#else - plrv_bo = PyObject_Str(plrv); -#endif - } - if (!plrv_bo) - PLy_elog(ERROR, "could not create string representation of Python object"); - - PG_TRY(); - { - char *plrv_sc = PyBytes_AsString(plrv_bo); - size_t plen = PyBytes_Size(plrv_bo); - 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"); - pg_verifymbstr(plrv_sc, slen, false); - rv = InputFunctionCall(&arg->typfunc, - plrv_sc, - arg->typioparam, - typmod); - } - PG_CATCH(); - { - Py_XDECREF(plrv_bo); - PG_RE_THROW(); - } - PG_END_TRY(); - - Py_XDECREF(plrv_bo); - - return rv; -} - -static Datum -PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv) -{ - ArrayType *array; - int i; - Datum *elems; - bool *nulls; - int len; - int lbs; - - Assert(plrv != Py_None); - - if (!PySequence_Check(plrv)) - PLy_elog(ERROR, "return value of function with array return type is not a Python sequence"); - - len = PySequence_Length(plrv); - elems = palloc(sizeof(*elems) * len); - nulls = palloc(sizeof(*nulls) * len); - - for (i = 0; i < len; i++) - { - PyObject *obj = PySequence_GetItem(plrv, i); - - if (obj == Py_None) - nulls[i] = true; - else - { - nulls[i] = false; - - /* - * We don't support arrays of row types yet, so the first argument - * can be NULL. - */ - elems[i] = arg->elm->func(arg->elm, -1, obj); - } - Py_XDECREF(obj); - } - - lbs = 1; - array = construct_md_array(elems, nulls, 1, &len, &lbs, - get_element_type(arg->typoid), arg->elm->typlen, arg->elm->typbyval, arg->elm->typalign); - return PointerGetDatum(array); -} - -static HeapTuple -PLyMapping_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping) -{ - HeapTuple tuple; - Datum *values; - bool *nulls; - volatile int i; - - Assert(PyMapping_Check(mapping)); - - 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; - PLyObToDatum *att; - - if (desc->attrs[i]->attisdropped) - { - values[i] = (Datum) 0; - nulls[i] = true; - continue; - } - - key = NameStr(desc->attrs[i]->attname); - value = NULL; - att = &info->out.r.atts[i]; - PG_TRY(); - { - value = PyMapping_GetItemString(mapping, key); - if (value == Py_None) - { - values[i] = (Datum) NULL; - nulls[i] = true; - } - else if (value) - { - values[i] = (att->func) (att, -1, value); - 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(value); - PG_RE_THROW(); - } - PG_END_TRY(); - } - - tuple = heap_form_tuple(desc, values, nulls); - ReleaseTupleDesc(desc); - pfree(values); - pfree(nulls); - - return tuple; -} - - -static HeapTuple -PLySequence_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *sequence) -{ - HeapTuple tuple; - Datum *values; - bool *nulls; - volatile int idx; - volatile 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 - */ - idx = 0; - for (i = 0; i < desc->natts; i++) - { - if (!desc->attrs[i]->attisdropped) - idx++; - } - if (PySequence_Length(sequence) != idx) - 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); - idx = 0; - for (i = 0; i < desc->natts; ++i) - { - PyObject *volatile value; - PLyObToDatum *att; - - if (desc->attrs[i]->attisdropped) - { - values[i] = (Datum) 0; - nulls[i] = true; - continue; - } - - value = NULL; - att = &info->out.r.atts[i]; - PG_TRY(); - { - value = PySequence_GetItem(sequence, idx); - Assert(value); - if (value == Py_None) - { - values[i] = (Datum) NULL; - nulls[i] = true; - } - else if (value) - { - values[i] = (att->func) (att, -1, value); - nulls[i] = false; - } - - Py_XDECREF(value); - value = NULL; - } - PG_CATCH(); - { - Py_XDECREF(value); - PG_RE_THROW(); - } - PG_END_TRY(); - - idx++; - } - - tuple = heap_form_tuple(desc, values, nulls); - ReleaseTupleDesc(desc); - pfree(values); - pfree(nulls); - - return tuple; -} - - -static HeapTuple -PLyGenericObject_ToTuple(PLyTypeInfo *info, TupleDesc desc, PyObject *object) -{ - HeapTuple tuple; - Datum *values; - bool *nulls; - volatile int i; - - 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; - PLyObToDatum *att; - - if (desc->attrs[i]->attisdropped) - { - values[i] = (Datum) 0; - nulls[i] = true; - continue; - } - - key = NameStr(desc->attrs[i]->attname); - value = NULL; - att = &info->out.r.atts[i]; - PG_TRY(); - { - value = PyObject_GetAttrString(object, key); - if (value == Py_None) - { - values[i] = (Datum) NULL; - nulls[i] = true; - } - else if (value) - { - values[i] = (att->func) (att, -1, value); - 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(value); - PG_RE_THROW(); - } - PG_END_TRY(); - } - - tuple = heap_form_tuple(desc, values, nulls); - ReleaseTupleDesc(desc); - pfree(values); - pfree(nulls); - - return tuple; -} - - -/* initialization, some python variables function declared here */ - -/* interface to postgresql elog */ -static PyObject *PLy_debug(PyObject *, PyObject *); -static PyObject *PLy_log(PyObject *, PyObject *); -static PyObject *PLy_info(PyObject *, PyObject *); -static PyObject *PLy_notice(PyObject *, PyObject *); -static PyObject *PLy_warning(PyObject *, PyObject *); -static PyObject *PLy_error(PyObject *, PyObject *); -static PyObject *PLy_fatal(PyObject *, PyObject *); - -/* PLyPlanObject, PLyResultObject and SPI interface */ -#define is_PLyPlanObject(x) ((x)->ob_type == &PLy_PlanType) -static PyObject *PLy_plan_new(void); -static void PLy_plan_dealloc(PyObject *); -static PyObject *PLy_plan_status(PyObject *, PyObject *); - -static PyObject *PLy_result_new(void); -static void PLy_result_dealloc(PyObject *); -static PyObject *PLy_result_nrows(PyObject *, PyObject *); -static PyObject *PLy_result_status(PyObject *, PyObject *); -static Py_ssize_t PLy_result_length(PyObject *); -static PyObject *PLy_result_item(PyObject *, Py_ssize_t); -static PyObject *PLy_result_slice(PyObject *, Py_ssize_t, Py_ssize_t); -static int PLy_result_ass_item(PyObject *, Py_ssize_t, PyObject *); -static int PLy_result_ass_slice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); - - -/* handling of SPI operations inside subtransactions */ -static void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner); -static void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner); -static void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner); - -/* SPI operations */ -static PyObject *PLy_spi_prepare(PyObject *, PyObject *); -static PyObject *PLy_spi_execute(PyObject *, PyObject *); -static PyObject *PLy_spi_execute_query(char *query, long limit); -static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, long); -static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int); - -static PyObject *PLy_quote_literal(PyObject *self, PyObject *args); -static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args); -static PyObject *PLy_quote_ident(PyObject *self, PyObject *args); - -static PyObject *PLy_subtransaction(PyObject *, PyObject *); -static PyObject *PLy_subtransaction_new(void); -static void PLy_subtransaction_dealloc(PyObject *); -static PyObject *PLy_subtransaction_enter(PyObject *, PyObject *); -static PyObject *PLy_subtransaction_exit(PyObject *, PyObject *); - -static PyObject *PLy_cursor(PyObject *self, PyObject *unused); -static PyObject *PLy_cursor_query(const char *query); -static PyObject *PLy_cursor_plan(PyObject *ob, PyObject *args); -static void PLy_cursor_dealloc(PyObject *arg); -static PyObject *PLy_cursor_iternext(PyObject *self); -static PyObject *PLy_cursor_fetch(PyObject *self, PyObject *args); -static PyObject *PLy_cursor_close(PyObject *self, PyObject *unused); - - -static PyMethodDef PLy_plan_methods[] = { - {"status", PLy_plan_status, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static PyTypeObject PLy_PlanType = { - PyVarObject_HEAD_INIT(NULL, 0) - "PLyPlan", /* tp_name */ - sizeof(PLyPlanObject), /* tp_size */ - 0, /* tp_itemsize */ - - /* - * methods - */ - PLy_plan_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - PLy_plan_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - PLy_plan_methods, /* tp_tpmethods */ -}; - -static PySequenceMethods PLy_result_as_sequence = { - PLy_result_length, /* sq_length */ - NULL, /* sq_concat */ - NULL, /* sq_repeat */ - PLy_result_item, /* sq_item */ - PLy_result_slice, /* sq_slice */ - PLy_result_ass_item, /* sq_ass_item */ - PLy_result_ass_slice, /* sq_ass_slice */ -}; - -static PyMethodDef PLy_result_methods[] = { - {"nrows", PLy_result_nrows, METH_VARARGS, NULL}, - {"status", PLy_result_status, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static PyTypeObject PLy_ResultType = { - PyVarObject_HEAD_INIT(NULL, 0) - "PLyResult", /* tp_name */ - sizeof(PLyResultObject), /* tp_size */ - 0, /* tp_itemsize */ - - /* - * methods - */ - PLy_result_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - &PLy_result_as_sequence, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - PLy_result_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - PLy_result_methods, /* tp_tpmethods */ -}; - -static PyMethodDef PLy_subtransaction_methods[] = { - {"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL}, - {"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL}, - /* user-friendly names for Python <2.6 */ - {"enter", PLy_subtransaction_enter, METH_VARARGS, NULL}, - {"exit", PLy_subtransaction_exit, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static PyTypeObject PLy_SubtransactionType = { - PyVarObject_HEAD_INIT(NULL, 0) - "PLySubtransaction", /* tp_name */ - sizeof(PLySubtransactionObject), /* tp_size */ - 0, /* tp_itemsize */ - - /* - * methods - */ - PLy_subtransaction_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - PLy_subtransaction_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - PLy_subtransaction_methods, /* tp_tpmethods */ -}; - -static PyMethodDef PLy_cursor_methods[] = { - {"fetch", PLy_cursor_fetch, METH_VARARGS, NULL}, - {"close", PLy_cursor_close, METH_NOARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static PyTypeObject PLy_CursorType = { - PyVarObject_HEAD_INIT(NULL, 0) - "PLyCursor", /* tp_name */ - sizeof(PLyCursorObject), /* tp_size */ - 0, /* tp_itemsize */ - - /* - * methods - */ - PLy_cursor_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ - PLy_cursor_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - PyObject_SelfIter, /* tp_iter */ - PLy_cursor_iternext, /* tp_iternext */ - PLy_cursor_methods, /* tp_tpmethods */ -}; - -static PyMethodDef PLy_methods[] = { - /* - * logging methods - */ - {"debug", PLy_debug, METH_VARARGS, NULL}, - {"log", PLy_log, METH_VARARGS, NULL}, - {"info", PLy_info, METH_VARARGS, NULL}, - {"notice", PLy_notice, METH_VARARGS, NULL}, - {"warning", PLy_warning, METH_VARARGS, NULL}, - {"error", PLy_error, METH_VARARGS, NULL}, - {"fatal", PLy_fatal, METH_VARARGS, NULL}, - - /* - * create a stored plan - */ - {"prepare", PLy_spi_prepare, METH_VARARGS, NULL}, - - /* - * execute a plan or query - */ - {"execute", PLy_spi_execute, METH_VARARGS, NULL}, - - /* - * escaping strings - */ - {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL}, - {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL}, - {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL}, - - /* - * create the subtransaction context manager - */ - {"subtransaction", PLy_subtransaction, METH_NOARGS, NULL}, - - /* - * create a cursor - */ - {"cursor", PLy_cursor, METH_VARARGS, NULL}, - - {NULL, NULL, 0, NULL} -}; - -static PyMethodDef PLy_exc_methods[] = { - {NULL, NULL, 0, NULL} -}; - -#if PY_MAJOR_VERSION >= 3 -static PyModuleDef PLy_module = { - PyModuleDef_HEAD_INIT, /* m_base */ - "plpy", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - PLy_methods, /* m_methods */ -}; - -static PyModuleDef PLy_exc_module = { - PyModuleDef_HEAD_INIT, /* m_base */ - "spiexceptions", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - PLy_exc_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL /* m_free */ -}; -#endif - -/* plan object methods */ -static PyObject * -PLy_plan_new(void) -{ - PLyPlanObject *ob; - - if ((ob = PyObject_New(PLyPlanObject, &PLy_PlanType)) == NULL) - return NULL; - - ob->plan = NULL; - ob->nargs = 0; - ob->types = NULL; - ob->values = NULL; - ob->args = NULL; - - return (PyObject *) ob; -} - - -static void -PLy_plan_dealloc(PyObject *arg) -{ - PLyPlanObject *ob = (PLyPlanObject *) arg; - - if (ob->plan) - SPI_freeplan(ob->plan); - if (ob->types) - PLy_free(ob->types); - if (ob->values) - PLy_free(ob->values); - if (ob->args) - { - int i; - - for (i = 0; i < ob->nargs; i++) - PLy_typeinfo_dealloc(&ob->args[i]); - PLy_free(ob->args); - } - - arg->ob_type->tp_free(arg); -} - - -static PyObject * -PLy_plan_status(PyObject *self, PyObject *args) -{ - if (PyArg_ParseTuple(args, "")) - { - Py_INCREF(Py_True); - return Py_True; - /* return PyInt_FromLong(self->status); */ - } - PLy_exception_set(PLy_exc_error, "plan.status takes no arguments"); - return NULL; -} - - - -/* result object methods */ - -static PyObject * -PLy_result_new(void) -{ - PLyResultObject *ob; - - if ((ob = PyObject_New(PLyResultObject, &PLy_ResultType)) == NULL) - return NULL; - - /* ob->tuples = NULL; */ - - Py_INCREF(Py_None); - ob->status = Py_None; - ob->nrows = PyInt_FromLong(-1); - ob->rows = PyList_New(0); - - return (PyObject *) ob; -} - -static void -PLy_result_dealloc(PyObject *arg) -{ - PLyResultObject *ob = (PLyResultObject *) arg; - - Py_XDECREF(ob->nrows); - Py_XDECREF(ob->rows); - Py_XDECREF(ob->status); - - arg->ob_type->tp_free(arg); -} - -static PyObject * -PLy_result_nrows(PyObject *self, PyObject *args) -{ - PLyResultObject *ob = (PLyResultObject *) self; - - Py_INCREF(ob->nrows); - return ob->nrows; -} - -static PyObject * -PLy_result_status(PyObject *self, PyObject *args) -{ - PLyResultObject *ob = (PLyResultObject *) self; - - Py_INCREF(ob->status); - return ob->status; -} - -static Py_ssize_t -PLy_result_length(PyObject *arg) -{ - PLyResultObject *ob = (PLyResultObject *) arg; - - return PyList_Size(ob->rows); -} - -static PyObject * -PLy_result_item(PyObject *arg, Py_ssize_t idx) -{ - PyObject *rv; - PLyResultObject *ob = (PLyResultObject *) arg; - - rv = PyList_GetItem(ob->rows, idx); - if (rv != NULL) - Py_INCREF(rv); - return rv; -} - -static int -PLy_result_ass_item(PyObject *arg, Py_ssize_t idx, PyObject *item) -{ - int rv; - PLyResultObject *ob = (PLyResultObject *) arg; - - Py_INCREF(item); - rv = PyList_SetItem(ob->rows, idx, item); - return rv; -} - -static PyObject * -PLy_result_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx) -{ - PLyResultObject *ob = (PLyResultObject *) arg; - - return PyList_GetSlice(ob->rows, lidx, hidx); -} - -static int -PLy_result_ass_slice(PyObject *arg, Py_ssize_t lidx, Py_ssize_t hidx, PyObject *slice) -{ - int rv; - PLyResultObject *ob = (PLyResultObject *) arg; - - rv = PyList_SetSlice(ob->rows, lidx, hidx, slice); - return rv; -} - -/* - * Utilities for running SPI functions in subtransactions. - * - * Usage: - * - * MemoryContext oldcontext = CurrentMemoryContext; - * ResourceOwner oldowner = CurrentResourceOwner; - * - * PLy_spi_subtransaction_begin(oldcontext, oldowner); - * PG_TRY(); - * { - * <call SPI functions> - * PLy_spi_subtransaction_commit(oldcontext, oldowner); - * } - * PG_CATCH(); - * { - * <do cleanup> - * PLy_spi_subtransaction_abort(oldcontext, oldowner); - * return NULL; - * } - * PG_END_TRY(); - * - * These utilities take care of restoring connection to the SPI manager and - * setting a Python exception in case of an abort. - */ -static void -PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner) -{ - BeginInternalSubTransaction(NULL); - /* Want to run inside function's memory context */ - MemoryContextSwitchTo(oldcontext); -} - -static void -PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner) -{ - /* Commit the inner transaction, return to outer xact context */ - ReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(oldcontext); - CurrentResourceOwner = oldowner; - - /* - * AtEOSubXact_SPI() should not have popped any SPI context, but just - * in case it did, make sure we remain connected. - */ - SPI_restore_connection(); -} - -static void -PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner) -{ - ErrorData *edata; - PLyExceptionEntry *entry; - PyObject *exc; - - /* Save error info */ - MemoryContextSwitchTo(oldcontext); - edata = CopyErrorData(); - FlushErrorState(); - - /* Abort the inner transaction */ - RollbackAndReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(oldcontext); - CurrentResourceOwner = oldowner; - - /* - * If AtEOSubXact_SPI() popped any SPI context of the subxact, it will have - * left us in a disconnected state. We need this hack to return to - * connected state. - */ - SPI_restore_connection(); - - /* Look up the correct exception */ - entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode), - HASH_FIND, NULL); - /* We really should find it, but just in case have a fallback */ - Assert(entry != NULL); - exc = entry ? entry->exc : PLy_exc_spi_error; - /* Make Python raise the exception */ - PLy_spi_exception_set(exc, edata); - FreeErrorData(edata); -} - - -/* SPI interface */ -static PyObject * -PLy_spi_prepare(PyObject *self, PyObject *args) -{ - PLyPlanObject *plan; - PyObject *list = NULL; - PyObject *volatile optr = NULL; - char *query; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - volatile int nargs; - - if (!PyArg_ParseTuple(args, "s|O", &query, &list)) - return NULL; - - if (list && (!PySequence_Check(list))) - { - PLy_exception_set(PyExc_TypeError, - "second argument of plpy.prepare must be a sequence"); - return NULL; - } - - if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL) - return NULL; - - nargs = list ? PySequence_Length(list) : 0; - - plan->nargs = nargs; - plan->types = nargs ? PLy_malloc(sizeof(Oid) * nargs) : NULL; - plan->values = nargs ? PLy_malloc(sizeof(Datum) * nargs) : NULL; - plan->args = nargs ? PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL; - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - int i; - - /* - * the other loop might throw an exception, if PLyTypeInfo member - * isn't properly initialized the Py_DECREF(plan) will go boom - */ - for (i = 0; i < nargs; i++) - { - PLy_typeinfo_init(&plan->args[i]); - plan->values[i] = PointerGetDatum(NULL); - } - - for (i = 0; i < nargs; i++) - { - char *sptr; - HeapTuple typeTup; - Oid typeId; - int32 typmod; - Form_pg_type typeStruct; - - optr = PySequence_GetItem(list, i); - if (PyString_Check(optr)) - sptr = PyString_AsString(optr); - else if (PyUnicode_Check(optr)) - sptr = PLyUnicode_AsString(optr); - else - { - ereport(ERROR, - (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); - sptr = NULL; /* keep compiler quiet */ - } - - /******************************************************** - * Resolve argument type names and then look them up by - * oid in the system cache, and remember the required - *information for input conversion. - ********************************************************/ - - parseTypeString(sptr, &typeId, &typmod); - - typeTup = SearchSysCache1(TYPEOID, - ObjectIdGetDatum(typeId)); - if (!HeapTupleIsValid(typeTup)) - elog(ERROR, "cache lookup failed for type %u", typeId); - - Py_DECREF(optr); - - /* - * set optr to NULL, so we won't try to unref it again in case of - * an error - */ - optr = NULL; - - plan->types[i] = typeId; - typeStruct = (Form_pg_type) GETSTRUCT(typeTup); - if (typeStruct->typtype != TYPTYPE_COMPOSITE) - PLy_output_datum_func(&plan->args[i], typeTup); - else - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpy.prepare does not support composite types"))); - ReleaseSysCache(typeTup); - } - - pg_verifymbstr(query, strlen(query), false); - plan->plan = SPI_prepare(query, plan->nargs, plan->types); - if (plan->plan == NULL) - elog(ERROR, "SPI_prepare failed: %s", - SPI_result_code_string(SPI_result)); - - /* transfer plan from procCxt to topCxt */ - if (SPI_keepplan(plan->plan)) - elog(ERROR, "SPI_keepplan failed"); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - Py_DECREF(plan); - Py_XDECREF(optr); - - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - Assert(plan->plan != NULL); - return (PyObject *) plan; -} - -/* execute(query="select * from foo", limit=5) - * execute(plan=plan, values=(foo, bar), limit=5) - */ -static PyObject * -PLy_spi_execute(PyObject *self, PyObject *args) -{ - char *query; - PyObject *plan; - PyObject *list = NULL; - long limit = 0; - - if (PyArg_ParseTuple(args, "s|l", &query, &limit)) - return PLy_spi_execute_query(query, limit); - - PyErr_Clear(); - - if (PyArg_ParseTuple(args, "O|Ol", &plan, &list, &limit) && - is_PLyPlanObject(plan)) - return PLy_spi_execute_plan(plan, list, limit); - - PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan"); - return NULL; -} - -static PyObject * -PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) -{ - volatile int nargs; - int i, - rv; - PLyPlanObject *plan; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - PyObject *ret; - - if (list != NULL) - { - if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list)) - { - PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument"); - return NULL; - } - nargs = PySequence_Length(list); - } - else - nargs = 0; - - plan = (PLyPlanObject *) ob; - - if (nargs != plan->nargs) - { - char *sv; - PyObject *so = PyObject_Str(list); - - if (!so) - PLy_elog(ERROR, "could not execute plan"); - sv = PyString_AsString(so); - PLy_exception_set_plural(PyExc_TypeError, - "Expected sequence of %d argument, got %d: %s", - "Expected sequence of %d arguments, got %d: %s", - plan->nargs, - plan->nargs, nargs, sv); - Py_DECREF(so); - - return NULL; - } - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - char *volatile nulls; - volatile int j; - - if (nargs > 0) - nulls = palloc(nargs * sizeof(char)); - else - nulls = NULL; - - for (j = 0; j < nargs; j++) - { - PyObject *elem; - - elem = PySequence_GetItem(list, j); - if (elem != Py_None) - { - PG_TRY(); - { - plan->values[j] = - plan->args[j].out.d.func(&(plan->args[j].out.d), - -1, - elem); - } - PG_CATCH(); - { - Py_DECREF(elem); - PG_RE_THROW(); - } - PG_END_TRY(); - - Py_DECREF(elem); - nulls[j] = ' '; - } - else - { - Py_DECREF(elem); - plan->values[j] = - InputFunctionCall(&(plan->args[j].out.d.typfunc), - NULL, - plan->args[j].out.d.typioparam, - -1); - nulls[j] = 'n'; - } - } - - rv = SPI_execute_plan(plan->plan, plan->values, nulls, - PLy_curr_procedure->fn_readonly, limit); - ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); - - if (nargs > 0) - pfree(nulls); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - int k; - - /* - * cleanup plan->values array - */ - for (k = 0; k < nargs; k++) - { - if (!plan->args[k].out.d.typbyval && - (plan->values[k] != PointerGetDatum(NULL))) - { - pfree(DatumGetPointer(plan->values[k])); - plan->values[k] = PointerGetDatum(NULL); - } - } - - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - for (i = 0; i < nargs; i++) - { - if (!plan->args[i].out.d.typbyval && - (plan->values[i] != PointerGetDatum(NULL))) - { - pfree(DatumGetPointer(plan->values[i])); - plan->values[i] = PointerGetDatum(NULL); - } - } - - if (rv < 0) - { - PLy_exception_set(PLy_exc_spi_error, - "SPI_execute_plan failed: %s", - SPI_result_code_string(rv)); - return NULL; - } - - return ret; -} - -static PyObject * -PLy_spi_execute_query(char *query, long limit) -{ - int rv; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - PyObject *ret; - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - pg_verifymbstr(query, strlen(query), false); - rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit); - ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - if (rv < 0) - { - PLy_exception_set(PLy_exc_spi_error, - "SPI_execute failed: %s", - SPI_result_code_string(rv)); - return NULL; - } - - return ret; -} - -static PyObject * -PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) -{ - PLyResultObject *result; - volatile MemoryContext oldcontext; - - result = (PLyResultObject *) PLy_result_new(); - Py_DECREF(result->status); - result->status = PyInt_FromLong(status); - - if (status > 0 && tuptable == NULL) - { - Py_DECREF(result->nrows); - result->nrows = PyInt_FromLong(rows); - } - else if (status > 0 && tuptable != NULL) - { - PLyTypeInfo args; - int i; - - Py_DECREF(result->nrows); - result->nrows = PyInt_FromLong(rows); - PLy_typeinfo_init(&args); - - oldcontext = CurrentMemoryContext; - PG_TRY(); - { - if (rows) - { - Py_DECREF(result->rows); - result->rows = PyList_New(rows); - - PLy_input_tuple_funcs(&args, tuptable->tupdesc); - for (i = 0; i < rows; i++) - { - PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i], - tuptable->tupdesc); - - PyList_SetItem(result->rows, i, row); - } - } - } - PG_CATCH(); - { - MemoryContextSwitchTo(oldcontext); - if (!PyErr_Occurred()) - PLy_exception_set(PLy_exc_error, - "unrecognized error in PLy_spi_execute_fetch_result"); - PLy_typeinfo_dealloc(&args); - SPI_freetuptable(tuptable); - Py_DECREF(result); - return NULL; - } - PG_END_TRY(); - - PLy_typeinfo_dealloc(&args); - SPI_freetuptable(tuptable); - } - - return (PyObject *) result; -} - -/* - * c = plpy.cursor("select * from largetable") - * c = plpy.cursor(plan, []) - */ -static PyObject * -PLy_cursor(PyObject *self, PyObject *args) -{ - char *query; - PyObject *plan; - PyObject *planargs = NULL; - - if (PyArg_ParseTuple(args, "s", &query)) - return PLy_cursor_query(query); - - PyErr_Clear(); - - if (PyArg_ParseTuple(args, "O|O", &plan, &planargs)) - return PLy_cursor_plan(plan, planargs); - - PLy_exception_set(PLy_exc_error, "plpy.cursor expected a query or a plan"); - return NULL; -} - - -static PyObject * -PLy_cursor_query(const char *query) -{ - PLyCursorObject *cursor; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - - if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) - return NULL; - cursor->portalname = NULL; - cursor->closed = false; - PLy_typeinfo_init(&cursor->result); - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - SPIPlanPtr plan; - Portal portal; - - pg_verifymbstr(query, strlen(query), false); - - plan = SPI_prepare(query, 0, NULL); - if (plan == NULL) - elog(ERROR, "SPI_prepare failed: %s", - SPI_result_code_string(SPI_result)); - - portal = SPI_cursor_open(NULL, plan, NULL, NULL, - PLy_curr_procedure->fn_readonly); - SPI_freeplan(plan); - - if (portal == NULL) - elog(ERROR, "SPI_cursor_open() failed:%s", - SPI_result_code_string(SPI_result)); - - cursor->portalname = PLy_strdup(portal->name); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - Assert(cursor->portalname != NULL); - return (PyObject *) cursor; -} - -static PyObject * -PLy_cursor_plan(PyObject *ob, PyObject *args) -{ - PLyCursorObject *cursor; - volatile int nargs; - int i; - PLyPlanObject *plan; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - - if (args) - { - if (!PySequence_Check(args) || PyString_Check(args) || PyUnicode_Check(args)) - { - PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument"); - return NULL; - } - nargs = PySequence_Length(args); - } - else - nargs = 0; - - plan = (PLyPlanObject *) ob; - - if (nargs != plan->nargs) - { - char *sv; - PyObject *so = PyObject_Str(args); - - if (!so) - PLy_elog(ERROR, "could not execute plan"); - sv = PyString_AsString(so); - PLy_exception_set_plural(PyExc_TypeError, - "Expected sequence of %d argument, got %d: %s", - "Expected sequence of %d arguments, got %d: %s", - plan->nargs, - plan->nargs, nargs, sv); - Py_DECREF(so); - - return NULL; - } - - if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) - return NULL; - cursor->portalname = NULL; - cursor->closed = false; - PLy_typeinfo_init(&cursor->result); - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - Portal portal; - char *volatile nulls; - volatile int j; - - if (nargs > 0) - nulls = palloc(nargs * sizeof(char)); - else - nulls = NULL; - - for (j = 0; j < nargs; j++) - { - PyObject *elem; - - elem = PySequence_GetItem(args, j); - if (elem != Py_None) - { - PG_TRY(); - { - plan->values[j] = - plan->args[j].out.d.func(&(plan->args[j].out.d), - -1, - elem); - } - PG_CATCH(); - { - Py_DECREF(elem); - PG_RE_THROW(); - } - PG_END_TRY(); - - Py_DECREF(elem); - nulls[j] = ' '; - } - else - { - Py_DECREF(elem); - plan->values[j] = - InputFunctionCall(&(plan->args[j].out.d.typfunc), - NULL, - plan->args[j].out.d.typioparam, - -1); - nulls[j] = 'n'; - } - } - - portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, - PLy_curr_procedure->fn_readonly); - if (portal == NULL) - elog(ERROR, "SPI_cursor_open() failed:%s", - SPI_result_code_string(SPI_result)); - - cursor->portalname = PLy_strdup(portal->name); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - int k; - - /* cleanup plan->values array */ - for (k = 0; k < nargs; k++) - { - if (!plan->args[k].out.d.typbyval && - (plan->values[k] != PointerGetDatum(NULL))) - { - pfree(DatumGetPointer(plan->values[k])); - plan->values[k] = PointerGetDatum(NULL); - } - } - - Py_DECREF(cursor); - - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - for (i = 0; i < nargs; i++) - { - if (!plan->args[i].out.d.typbyval && - (plan->values[i] != PointerGetDatum(NULL))) - { - pfree(DatumGetPointer(plan->values[i])); - plan->values[i] = PointerGetDatum(NULL); - } - } - - Assert(cursor->portalname != NULL); - return (PyObject *) cursor; -} - -static void -PLy_cursor_dealloc(PyObject *arg) -{ - PLyCursorObject *cursor; - Portal portal; - - cursor = (PLyCursorObject *) arg; - - if (!cursor->closed) - { - portal = GetPortalByName(cursor->portalname); - - if (PortalIsValid(portal)) - SPI_cursor_close(portal); - } - - PLy_free(cursor->portalname); - cursor->portalname = NULL; - - PLy_typeinfo_dealloc(&cursor->result); - arg->ob_type->tp_free(arg); -} - -static PyObject * -PLy_cursor_iternext(PyObject *self) -{ - PLyCursorObject *cursor; - PyObject *ret; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - Portal portal; - - cursor = (PLyCursorObject *) self; - - if (cursor->closed) - { - PLy_exception_set(PyExc_ValueError, "iterating a closed cursor"); - return NULL; - } - - portal = GetPortalByName(cursor->portalname); - if (!PortalIsValid(portal)) - { - PLy_exception_set(PyExc_ValueError, - "iterating a cursor in an aborted subtransaction"); - return NULL; - } - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - SPI_cursor_fetch(portal, true, 1); - if (SPI_processed == 0) - { - PyErr_SetNone(PyExc_StopIteration); - ret = NULL; - } - else - { - if (cursor->result.is_rowtype != 1) - PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); - - ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0], - SPI_tuptable->tupdesc); - } - - SPI_freetuptable(SPI_tuptable); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - SPI_freetuptable(SPI_tuptable); - - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - return ret; -} - -static PyObject * -PLy_cursor_fetch(PyObject *self, PyObject *args) -{ - PLyCursorObject *cursor; - int count; - PLyResultObject *ret; - volatile MemoryContext oldcontext; - volatile ResourceOwner oldowner; - Portal portal; - - if (!PyArg_ParseTuple(args, "i", &count)) - return NULL; - - cursor = (PLyCursorObject *) self; - - if (cursor->closed) - { - PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor"); - return NULL; - } - - portal = GetPortalByName(cursor->portalname); - if (!PortalIsValid(portal)) - { - PLy_exception_set(PyExc_ValueError, - "iterating a cursor in an aborted subtransaction"); - return NULL; - } - - ret = (PLyResultObject *) PLy_result_new(); - if (ret == NULL) - return NULL; - - oldcontext = CurrentMemoryContext; - oldowner = CurrentResourceOwner; - - PLy_spi_subtransaction_begin(oldcontext, oldowner); - - PG_TRY(); - { - SPI_cursor_fetch(portal, true, count); - - if (cursor->result.is_rowtype != 1) - PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); - - Py_DECREF(ret->status); - ret->status = PyInt_FromLong(SPI_OK_FETCH); - - Py_DECREF(ret->nrows); - ret->nrows = PyInt_FromLong(SPI_processed); - - if (SPI_processed != 0) - { - int i; - - Py_DECREF(ret->rows); - ret->rows = PyList_New(SPI_processed); - - for (i = 0; i < SPI_processed; i++) - { - PyObject *row = PLyDict_FromTuple(&cursor->result, - SPI_tuptable->vals[i], - SPI_tuptable->tupdesc); - PyList_SetItem(ret->rows, i, row); - } - } - - SPI_freetuptable(SPI_tuptable); - - PLy_spi_subtransaction_commit(oldcontext, oldowner); - } - PG_CATCH(); - { - SPI_freetuptable(SPI_tuptable); - - PLy_spi_subtransaction_abort(oldcontext, oldowner); - return NULL; - } - PG_END_TRY(); - - return (PyObject *) ret; -} - -static PyObject * -PLy_cursor_close(PyObject *self, PyObject *unused) -{ - PLyCursorObject *cursor = (PLyCursorObject *) self; - - if (!cursor->closed) - { - Portal portal = GetPortalByName(cursor->portalname); - - if (!PortalIsValid(portal)) - { - PLy_exception_set(PyExc_ValueError, - "closing a cursor in an aborted subtransaction"); - return NULL; - } - - SPI_cursor_close(portal); - cursor->closed = true; - } - - Py_INCREF(Py_None); - return Py_None; -} - -/* s = plpy.subtransaction() */ -static PyObject * -PLy_subtransaction(PyObject *self, PyObject *unused) -{ - return PLy_subtransaction_new(); -} - -/* Allocate and initialize a PLySubtransactionObject */ -static PyObject * -PLy_subtransaction_new(void) -{ - PLySubtransactionObject *ob; - - ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType); - - if (ob == NULL) - return NULL; - - ob->started = false; - ob->exited = false; - - return (PyObject *) ob; -} - -/* Python requires a dealloc function to be defined */ -static void -PLy_subtransaction_dealloc(PyObject *subxact) -{ -} - -/* - * subxact.__enter__() or subxact.enter() - * - * Start an explicit subtransaction. SPI calls within an explicit - * subtransaction will not start another one, so you can atomically - * execute many SPI calls and still get a controllable exception if - * one of them fails. - */ -static PyObject * -PLy_subtransaction_enter(PyObject *self, PyObject *unused) -{ - PLySubtransactionData *subxactdata; - MemoryContext oldcontext; - PLySubtransactionObject *subxact = (PLySubtransactionObject *) self; - - if (subxact->started) - { - PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered"); - return NULL; - } - - if (subxact->exited) - { - PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited"); - return NULL; - } - - subxact->started = true; - oldcontext = CurrentMemoryContext; - - subxactdata = PLy_malloc(sizeof(*subxactdata)); - subxactdata->oldcontext = oldcontext; - subxactdata->oldowner = CurrentResourceOwner; - - BeginInternalSubTransaction(NULL); - /* Do not want to leave the previous memory context */ - MemoryContextSwitchTo(oldcontext); - - explicit_subtransactions = lcons(subxactdata, explicit_subtransactions); - - Py_INCREF(self); - return self; -} - -/* - * subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb) - * - * Exit an explicit subtransaction. exc_type is an exception type, exc - * is the exception object, tb is the traceback. If exc_type is None, - * commit the subtransactiony, if not abort it. - * - * The method signature is chosen to allow subtransaction objects to - * be used as context managers as described in - * <http://www.python.org/dev/peps/pep-0343/>. - */ -static PyObject * -PLy_subtransaction_exit(PyObject *self, PyObject *args) -{ - PyObject *type; - PyObject *value; - PyObject *traceback; - PLySubtransactionData *subxactdata; - PLySubtransactionObject *subxact = (PLySubtransactionObject *) self; - - if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback)) - return NULL; - - if (!subxact->started) - { - PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered"); - return NULL; - } - - if (subxact->exited) - { - PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited"); - return NULL; - } - - if (explicit_subtransactions == NIL) - { - PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from"); - return NULL; - } - - subxact->exited = true; - - if (type != Py_None) - { - /* Abort the inner transaction */ - RollbackAndReleaseCurrentSubTransaction(); - } - else - { - ReleaseCurrentSubTransaction(); - } - - subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions); - explicit_subtransactions = list_delete_first(explicit_subtransactions); - - MemoryContextSwitchTo(subxactdata->oldcontext); - CurrentResourceOwner = subxactdata->oldowner; - PLy_free(subxactdata); - - /* - * AtEOSubXact_SPI() should not have popped any SPI context, but just in - * case it did, make sure we remain connected. - */ - SPI_restore_connection(); - - Py_INCREF(Py_None); - return Py_None; -} - - -/* - * language handler and interpreter initialization - */ - -/* - * Add exceptions to the plpy module - */ - -/* - * Add all the autogenerated exceptions as subclasses of SPIError - */ -static void -PLy_generate_spi_exceptions(PyObject *mod, PyObject *base) -{ - int i; - - for (i = 0; exception_map[i].name != NULL; i++) - { - bool found; - PyObject *exc; - PLyExceptionEntry *entry; - PyObject *sqlstate; - PyObject *dict = PyDict_New(); - - sqlstate = PyString_FromString(unpack_sql_state(exception_map[i].sqlstate)); - PyDict_SetItemString(dict, "sqlstate", sqlstate); - Py_DECREF(sqlstate); - exc = PyErr_NewException(exception_map[i].name, base, dict); - PyModule_AddObject(mod, exception_map[i].classname, exc); - entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate, - HASH_ENTER, &found); - entry->exc = exc; - Assert(!found); - } -} - -static void -PLy_add_exceptions(PyObject *plpy) -{ - PyObject *excmod; - HASHCTL hash_ctl; - -#if PY_MAJOR_VERSION < 3 - excmod = Py_InitModule("spiexceptions", PLy_exc_methods); -#else - excmod = PyModule_Create(&PLy_exc_module); -#endif - if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0) - PLy_elog(ERROR, "could not add the spiexceptions module"); - -/* - * XXX it appears that in some circumstances the reference count of the - * spiexceptions module drops to zero causing a Python assert failure when - * the garbage collector visits the module. This has been observed on the - * buildfarm. To fix this, add an additional ref for the module here. - * - * This shouldn't cause a memory leak - we don't want this garbage collected, - * and this function shouldn't be called more than once per backend. - */ - Py_INCREF(excmod); - - PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL); - PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL); - PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL); - - Py_INCREF(PLy_exc_error); - PyModule_AddObject(plpy, "Error", PLy_exc_error); - Py_INCREF(PLy_exc_fatal); - PyModule_AddObject(plpy, "Fatal", PLy_exc_fatal); - Py_INCREF(PLy_exc_spi_error); - PyModule_AddObject(plpy, "SPIError", PLy_exc_spi_error); - - memset(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(int); - hash_ctl.entrysize = sizeof(PLyExceptionEntry); - hash_ctl.hash = tag_hash; - PLy_spi_exceptions = hash_create("SPI exceptions", 256, - &hash_ctl, HASH_ELEM | HASH_FUNCTION); - - PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error); -} - -#if PY_MAJOR_VERSION >= 3 -/* - * Must have external linkage, because PyMODINIT_FUNC does dllexport on - * Windows-like platforms. - */ -PyMODINIT_FUNC PyInit_plpy(void); - -PyMODINIT_FUNC -PyInit_plpy(void) -{ - PyObject *m; - - m = PyModule_Create(&PLy_module); - if (m == NULL) - return NULL; - - PLy_add_exceptions(m); - - return m; -} -#endif - - -static const int plpython_python_version = PY_MAJOR_VERSION; - -/* - * _PG_init() - library load-time initialization - * - * DO NOT make this static nor change its name! - */ -void -_PG_init(void) -{ - /* Be sure we do initialization only once (should be redundant now) */ - static bool inited = false; - const int **version_ptr; - HASHCTL hash_ctl; - - if (inited) - return; - - /* Be sure we don't run Python 2 and 3 in the same session (might crash) */ - version_ptr = (const int **) find_rendezvous_variable("plpython_python_version"); - if (!(*version_ptr)) - *version_ptr = &plpython_python_version; - else - { - if (**version_ptr != plpython_python_version) - ereport(FATAL, - (errmsg("Python major version mismatch in session"), - errdetail("This session has previously used Python major version %d, and it is now attempting to use Python major version %d.", - **version_ptr, plpython_python_version), - errhint("Start a new session to use a different Python major version."))); - } - - pg_bindtextdomain(TEXTDOMAIN); - -#if PY_MAJOR_VERSION >= 3 - PyImport_AppendInittab("plpy", PyInit_plpy); -#endif - Py_Initialize(); -#if PY_MAJOR_VERSION >= 3 - PyImport_ImportModule("plpy"); -#endif - PLy_init_interp(); - PLy_init_plpy(); - if (PyErr_Occurred()) - PLy_elog(FATAL, "untrapped error in initialization"); - - memset(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(Oid); - hash_ctl.entrysize = sizeof(PLyProcedureEntry); - hash_ctl.hash = oid_hash; - PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl, - HASH_ELEM | HASH_FUNCTION); - - memset(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(Oid); - hash_ctl.entrysize = sizeof(PLyProcedureEntry); - hash_ctl.hash = oid_hash; - PLy_trigger_cache = hash_create("PL/Python triggers", 32, &hash_ctl, - HASH_ELEM | HASH_FUNCTION); - - explicit_subtransactions = NIL; - - inited = true; -} - -static void -PLy_init_interp(void) -{ - PyObject *mainmod; - - mainmod = PyImport_AddModule("__main__"); - if (mainmod == NULL || PyErr_Occurred()) - PLy_elog(ERROR, "could not import \"__main__\" module"); - Py_INCREF(mainmod); - PLy_interp_globals = PyModule_GetDict(mainmod); - PLy_interp_safe_globals = PyDict_New(); - PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals); - Py_DECREF(mainmod); - if (PLy_interp_globals == NULL || PyErr_Occurred()) - PLy_elog(ERROR, "could not initialize globals"); -} - -static void -PLy_init_plpy(void) -{ - PyObject *main_mod, - *main_dict, - *plpy_mod; -#if PY_MAJOR_VERSION < 3 - PyObject *plpy; -#endif - - /* - * initialize plpy module - */ - if (PyType_Ready(&PLy_PlanType) < 0) - elog(ERROR, "could not initialize PLy_PlanType"); - if (PyType_Ready(&PLy_ResultType) < 0) - elog(ERROR, "could not initialize PLy_ResultType"); - if (PyType_Ready(&PLy_SubtransactionType) < 0) - elog(ERROR, "could not initialize PLy_SubtransactionType"); - if (PyType_Ready(&PLy_CursorType) < 0) - elog(ERROR, "could not initialize PLy_CursorType"); - -#if PY_MAJOR_VERSION >= 3 - PyModule_Create(&PLy_module); - /* for Python 3 we initialized the exceptions in PyInit_plpy */ -#else - plpy = Py_InitModule("plpy", PLy_methods); - PLy_add_exceptions(plpy); -#endif - - /* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */ - - /* - * initialize main module, and add plpy - */ - main_mod = PyImport_AddModule("__main__"); - main_dict = PyModule_GetDict(main_mod); - plpy_mod = PyImport_AddModule("plpy"); - PyDict_SetItemString(main_dict, "plpy", plpy_mod); - if (PyErr_Occurred()) - elog(ERROR, "could not initialize plpy"); -} - -/* the python interface to the elog function - * don't confuse these with PLy_elog - */ -static PyObject *PLy_output(volatile int, PyObject *, PyObject *); - -static PyObject * -PLy_debug(PyObject *self, PyObject *args) -{ - return PLy_output(DEBUG2, self, args); -} - -static PyObject * -PLy_log(PyObject *self, PyObject *args) -{ - return PLy_output(LOG, self, args); -} - -static PyObject * -PLy_info(PyObject *self, PyObject *args) -{ - return PLy_output(INFO, self, args); -} - -static PyObject * -PLy_notice(PyObject *self, PyObject *args) -{ - return PLy_output(NOTICE, self, args); -} - -static PyObject * -PLy_warning(PyObject *self, PyObject *args) -{ - return PLy_output(WARNING, self, args); -} - -static PyObject * -PLy_error(PyObject *self, PyObject *args) -{ - return PLy_output(ERROR, self, args); -} - -static PyObject * -PLy_fatal(PyObject *self, PyObject *args) -{ - return PLy_output(FATAL, self, args); -} - - -static PyObject * -PLy_output(volatile int level, PyObject *self, PyObject *args) -{ - PyObject *volatile so; - char *volatile sv; - volatile MemoryContext oldcontext; - - if (PyTuple_Size(args) == 1) - { - /* - * Treat single argument specially to avoid undesirable ('tuple',) - * decoration. - */ - PyObject *o; - - PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o); - so = PyObject_Str(o); - } - else - so = PyObject_Str(args); - if (so == NULL || ((sv = PyString_AsString(so)) == NULL)) - { - level = ERROR; - sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog"); - } - - oldcontext = CurrentMemoryContext; - PG_TRY(); - { - pg_verifymbstr(sv, strlen(sv), false); - elog(level, "%s", sv); - } - PG_CATCH(); - { - ErrorData *edata; - - MemoryContextSwitchTo(oldcontext); - edata = CopyErrorData(); - FlushErrorState(); - - /* - * Note: If sv came from PyString_AsString(), it points into storage - * owned by so. So free so after using sv. - */ - Py_XDECREF(so); - - /* Make Python raise the exception */ - PLy_exception_set(PLy_exc_error, "%s", edata->message); - return NULL; - } - PG_END_TRY(); - - Py_XDECREF(so); - - /* - * return a legal object so the interpreter will continue on its merry way - */ - Py_INCREF(Py_None); - return Py_None; -} - - -static PyObject * -PLy_quote_literal(PyObject *self, PyObject *args) -{ - const char *str; - char *quoted; - PyObject *ret; - - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; - - quoted = quote_literal_cstr(str); - ret = PyString_FromString(quoted); - pfree(quoted); - - return ret; -} - -static PyObject * -PLy_quote_nullable(PyObject *self, PyObject *args) -{ - const char *str; - char *quoted; - PyObject *ret; - - if (!PyArg_ParseTuple(args, "z", &str)) - return NULL; - - if (str == NULL) - return PyString_FromString("NULL"); - - quoted = quote_literal_cstr(str); - ret = PyString_FromString(quoted); - pfree(quoted); - - return ret; -} - -static PyObject * -PLy_quote_ident(PyObject *self, PyObject *args) -{ - const char *str; - const char *quoted; - PyObject *ret; - - if (!PyArg_ParseTuple(args, "s", &str)) - return NULL; - - quoted = quote_identifier(str); - ret = PyString_FromString(quoted); - - return ret; -} - - -/* - * Get the name of the last procedure called by the backend (the - * innermost, if a plpython procedure call calls the backend and the - * backend calls another plpython procedure). - * - * NB: this returns the SQL name, not the internal Python procedure name - */ -static char * -PLy_procedure_name(PLyProcedure *proc) -{ - if (proc == NULL) - return "<unknown procedure>"; - return proc->proname; -} - -/* - * Call PyErr_SetString with a vprint interface and translation support - */ -static void -PLy_exception_set(PyObject *exc, const char *fmt,...) -{ - char buf[1024]; - va_list ap; - - va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap); - va_end(ap); - - PyErr_SetString(exc, buf); -} - -/* - * The same, pluralized. - */ -static void -PLy_exception_set_plural(PyObject *exc, - const char *fmt_singular, const char *fmt_plural, - unsigned long n,...) -{ - char buf[1024]; - va_list ap; - - va_start(ap, n); - vsnprintf(buf, sizeof(buf), - dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n), - ap); - va_end(ap); - - PyErr_SetString(exc, buf); -} - -/* - * Raise a SPIError, passing in it more error details, like the - * internal query and error position. - */ -static void -PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) -{ - PyObject *args = NULL; - PyObject *spierror = NULL; - PyObject *spidata = NULL; - - args = Py_BuildValue("(s)", edata->message); - if (!args) - goto failure; - - /* create a new SPI exception with the error message as the parameter */ - spierror = PyObject_CallObject(excclass, args); - if (!spierror) - goto failure; - - spidata = Py_BuildValue("(izzzi)", edata->sqlerrcode, edata->detail, edata->hint, - edata->internalquery, edata->internalpos); - if (!spidata) - goto failure; - - if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1) - goto failure; - - PyErr_SetObject(excclass, spierror); - - Py_DECREF(args); - Py_DECREF(spierror); - Py_DECREF(spidata); - return; - -failure: - Py_XDECREF(args); - Py_XDECREF(spierror); - Py_XDECREF(spidata); - elog(ERROR, "could not convert SPI error to Python exception"); -} - -/* Emit a PG error or notice, together with any available info about - * the current Python error, previously set by PLy_exception_set(). - * This should be used to propagate Python errors into PG. If fmt is - * NULL, the Python error becomes the primary error message, otherwise - * it becomes the detail. If there is a Python traceback, it is put - * in the context. - */ -static void -PLy_elog(int elevel, const char *fmt,...) -{ - char *xmsg; - char *tbmsg; - int tb_depth; - StringInfoData emsg; - PyObject *exc, - *val, - *tb; - const char *primary = NULL; - int sqlerrcode = 0; - char *detail = NULL; - char *hint = NULL; - char *query = NULL; - int position = 0; - - PyErr_Fetch(&exc, &val, &tb); - if (exc != NULL) - { - if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) - PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position); - else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) - elevel = FATAL; - } - PyErr_Restore(exc, val, tb); - - PLy_traceback(&xmsg, &tbmsg, &tb_depth); - - if (fmt) - { - initStringInfo(&emsg); - for (;;) - { - va_list ap; - bool success; - - va_start(ap, fmt); - success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); - va_end(ap); - if (success) - break; - enlargeStringInfo(&emsg, emsg.maxlen); - } - primary = emsg.data; - - /* Since we have a format string, we cannot have a SPI detail. */ - Assert(detail == NULL); - - /* If there's an exception message, it goes in the detail. */ - if (xmsg) - detail = xmsg; - } - else - { - if (xmsg) - primary = xmsg; - } - - PG_TRY(); - { - ereport(elevel, - (errcode(sqlerrcode ? sqlerrcode : ERRCODE_INTERNAL_ERROR), - errmsg_internal("%s", primary ? primary : "no exception data"), - (detail) ? errdetail_internal("%s", detail) : 0, - (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, - (hint) ? errhint("%s", hint) : 0, - (query) ? internalerrquery(query) : 0, - (position) ? internalerrposition(position) : 0)); - } - PG_CATCH(); - { - if (fmt) - pfree(emsg.data); - if (xmsg) - pfree(xmsg); - if (tbmsg) - pfree(tbmsg); - PG_RE_THROW(); - } - PG_END_TRY(); - - if (fmt) - pfree(emsg.data); - if (xmsg) - pfree(xmsg); - if (tbmsg) - pfree(tbmsg); -} - -/* - * Extract the error data from a SPIError - */ -static void -PLy_get_spi_error_data(PyObject *exc, int* sqlerrcode, char **detail, char **hint, char **query, int *position) -{ - PyObject *spidata = NULL; - - spidata = PyObject_GetAttrString(exc, "spidata"); - if (!spidata) - goto cleanup; - - if (!PyArg_ParseTuple(spidata, "izzzi", sqlerrcode, detail, hint, query, position)) - goto cleanup; - -cleanup: - PyErr_Clear(); - /* no elog here, we simply won't report the errhint, errposition etc */ - Py_XDECREF(spidata); -} - -/* - * Get the given source line as a palloc'd string - */ -static char * -get_source_line(const char *src, int lineno) -{ - const char *s = NULL; - const char *next = src; - int current = 0; - - while (current < lineno) - { - s = next; - next = strchr(s + 1, '\n'); - current++; - if (next == NULL) - break; - } - - if (current != lineno) - return NULL; - - while (*s && isspace((unsigned char) *s)) - s++; - - if (next == NULL) - return pstrdup(s); - - /* - * Sanity check, next < s if the line was all-whitespace, which should - * never happen if Python reported a frame created on that line, but check - * anyway. - */ - if (next < s) - return NULL; - - return pnstrdup(s, next - s); -} - -/* - * Extract a Python traceback from the current exception. - * - * The exception error message is returned in xmsg, the traceback in - * tbmsg (both as palloc'd strings) and the traceback depth in - * tb_depth. - */ -static void -PLy_traceback(char **xmsg, char **tbmsg, int *tb_depth) -{ - PyObject *e, - *v, - *tb; - PyObject *e_type_o; - PyObject *e_module_o; - char *e_type_s = NULL; - char *e_module_s = NULL; - PyObject *vob = NULL; - char *vstr; - StringInfoData xstr; - StringInfoData tbstr; - - /* - * get the current exception - */ - PyErr_Fetch(&e, &v, &tb); - - /* - * oops, no exception, return - */ - if (e == NULL) - { - *xmsg = NULL; - *tbmsg = NULL; - *tb_depth = 0; - - return; - } - - PyErr_NormalizeException(&e, &v, &tb); - - /* - * Format the exception and its value and put it in xmsg. - */ - - e_type_o = PyObject_GetAttrString(e, "__name__"); - e_module_o = PyObject_GetAttrString(e, "__module__"); - if (e_type_o) - e_type_s = PyString_AsString(e_type_o); - if (e_type_s) - e_module_s = PyString_AsString(e_module_o); - - if (v && ((vob = PyObject_Str(v)) != NULL)) - vstr = PyString_AsString(vob); - else - vstr = "unknown"; - - initStringInfo(&xstr); - if (!e_type_s || !e_module_s) - { - if (PyString_Check(e)) - /* deprecated string exceptions */ - appendStringInfoString(&xstr, PyString_AsString(e)); - else - /* shouldn't happen */ - appendStringInfoString(&xstr, "unrecognized exception"); - } - /* mimics behavior of traceback.format_exception_only */ - else if (strcmp(e_module_s, "builtins") == 0 - || strcmp(e_module_s, "__main__") == 0 - || strcmp(e_module_s, "exceptions") == 0) - appendStringInfo(&xstr, "%s", e_type_s); - else - appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); - appendStringInfo(&xstr, ": %s", vstr); - - *xmsg = xstr.data; - - /* - * Now format the traceback and put it in tbmsg. - */ - - *tb_depth = 0; - initStringInfo(&tbstr); - /* Mimick Python traceback reporting as close as possible. */ - appendStringInfoString(&tbstr, "Traceback (most recent call last):"); - while (tb != NULL && tb != Py_None) - { - PyObject *volatile tb_prev = NULL; - PyObject *volatile frame = NULL; - PyObject *volatile code = NULL; - PyObject *volatile name = NULL; - PyObject *volatile lineno = NULL; - PyObject *volatile filename = NULL; - - PG_TRY(); - { - lineno = PyObject_GetAttrString(tb, "tb_lineno"); - if (lineno == NULL) - elog(ERROR, "could not get line number from Python traceback"); - - frame = PyObject_GetAttrString(tb, "tb_frame"); - if (frame == NULL) - elog(ERROR, "could not get frame from Python traceback"); - - code = PyObject_GetAttrString(frame, "f_code"); - if (code == NULL) - elog(ERROR, "could not get code object from Python frame"); - - name = PyObject_GetAttrString(code, "co_name"); - if (name == NULL) - elog(ERROR, "could not get function name from Python code object"); - - filename = PyObject_GetAttrString(code, "co_filename"); - if (filename == NULL) - elog(ERROR, "could not get file name from Python code object"); - } - PG_CATCH(); - { - Py_XDECREF(frame); - Py_XDECREF(code); - Py_XDECREF(name); - Py_XDECREF(lineno); - Py_XDECREF(filename); - PG_RE_THROW(); - } - PG_END_TRY(); - - /* The first frame always points at <module>, skip it. */ - if (*tb_depth > 0) - { - char *proname; - char *fname; - char *line; - char *plain_filename; - long plain_lineno; - - /* - * The second frame points at the internal function, but to mimick - * Python error reporting we want to say <module>. - */ - if (*tb_depth == 1) - fname = "<module>"; - else - fname = PyString_AsString(name); - - proname = PLy_procedure_name(PLy_curr_procedure); - plain_filename = PyString_AsString(filename); - plain_lineno = PyInt_AsLong(lineno); - - if (proname == NULL) - appendStringInfo( - &tbstr, "\n PL/Python anonymous code block, line %ld, in %s", - plain_lineno - 1, fname); - else - appendStringInfo( - &tbstr, "\n PL/Python function \"%s\", line %ld, in %s", - proname, plain_lineno - 1, fname); - - /* - * function code object was compiled with "<string>" as the - * filename - */ - if (PLy_curr_procedure && plain_filename != NULL && - strcmp(plain_filename, "<string>") == 0) - { - /* - * If we know the current procedure, append the exact line - * from the source, again mimicking Python's traceback.py - * module behavior. We could store the already line-split - * source to avoid splitting it every time, but producing a - * traceback is not the most important scenario to optimize - * for. But we do not go as far as traceback.py in reading - * the source of imported modules. - */ - line = get_source_line(PLy_curr_procedure->src, plain_lineno); - if (line) - { - appendStringInfo(&tbstr, "\n %s", line); - pfree(line); - } - } - } - - Py_DECREF(frame); - Py_DECREF(code); - Py_DECREF(name); - Py_DECREF(lineno); - Py_DECREF(filename); - - /* Release the current frame and go to the next one. */ - tb_prev = tb; - tb = PyObject_GetAttrString(tb, "tb_next"); - Assert(tb_prev != Py_None); - Py_DECREF(tb_prev); - if (tb == NULL) - elog(ERROR, "could not traverse Python traceback"); - (*tb_depth)++; - } - - /* Return the traceback. */ - *tbmsg = tbstr.data; - - Py_XDECREF(e_type_o); - Py_XDECREF(e_module_o); - Py_XDECREF(vob); - Py_XDECREF(v); - Py_DECREF(e); -} - -/* python module code */ - -/* some dumb utility functions */ -static void * -PLy_malloc(size_t bytes) -{ - /* We need our allocations to be long-lived, so use TopMemoryContext */ - return MemoryContextAlloc(TopMemoryContext, bytes); -} - -static void * -PLy_malloc0(size_t bytes) -{ - void *ptr = PLy_malloc(bytes); - - MemSet(ptr, 0, bytes); - return ptr; -} - -static char * -PLy_strdup(const char *str) -{ - char *result; - size_t len; - - len = strlen(str) + 1; - result = PLy_malloc(len); - memcpy(result, str, len); - - return result; -} - -/* define this away */ -static void -PLy_free(void *ptr) -{ - pfree(ptr); -} - -/* - * Convert a Python unicode object to a Python string/bytes object in - * PostgreSQL server encoding. Reference ownership is passed to the - * caller. - */ -static PyObject * -PLyUnicode_Bytes(PyObject *unicode) -{ - PyObject *rv; - const char *serverenc; - - /* - * Python understands almost all PostgreSQL encoding names, but it doesn't - * know SQL_ASCII. - */ - if (GetDatabaseEncoding() == PG_SQL_ASCII) - serverenc = "ascii"; - else - serverenc = GetDatabaseEncodingName(); - rv = PyUnicode_AsEncodedString(unicode, serverenc, "strict"); - if (rv == NULL) - PLy_elog(ERROR, "could not convert Python Unicode object to PostgreSQL server encoding"); - return rv; -} - -/* - * Convert a Python unicode object to a C string in PostgreSQL server - * encoding. No Python object reference is passed out of this - * function. The result is palloc'ed. - * - * Note that this function is disguised as PyString_AsString() when - * using Python 3. That function retuns a pointer into the internal - * memory of the argument, which isn't exactly the interface of this - * function. But in either case you get a rather short-lived - * reference that you ought to better leave alone. - */ -static char * -PLyUnicode_AsString(PyObject *unicode) -{ - PyObject *o = PLyUnicode_Bytes(unicode); - char *rv = pstrdup(PyBytes_AsString(o)); - - Py_XDECREF(o); - return rv; -} - -#if PY_MAJOR_VERSION >= 3 -/* - * Convert a C string in the PostgreSQL server encoding to a Python - * unicode object. Reference ownership is passed to the caller. - */ -static PyObject * -PLyUnicode_FromString(const char *s) -{ - char *utf8string; - PyObject *o; - - utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s, - strlen(s), - GetDatabaseEncoding(), - PG_UTF8); - - o = PyUnicode_FromString(utf8string); - - if (utf8string != s) - pfree(utf8string); - - return o; -} -#endif /* PY_MAJOR_VERSION >= 3 */ - -#if PY_MAJOR_VERSION < 3 - -/* Define aliases plpython2_call_handler etc */ -Datum plpython2_call_handler(PG_FUNCTION_ARGS); -Datum plpython2_inline_handler(PG_FUNCTION_ARGS); -Datum plpython2_validator(PG_FUNCTION_ARGS); - -PG_FUNCTION_INFO_V1(plpython2_call_handler); - -Datum -plpython2_call_handler(PG_FUNCTION_ARGS) -{ - return plpython_call_handler(fcinfo); -} - -PG_FUNCTION_INFO_V1(plpython2_inline_handler); - -Datum -plpython2_inline_handler(PG_FUNCTION_ARGS) -{ - return plpython_inline_handler(fcinfo); -} - -PG_FUNCTION_INFO_V1(plpython2_validator); - -Datum -plpython2_validator(PG_FUNCTION_ARGS) -{ - return plpython_validator(fcinfo); -} - -#endif /* PY_MAJOR_VERSION < 3 */ |