plpython-refactor.diff
text/x-patch
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/pl/plpython/expected/plpython_do.out | 0 | 0 |
| src/pl/plpython/expected/plpython_error.out | 0 | 0 |
| src/pl/plpython/expected/plpython_test.out | 0 | 0 |
| src/pl/plpython/expected/plpython_types_3.out | 0 | 0 |
| src/pl/plpython/expected/plpython_types.out | 0 | 0 |
| src/pl/plpython/plpython.c | 0 | 0 |
| src/pl/plpython/sql/plpython_test.sql | 0 | 0 |
diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out
index 9de261a..a21b088 100644
*** a/src/pl/plpython/expected/plpython_do.out
--- b/src/pl/plpython/expected/plpython_do.out
*************** DO $$ plpy.notice("This is plpythonu.")
*** 2,6 ****
NOTICE: This is plpythonu.
CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
! ERROR: PL/Python: NameError: global name 'nonsense' is not defined
CONTEXT: PL/Python anonymous code block
--- 2,6 ----
NOTICE: This is plpythonu.
CONTEXT: PL/Python anonymous code block
DO $$ nonsense $$ LANGUAGE plpythonu;
! ERROR: NameError: global name 'nonsense' is not defined
CONTEXT: PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index 1f24c13..70890a8 100644
*** a/src/pl/plpython/expected/plpython_error.out
--- b/src/pl/plpython/expected/plpython_error.out
*************** CREATE FUNCTION sql_syntax_error() RETUR
*** 8,19 ****
'plpy.execute("syntax error")'
LANGUAGE plpythonu;
SELECT sql_syntax_error();
! WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_execute_query
CONTEXT: PL/Python function "sql_syntax_error"
! ERROR: syntax error at or near "syntax"
! LINE 1: syntax error
! ^
! QUERY: syntax error
CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
--- 8,16 ----
'plpy.execute("syntax error")'
LANGUAGE plpythonu;
SELECT sql_syntax_error();
! WARNING: plpy.SPIError: unrecognized error in PLy_spi_execute_query
CONTEXT: PL/Python function "sql_syntax_error"
! ERROR: plpy.SPIError: syntax error at or near "syntax"
CONTEXT: PL/Python function "sql_syntax_error"
/* check the handling of uncaught python exceptions
*/
*************** CREATE FUNCTION exception_index_invalid(
*** 22,28 ****
'return args[1]'
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
! ERROR: PL/Python: IndexError: list index out of range
CONTEXT: PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
--- 19,25 ----
'return args[1]'
LANGUAGE plpythonu;
SELECT exception_index_invalid('test');
! ERROR: IndexError: list index out of range
CONTEXT: PL/Python function "exception_index_invalid"
/* check handling of nested exceptions
*/
*************** CREATE FUNCTION exception_index_invalid_
*** 32,44 ****
return rv[0]'
LANGUAGE plpythonu;
SELECT exception_index_invalid_nested();
! WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_execute_query
CONTEXT: PL/Python function "exception_index_invalid_nested"
! ERROR: function test5(unknown) does not exist
! LINE 1: SELECT test5('foo')
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
! QUERY: SELECT test5('foo')
CONTEXT: PL/Python function "exception_index_invalid_nested"
/* a typo
*/
--- 29,37 ----
return rv[0]'
LANGUAGE plpythonu;
SELECT exception_index_invalid_nested();
! WARNING: plpy.SPIError: unrecognized error in PLy_spi_execute_query
CONTEXT: PL/Python function "exception_index_invalid_nested"
! ERROR: plpy.SPIError: function test5(unknown) does not exist
CONTEXT: PL/Python function "exception_index_invalid_nested"
/* a typo
*/
*************** return None
*** 54,62 ****
'
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
! WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_uncaught"
! ERROR: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
--- 47,55 ----
'
LANGUAGE plpythonu;
SELECT invalid_type_uncaught('rick');
! WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_uncaught"
! ERROR: plpy.SPIError: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_uncaught"
/* for what it's worth catch the exception generated by
* the typo, and return None
*************** return None
*** 77,86 ****
'
LANGUAGE plpythonu;
SELECT invalid_type_caught('rick');
! WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_caught"
! ERROR: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_caught"
/* for what it's worth catch the exception generated by
* the typo, and reraise it as a plain error
*/
--- 70,84 ----
'
LANGUAGE plpythonu;
SELECT invalid_type_caught('rick');
! WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_caught"
! NOTICE: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_caught"
+ invalid_type_caught
+ ---------------------
+
+ (1 row)
+
/* for what it's worth catch the exception generated by
* the typo, and reraise it as a plain error
*/
*************** return None
*** 99,107 ****
'
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
! WARNING: PL/Python: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_reraised"
! ERROR: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
--- 97,105 ----
'
LANGUAGE plpythonu;
SELECT invalid_type_reraised('rick');
! WARNING: plpy.SPIError: unrecognized error in PLy_spi_prepare
CONTEXT: PL/Python function "invalid_type_reraised"
! ERROR: plpy.Error: type "test" does not exist
CONTEXT: PL/Python function "invalid_type_reraised"
/* no typo no messing about
*/
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
index a229b18..d92c987 100644
*** a/src/pl/plpython/expected/plpython_test.out
--- b/src/pl/plpython/expected/plpython_test.out
*************** select argument_test_one(users, fname, l
*** 35,40 ****
--- 35,53 ----
willem doe => {fname: willem, lname: doe, userid: 3, username: w_doe}
(3 rows)
+ -- check module contents
+ CREATE FUNCTION module_contents() RETURNS text AS
+ $$
+ contents = list(filter(lambda x: not x.startswith("__"), dir(plpy)))
+ contents.sort()
+ return ", ".join(contents)
+ $$ LANGUAGE plpythonu;
+ select module_contents();
+ module_contents
+ -------------------------------------------------------------------------------------------
+ Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, warning
+ (1 row)
+
CREATE FUNCTION elog_test() RETURNS void
AS $$
plpy.debug('debug')
*************** NOTICE: notice
*** 60,64 ****
CONTEXT: PL/Python function "elog_test"
WARNING: warning
CONTEXT: PL/Python function "elog_test"
! ERROR: error
CONTEXT: PL/Python function "elog_test"
--- 73,77 ----
CONTEXT: PL/Python function "elog_test"
WARNING: warning
CONTEXT: PL/Python function "elog_test"
! ERROR: plpy.Error: error
CONTEXT: PL/Python function "elog_test"
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index a165936..982005b 100644
*** a/src/pl/plpython/expected/plpython_types.out
--- b/src/pl/plpython/expected/plpython_types.out
*************** CREATE FUNCTION test_type_conversion_arr
*** 596,602 ****
return 5
$$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_error();
! ERROR: PL/Python: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_error"
--
--- 596,602 ----
return 5
$$ LANGUAGE plpythonu;
SELECT * FROM test_type_conversion_array_error();
! ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_error"
--
diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out
index 38ddf02..eeb23b7 100644
*** a/src/pl/plpython/expected/plpython_types_3.out
--- b/src/pl/plpython/expected/plpython_types_3.out
*************** CREATE FUNCTION test_type_conversion_arr
*** 596,602 ****
return 5
$$ LANGUAGE plpython3u;
SELECT * FROM test_type_conversion_array_error();
! ERROR: PL/Python: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_error"
--
--- 596,602 ----
return 5
$$ LANGUAGE plpython3u;
SELECT * FROM test_type_conversion_array_error();
! ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_error"
--
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 1ae1239..67eb0f3 100644
*** a/src/pl/plpython/plpython.c
--- b/src/pl/plpython/plpython.c
*************** typedef struct PLyProcedure
*** 214,223 ****
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global scope */
- PyObject *me; /* PyCObject containing pointer to this
- * PLyProcedure */
} PLyProcedure;
/* Python objects */
typedef struct PLyPlanObject
--- 214,227 ----
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;
/* Python objects */
typedef struct PLyPlanObject
*************** static HeapTuple PLy_modify_tuple(PLyPro
*** 311,321 ****
static PyObject *PLy_procedure_call(PLyProcedure *, char *, PyObject *);
! static PLyProcedure *PLy_procedure_get(FunctionCallInfo fcinfo,
! Oid tgreloid);
! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid tgreloid,
! char *key);
static void PLy_procedure_compile(PLyProcedure *, const char *);
static char *PLy_procedure_munge_source(const char *, const char *);
--- 315,324 ----
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 HeapTuple PLyObject_ToTuple(PLyTy
*** 358,379 ****
*/
static PLyProcedure *PLy_curr_procedure = NULL;
- /*
- * When a callback from Python into PG incurs an error, we temporarily store
- * the error information here, and return NULL to the Python interpreter.
- * Any further callback attempts immediately fail, and when the Python
- * interpreter returns to the calling function, we re-throw the error (even if
- * Python thinks it trapped the error and doesn't return NULL). Eventually
- * this ought to be improved to let Python code really truly trap the error,
- * but that's more of a change from the pre-8.0 semantics than I have time for
- * now --- it will only be possible if the callback query is executed inside a
- * subtransaction.
- */
- static ErrorData *PLy_error_in_progress = NULL;
static PyObject *PLy_interp_globals = NULL;
static PyObject *PLy_interp_safe_globals = NULL;
! static PyObject *PLy_procedure_cache = NULL;
/* Python exceptions */
static PyObject *PLy_exc_error = NULL;
--- 361,371 ----
*/
static PLyProcedure *PLy_curr_procedure = NULL;
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;
*************** plpython_call_handler(PG_FUNCTION_ARGS)
*** 444,450 ****
{
Datum retval;
PLyProcedure *save_curr_proc;
! PLyProcedure *volatile proc = NULL;
ErrorContextCallback plerrcontext;
if (SPI_connect() != SPI_OK_CONNECT)
--- 436,442 ----
{
Datum retval;
PLyProcedure *save_curr_proc;
! PLyProcedure *proc = NULL;
ErrorContextCallback plerrcontext;
if (SPI_connect() != SPI_OK_CONNECT)
*************** plpython_call_handler(PG_FUNCTION_ARGS)
*** 463,480 ****
{
if (CALLED_AS_TRIGGER(fcinfo))
{
- TriggerData *tdata = (TriggerData *) fcinfo->context;
HeapTuple trv;
! proc = PLy_procedure_get(fcinfo,
! RelationGetRelid(tdata->tg_relation));
PLy_curr_procedure = proc;
trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
! proc = PLy_procedure_get(fcinfo, InvalidOid);
PLy_curr_procedure = proc;
retval = PLy_function_handler(fcinfo, proc);
}
--- 455,470 ----
{
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);
}
*************** plpython_call_handler(PG_FUNCTION_ARGS)
*** 482,492 ****
PG_CATCH();
{
PLy_curr_procedure = save_curr_proc;
- if (proc)
- {
- /* note: Py_DECREF needs braces around it, as of 2003/08 */
- Py_DECREF(proc->me);
- }
PyErr_Clear();
PG_RE_THROW();
}
--- 472,477 ----
*************** plpython_call_handler(PG_FUNCTION_ARGS)
*** 497,504 ****
PLy_curr_procedure = save_curr_proc;
- Py_DECREF(proc->me);
-
return retval;
}
--- 482,487 ----
*************** static HeapTuple
*** 573,580 ****
PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
{
HeapTuple rv = NULL;
! PyObject *volatile plargs = NULL;
! PyObject *volatile plrv = NULL;
PG_TRY();
{
--- 556,578 ----
PLy_trigger_handler(FunctionCallInfo fcinfo, PLyProcedure *proc)
{
HeapTuple rv = NULL;
! PyObject *volatile plargs = NULL;
! PyObject *volatile plrv = NULL;
! TriggerData *tdata;
!
! AssertMacro(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();
{
*************** PLy_trigger_handler(FunctionCallInfo fci
*** 582,588 ****
plrv = PLy_procedure_call(proc, "TD", plargs);
Assert(plrv != NULL);
- Assert(!PLy_error_in_progress);
/*
* Disconnect from SPI manager
--- 580,585 ----
*************** PLy_function_handler(FunctionCallInfo fc
*** 992,1005 ****
plargs = PLy_function_build_args(fcinfo, proc);
plrv = PLy_procedure_call(proc, "args", plargs);
if (!proc->is_setof)
!
/*
* SETOF function parameters will be deleted when last row is
* returned
*/
PLy_function_delete_args(proc);
Assert(plrv != NULL);
- Assert(!PLy_error_in_progress);
}
/*
--- 989,1002 ----
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);
}
/*
*************** PLy_function_handler(FunctionCallInfo fc
*** 1149,1154 ****
--- 1146,1159 ----
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();
*************** PLy_procedure_call(PLyProcedure *proc, c
*** 1174,1192 ****
* If there was an error in a PG callback, propagate that no matter what
* Python claims about its success.
*/
- if (PLy_error_in_progress)
- {
- ErrorData *edata = PLy_error_in_progress;
-
- PLy_error_in_progress = NULL;
- ReThrowError(edata);
- }
! if (rv == NULL || PyErr_Occurred())
! {
! Py_XDECREF(rv);
PLy_elog(ERROR, NULL);
- }
return rv;
}
--- 1179,1188 ----
* If there was an error in a PG callback, propagate that no matter what
* Python claims about its success.
*/
! /* if the Python code returned an error, propagate it */
! if (rv == NULL)
PLy_elog(ERROR, NULL);
return rv;
}
*************** PLy_function_delete_args(PLyProcedure *p
*** 1286,1291 ****
--- 1282,1301 ----
}
+ /* Decide if a cached PLyProcedure struct is still valid */
+ static bool
+ PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
+ {
+ 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;
+
+ return true;
+ }
+
/*
* PLyProcedure functions
*/
*************** PLy_function_delete_args(PLyProcedure *p
*** 1296,1368 ****
* function calls.
*/
static PLyProcedure *
! PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid)
{
! Oid fn_oid;
! HeapTuple procTup;
! char key[128];
! PyObject *plproc;
! PLyProcedure *proc = NULL;
! int rv;
- fn_oid = fcinfo->flinfo->fn_oid;
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
if (!HeapTupleIsValid(procTup))
elog(ERROR, "cache lookup failed for function %u", fn_oid);
! rv = snprintf(key, sizeof(key), "%u_%u", fn_oid, tgreloid);
! if (rv >= sizeof(key) || rv < 0)
! elog(ERROR, "key too long");
!
! plproc = PyDict_GetItemString(PLy_procedure_cache, key);
! if (plproc != NULL)
{
! Py_INCREF(plproc);
! if (!PyCObject_Check(plproc))
! elog(FATAL, "expected a PyCObject, didn't get one");
!
! proc = PyCObject_AsVoidPtr(plproc);
! if (!proc)
! PLy_elog(ERROR, "PyCObject_AsVoidPtr() failed");
! if (proc->me != plproc)
! elog(FATAL, "proc->me != plproc");
! /* did we find an up-to-date cache entry? */
! if (proc->fn_xmin != HeapTupleHeaderGetXmin(procTup->t_data) ||
! !ItemPointerEquals(&proc->fn_tid, &procTup->t_self))
{
! Py_DECREF(plproc);
! proc = NULL;
}
}
! if (proc == NULL)
! proc = PLy_procedure_create(procTup, tgreloid, key);
! if (OidIsValid(tgreloid))
{
/*
! * 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.
*/
! TriggerData *tdata = (TriggerData *) fcinfo->context;
! Assert(CALLED_AS_TRIGGER(fcinfo));
! PLy_input_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
! PLy_output_tuple_funcs(&(proc->result), tdata->tg_relation->rd_att);
}
! ReleaseSysCache(procTup);
! return proc;
}
static PLyProcedure *
! PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
{
char procName[NAMEDATALEN + 256];
Form_pg_proc procStruct;
--- 1306,1492 ----
* function calls.
*/
static PLyProcedure *
! PLy_procedure_get(Oid fn_oid, bool is_trigger)
{
! HeapTuple procTup;
! PLyProcedureEntry *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;
! }
!
! /* Set up output conversion functions for a procedure */
! static void
! PLy_procedure_output_conversion(PLyProcedure *proc, Form_pg_proc procStruct)
! {
! HeapTuple rvTypeTup;
! Form_pg_type rvTypeStruct;
!
! /* Get the return type */
! 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 */
! if (rvTypeStruct->typtype == TYPTYPE_PSEUDO &&
! procStruct->prorettype != VOIDOID)
! {
! if (procStruct->prorettype == TRIGGEROID)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("trigger functions can only be called as triggers")));
! else
! 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)
{
/*
! * Tuple: set up later, during first call to
! * PLy_function_handler
*/
! proc->result.out.d.typoid = procStruct->prorettype;
! proc->result.is_rowtype = 2;
! }
! else
! {
! /* Do the real work */
! PLy_output_datum_func(&proc->result, rvTypeTup);
! }
! ReleaseSysCache(rvTypeTup);
! }
!
! /* Set up output conversion functions for a procedure */
! static void
! PLy_procedure_input_conversion(PLyProcedure *proc, HeapTuple procTup,
! Form_pg_proc procStruct)
! {
! 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++;
! }
}
+ /* 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;
*************** PLy_procedure_create(HeapTuple procTup,
*** 1373,1395 ****
int i,
rv;
! procStruct = (Form_pg_proc) GETSTRUCT(procTup);
! if (OidIsValid(tgreloid))
! rv = snprintf(procName, sizeof(procName),
! "__plpython_procedure_%s_%u_trigger_%u",
! NameStr(procStruct->proname),
! HeapTupleGetOid(procTup),
! tgreloid);
! else
! rv = snprintf(procName, sizeof(procName),
! "__plpython_procedure_%s_%u",
! NameStr(procStruct->proname),
! HeapTupleGetOid(procTup));
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);
--- 1497,1512 ----
int i,
rv;
! proc = PLy_malloc(sizeof(PLyProcedure));
! 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->proname = PLy_strdup(NameStr(procStruct->proname));
proc->pyname = PLy_strdup(procName);
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
*************** PLy_procedure_create(HeapTuple procTup,
*** 1402,1408 ****
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
proc->code = proc->statics = NULL;
! proc->globals = proc->me = NULL;
proc->is_setof = procStruct->proretset;
proc->setof = NULL;
proc->argnames = NULL;
--- 1519,1525 ----
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->argnames = NULL;
*************** PLy_procedure_create(HeapTuple procTup,
*** 1413,1459 ****
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
! if (!OidIsValid(tgreloid))
! {
! 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 */
! if (rvTypeStruct->typtype == TYPTYPE_PSEUDO &&
! procStruct->prorettype != VOIDOID)
! {
! if (procStruct->prorettype == TRIGGEROID)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("trigger functions can only be called as triggers")));
! else
! 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)
! {
! /*
! * Tuple: set up later, during first call to
! * PLy_function_handler
! */
! proc->result.out.d.typoid = procStruct->prorettype;
! proc->result.is_rowtype = 2;
! }
! else
! PLy_output_datum_func(&proc->result, rvTypeTup);
!
! ReleaseSysCache(rvTypeTup);
! }
/*
* Now get information required for input conversion of the
--- 1530,1537 ----
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
! if (!is_trigger)
! PLy_procedure_output_conversion(proc, procStruct);
/*
* Now get information required for input conversion of the
*************** PLy_procedure_create(HeapTuple procTup,
*** 1463,1541 ****
* arguments.
*/
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.
--- 1541,1547 ----
* arguments.
*/
if (procStruct->pronargs)
! PLy_procedure_input_conversion(proc, procTup, procStruct);
/*
* get the text of the function.
*************** PLy_procedure_create(HeapTuple procTup,
*** 1550,1560 ****
pfree(procSource);
procSource = NULL;
-
- proc->me = PyCObject_FromVoidPtr(proc, NULL);
- if (!proc->me)
- PLy_elog(ERROR, "PyCObject_FromVoidPtr() failed");
- PyDict_SetItemString(PLy_procedure_cache, key, proc->me);
}
PG_CATCH();
{
--- 1556,1561 ----
*************** PLy_procedure_create(HeapTuple procTup,
*** 1569,1574 ****
--- 1570,1576 ----
return proc;
}
+ /* Insert the procedure into the Python interpreter */
static void
PLy_procedure_compile(PLyProcedure *proc, const char *src)
{
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1589,1597 ****
*/
msrc = PLy_procedure_munge_source(proc->pyname, src);
crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
! free(msrc);
! if (crv != NULL && (!PyErr_Occurred()))
{
int clen;
char call[NAMEDATALEN + 256];
--- 1591,1599 ----
*/
msrc = PLy_procedure_munge_source(proc->pyname, src);
crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
! pfree(msrc);
! if (crv != NULL)
{
int clen;
char call[NAMEDATALEN + 256];
*************** PLy_procedure_compile(PLyProcedure *proc
*** 1605,1617 ****
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 && (!PyErr_Occurred()))
return;
}
- else
- Py_XDECREF(crv);
! PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", proc->proname);
}
static char *
--- 1607,1621 ----
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 *n
*** 1628,1634 ****
*/
mlen = (strlen(src) * 2) + strlen(name) + 16;
! mrc = PLy_malloc(mlen);
plen = snprintf(mrc, mlen, "def %s():\n\t", name);
Assert(plen >= 0 && plen < mlen);
--- 1632,1638 ----
*/
mlen = (strlen(src) * 2) + strlen(name) + 16;
! mrc = palloc(mlen);
plen = snprintf(mrc, mlen, "def %s():\n\t", name);
Assert(plen >= 0 && plen < mlen);
*************** PLy_procedure_delete(PLyProcedure *proc)
*** 1667,1673 ****
Py_XDECREF(proc->code);
Py_XDECREF(proc->statics);
Py_XDECREF(proc->globals);
- Py_XDECREF(proc->me);
if (proc->proname)
PLy_free(proc->proname);
if (proc->pyname)
--- 1671,1676 ----
*************** PLy_procedure_delete(PLyProcedure *proc)
*** 1686,1692 ****
}
if (proc->argnames)
PLy_free(proc->argnames);
- PLy_free(proc);
}
/*
--- 1689,1694 ----
*************** PLyMapping_ToTuple(PLyTypeInfo *info, Py
*** 2289,2294 ****
--- 2291,2299 ----
PyObject *volatile value;
PLyObToDatum *att;
+ if (desc->attrs[i]->attisdropped)
+ continue;
+
key = NameStr(desc->attrs[i]->attname);
value = NULL;
att = &info->out.r.atts[i];
*************** PLySequence_ToTuple(PLyTypeInfo *info, P
*** 2339,2355 ****
HeapTuple tuple;
Datum *values;
bool *nulls;
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
*/
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
! if (PySequence_Length(sequence) != desc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("length of returned sequence did not match number of columns in row")));
--- 2344,2366 ----
HeapTuple tuple;
Datum *values;
bool *nulls;
+ 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
*/
desc = lookup_rowtype_tupdesc(info->out.d.typoid, -1);
! 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")));
*************** PLySequence_ToTuple(PLyTypeInfo *info, P
*** 2361,2376 ****
/* Build tuple */
values = palloc(sizeof(Datum) * desc->natts);
nulls = palloc(sizeof(bool) * desc->natts);
for (i = 0; i < desc->natts; ++i)
{
PyObject *volatile value;
PLyObToDatum *att;
value = NULL;
att = &info->out.r.atts[i];
PG_TRY();
{
! value = PySequence_GetItem(sequence, i);
Assert(value);
if (value == Py_None)
{
--- 2372,2391 ----
/* 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)
+ continue;
+
value = NULL;
att = &info->out.r.atts[i];
PG_TRY();
{
! value = PySequence_GetItem(sequence, idx);
Assert(value);
if (value == Py_None)
{
*************** PLySequence_ToTuple(PLyTypeInfo *info, P
*** 2392,2397 ****
--- 2407,2414 ----
PG_RE_THROW();
}
PG_END_TRY();
+
+ idx++;
}
tuple = heap_form_tuple(desc, values, nulls);
*************** PLyObject_ToTuple(PLyTypeInfo *info, PyO
*** 2426,2431 ****
--- 2443,2451 ----
PyObject *volatile value;
PLyObToDatum *att;
+ if (desc->attrs[i]->attisdropped)
+ continue;
+
key = NameStr(desc->attrs[i]->attname);
value = NULL;
att = &info->out.r.atts[i];
*************** static PyModuleDef PLy_module = {
*** 2628,2633 ****
--- 2648,2657 ----
NULL, /* m_doc */
-1, /* m_size */
PLy_methods, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL /* m_free */
};
#endif
*************** PLy_plan_new(void)
*** 2637,2648 ****
{
PLyPlanObject *ob;
! if ((ob = PyObject_NEW(PLyPlanObject, &PLy_PlanType)) == NULL)
return NULL;
ob->plan = NULL;
ob->nargs = 0;
ob->types = NULL;
ob->args = NULL;
return (PyObject *) ob;
--- 2661,2673 ----
{
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;
*************** PLy_plan_dealloc(PyObject *arg)
*** 2658,2663 ****
--- 2683,2690 ----
SPI_freeplan(ob->plan);
if (ob->types)
PLy_free(ob->types);
+ if (ob->values)
+ PLy_free(ob->values);
if (ob->args)
{
int i;
*************** PLy_result_new(void)
*** 2693,2699 ****
{
PLyResultObject *ob;
! if ((ob = PyObject_NEW(PLyResultObject, &PLy_ResultType)) == NULL)
return NULL;
/* ob->tuples = NULL; */
--- 2720,2726 ----
{
PLyResultObject *ob;
! if ((ob = PyObject_New(PLyResultObject, &PLy_ResultType)) == NULL)
return NULL;
/* ob->tuples = NULL; */
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 2799,2823 ****
PyObject *volatile optr = NULL;
char *query;
void *tmpplan;
! volatile MemoryContext oldcontext;
! /* Can't execute more if we have an unhandled error */
! if (PLy_error_in_progress)
! {
! PLy_exception_set(PLy_exc_error, "transaction aborted");
! return NULL;
! }
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
{
- PLy_exception_set(PLy_exc_spi_error,
- "invalid arguments for plpy.prepare");
return NULL;
}
if (list && (!PySequence_Check(list)))
{
! PLy_exception_set(PLy_exc_spi_error,
"second argument of plpy.prepare must be a sequence");
return NULL;
}
--- 2826,2843 ----
PyObject *volatile optr = NULL;
char *query;
void *tmpplan;
! int nargs;
! MemoryContext volatile oldcontext = CurrentMemoryContext;
if (!PyArg_ParseTuple(args, "s|O", &query, &list))
{
return NULL;
}
if (list && (!PySequence_Check(list)))
{
! PLy_exception_set(PyExc_ValueError,
"second argument of plpy.prepare must be a sequence");
return NULL;
}
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 2825,2904 ****
if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
return NULL;
! oldcontext = CurrentMemoryContext;
PG_TRY();
{
! if (list != NULL)
! {
! int nargs,
! i;
!
! nargs = PySequence_Length(list);
! if (nargs > 0)
! {
! plan->nargs = nargs;
! plan->types = PLy_malloc(sizeof(Oid) * nargs);
! plan->values = PLy_malloc(sizeof(Datum) * nargs);
! plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs);
! /*
! * 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);
! optr = NULL; /* this is important */
! 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);
--- 2845,2921 ----
if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
return NULL;
! nargs = (list == NULL ? 0 : PySequence_Length(list));
!
! plan->nargs = nargs;
! plan->types = PLy_malloc(sizeof(Oid) * nargs);
! plan->values = PLy_malloc(sizeof(Datum) * nargs);
! plan->args = PLy_malloc(sizeof(PLyTypeInfo) * nargs);
!
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);
*************** PLy_spi_prepare(PyObject *self, PyObject
*** 2917,2935 ****
}
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
! PLy_error_in_progress = CopyErrorData();
! FlushErrorState();
Py_DECREF(plan);
Py_XDECREF(optr);
if (!PyErr_Occurred())
PLy_exception_set(PLy_exc_spi_error,
"unrecognized error in PLy_spi_prepare");
PLy_elog(WARNING, NULL);
return NULL;
}
PG_END_TRY();
return (PyObject *) plan;
}
--- 2934,2955 ----
}
PG_CATCH();
{
+ ErrorData *edata;
+
MemoryContextSwitchTo(oldcontext);
! edata = CopyErrorData();
Py_DECREF(plan);
Py_XDECREF(optr);
if (!PyErr_Occurred())
PLy_exception_set(PLy_exc_spi_error,
"unrecognized error in PLy_spi_prepare");
PLy_elog(WARNING, NULL);
+ PLy_exception_set(PLy_exc_spi_error, edata->message);
return NULL;
}
PG_END_TRY();
+ Assert(plan->plan != NULL);
return (PyObject *) plan;
}
*************** PLy_spi_execute(PyObject *self, PyObject
*** 2944,2956 ****
PyObject *list = NULL;
long limit = 0;
- /* Can't execute more if we have an unhandled error */
- if (PLy_error_in_progress)
- {
- PLy_exception_set(PLy_exc_error, "transaction aborted");
- return NULL;
- }
-
if (PyArg_ParseTuple(args, "s|l", &query, &limit))
return PLy_spi_execute_query(query, limit);
--- 2964,2969 ----
*************** PLy_spi_execute(PyObject *self, PyObject
*** 2967,2983 ****
static PyObject *
PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
{
! volatile int nargs;
! int i,
! rv;
! PLyPlanObject *plan;
! volatile MemoryContext oldcontext;
if (list != NULL)
{
if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list))
{
! PLy_exception_set(PLy_exc_spi_error, "plpy.execute takes a sequence as its second argument");
return NULL;
}
nargs = PySequence_Length(list);
--- 2980,2996 ----
static PyObject *
PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
{
! int volatile nargs;
! int i, rv;
! PLyPlanObject *plan;
! PyObject *ret;
! MemoryContext volatile oldcontext = CurrentMemoryContext;
if (list != NULL)
{
if (!PySequence_Check(list) || PyString_Check(list) || PyUnicode_Check(list))
{
! PLy_exception_set(PyExc_ValueError, "plpy.execute takes a sequence as its second argument");
return NULL;
}
nargs = PySequence_Length(list);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 2992,3003 ****
char *sv;
PyObject *so = PyObject_Str(list);
! if (!so)
PLy_elog(ERROR, "could not execute plan");
sv = PyString_AsString(so);
! PLy_exception_set_plural(PLy_exc_spi_error,
! "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);
--- 3005,3016 ----
char *sv;
PyObject *so = PyObject_Str(list);
! if (so == NULL)
PLy_elog(ERROR, "could not execute plan");
sv = PyString_AsString(so);
! PLy_exception_set_plural(PyExc_ValueError,
! "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);
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3005,3015 ****
return NULL;
}
- oldcontext = CurrentMemoryContext;
PG_TRY();
{
! char *nulls = palloc(nargs * sizeof(char));
! volatile int j;
for (j = 0; j < nargs; j++)
{
--- 3018,3032 ----
return NULL;
}
PG_TRY();
{
! char *nulls;
! int volatile j;
!
! if (nargs > 0)
! nulls = palloc(nargs * sizeof(char));
! else
! nulls = NULL;
for (j = 0; j < nargs; j++)
{
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3049,3082 ****
rv = SPI_execute_plan(plan->plan, plan->values, nulls,
PLy_curr_procedure->fn_readonly, limit);
- pfree(nulls);
}
PG_CATCH();
{
! int k;
MemoryContextSwitchTo(oldcontext);
! PLy_error_in_progress = CopyErrorData();
FlushErrorState();
/*
* 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);
}
}
if (!PyErr_Occurred())
! PLy_exception_set(PLy_exc_error,
"unrecognized error in PLy_spi_execute_plan");
PLy_elog(WARNING, NULL);
return NULL;
}
PG_END_TRY();
--- 3066,3104 ----
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);
+
}
PG_CATCH();
{
! ErrorData *edata;
MemoryContextSwitchTo(oldcontext);
! edata = CopyErrorData();
FlushErrorState();
/*
* cleanup plan->values array
*/
! 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 (!PyErr_Occurred())
! PLy_exception_set(PLy_exc_spi_error,
"unrecognized error in PLy_spi_execute_plan");
PLy_elog(WARNING, NULL);
+ PLy_exception_set(PLy_exc_spi_error, edata->message);
return NULL;
}
PG_END_TRY();
*************** PLy_spi_execute_plan(PyObject *ob, PyObj
*** 3091,3128 ****
}
}
! if (rv < 0)
! {
! PLy_exception_set(PLy_exc_spi_error,
! "SPI_execute_plan failed: %s",
! SPI_result_code_string(rv));
! return NULL;
! }
!
! return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
}
static PyObject *
PLy_spi_execute_query(char *query, long limit)
{
int rv;
! volatile MemoryContext oldcontext;
- oldcontext = CurrentMemoryContext;
PG_TRY();
{
pg_verifymbstr(query, strlen(query), false);
rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
}
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
! PLy_error_in_progress = CopyErrorData();
FlushErrorState();
if (!PyErr_Occurred())
PLy_exception_set(PLy_exc_spi_error,
"unrecognized error in PLy_spi_execute_query");
PLy_elog(WARNING, NULL);
return NULL;
}
PG_END_TRY();
--- 3113,3146 ----
}
}
! return ret;
}
static PyObject *
PLy_spi_execute_query(char *query, long limit)
{
int rv;
! PyObject *ret;
! MemoryContext volatile oldcontext = CurrentMemoryContext;
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);
}
PG_CATCH();
{
+ ErrorData *edata;
+
MemoryContextSwitchTo(oldcontext);
! edata = CopyErrorData();
FlushErrorState();
if (!PyErr_Occurred())
PLy_exception_set(PLy_exc_spi_error,
"unrecognized error in PLy_spi_execute_query");
PLy_elog(WARNING, NULL);
+ PLy_exception_set(PLy_exc_spi_error, edata->message);
return NULL;
}
PG_END_TRY();
*************** PLy_spi_execute_query(char *query, long
*** 3135,3141 ****
return NULL;
}
! return PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
}
static PyObject *
--- 3153,3159 ----
return NULL;
}
! return ret;
}
static PyObject *
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 3183,3190 ****
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
- PLy_error_in_progress = CopyErrorData();
- FlushErrorState();
if (!PyErr_Occurred())
PLy_exception_set(PLy_exc_error,
"unrecognized error in PLy_spi_execute_fetch_result");
--- 3201,3206 ----
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 3202,3207 ****
--- 3218,3263 ----
return (PyObject *) result;
}
+ /*
+ * Exception support
+ */
+
+ /*
+ * Add an exception to the module. The first parameter is either the actual
+ * module (for Python 3) or the module dictionary
+ */
+ static void PLy_add_exception(PyObject *mod, const char *name, PyObject *exc)
+ {
+ #if PY_MAJOR_VERSION >= 3
+ Py_INCREF(exc);
+ PyModule_AddObject(mod, name, exc);
+ #else
+ PyDict_SetItemString(mod, name, exc);
+ #endif
+ }
+
+ /* Add exceptions to the plpy module */
+ static void
+ PLy_initialize_exceptions(PyObject *plpy)
+ {
+ PyObject *mod;
+
+ #if PY_MAJOR_VERSION < 3
+ /* For Python <3 we add the exceptions to the module dictionary */
+ mod = PyModule_GetDict(plpy);
+ #else
+ /* In Python 3 you add them directly into the module */
+ mod = plpy;
+ #endif
+
+ 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);
+
+ PLy_add_exception(mod, "Error", PLy_exc_error);
+ PLy_add_exception(mod, "Fatal", PLy_exc_fatal);
+ PLy_add_exception(mod, "SPIError", PLy_exc_spi_error);
+ }
/*
* language handler and interpreter initialization
*************** PLy_spi_execute_fetch_result(SPITupleTab
*** 3211,3217 ****
static PyMODINIT_FUNC
PyInit_plpy(void)
{
! return PyModule_Create(&PLy_module);
}
#endif
--- 3267,3281 ----
static PyMODINIT_FUNC
PyInit_plpy(void)
{
! PyObject *m;
!
! m = PyModule_Create(&PLy_module);
! if (m == NULL)
! return NULL;
!
! PLy_initialize_exceptions(m);
!
! return m;
}
#endif
*************** _PG_init(void)
*** 3229,3234 ****
--- 3293,3300 ----
/* 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;
*************** _PG_init(void)
*** 3260,3268 ****
PLy_init_plpy();
if (PyErr_Occurred())
PLy_elog(FATAL, "untrapped error in initialization");
! PLy_procedure_cache = PyDict_New();
! if (PLy_procedure_cache == NULL)
! PLy_elog(ERROR, "could not create procedure cache");
inited = true;
}
--- 3326,3346 ----
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);
inited = true;
}
*************** PLy_init_plpy(void)
*** 3290,3297 ****
PyObject *main_mod,
*main_dict,
*plpy_mod;
! PyObject *plpy,
! *plpy_dict;
/*
* initialize plpy module
--- 3368,3374 ----
PyObject *main_mod,
*main_dict,
*plpy_mod;
! PyObject *plpy;
/*
* initialize plpy module
*************** PLy_init_plpy(void)
*** 3305,3322 ****
plpy = PyModule_Create(&PLy_module);
#else
plpy = Py_InitModule("plpy", PLy_methods);
#endif
- plpy_dict = PyModule_GetDict(plpy);
/* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */
- 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);
- PyDict_SetItemString(plpy_dict, "Error", PLy_exc_error);
- PyDict_SetItemString(plpy_dict, "Fatal", PLy_exc_fatal);
- PyDict_SetItemString(plpy_dict, "SPIError", PLy_exc_spi_error);
-
/*
* initialize main module, and add plpy
*/
--- 3382,3393 ----
plpy = PyModule_Create(&PLy_module);
#else
plpy = Py_InitModule("plpy", PLy_methods);
+ /* for Python 3 we initialized the exceptions in PyInit_plpy */
+ PLy_initialize_exceptions(plpy);
#endif
/* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */
/*
* initialize main module, and add plpy
*/
*************** PLy_init_plpy(void)
*** 3331,3337 ****
/* 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)
--- 3402,3408 ----
/* the python interface to the elog function
* don't confuse these with PLy_elog
*/
! static PyObject *PLy_output(int, PyObject *, PyObject *);
static PyObject *
PLy_debug(PyObject *self, PyObject *args)
*************** PLy_fatal(PyObject *self, PyObject *args
*** 3379,3387 ****
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)
{
--- 3450,3458 ----
static PyObject *
PLy_output(volatile int level, PyObject *self, PyObject *args)
{
! PyObject *so;
! char *sv;
! MemoryContext volatile oldcontext = CurrentMemoryContext;
if (PyTuple_Size(args) == 1)
{
*************** PLy_output(volatile int level, PyObject
*** 3402,3432 ****
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();
{
! MemoryContextSwitchTo(oldcontext);
! PLy_error_in_progress = CopyErrorData();
! FlushErrorState();
! PyErr_SetString(PLy_exc_error, sv);
/*
! * Note: If sv came from PyString_AsString(), it points into storage
! * owned by so. So free so after using sv.
! */
Py_XDECREF(so);
! /*
! * returning NULL here causes the python interpreter to bail. when
! * control passes back to PLy_procedure_call, we check for PG
! * exceptions and re-throw the error.
! */
return NULL;
}
PG_END_TRY();
--- 3473,3500 ----
sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
}
! pg_verifymbstr(sv, strlen(sv), false);
PG_TRY();
{
elog(level, "%s", sv);
}
PG_CATCH();
{
! ErrorData *edata;
! /* Must reset elog.c's state */
! 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, edata->message);
return NULL;
}
PG_END_TRY();
*************** PLy_elog(int elevel, const char *fmt,...
*** 3528,3538 ****
{
if (fmt)
ereport(elevel,
! (errmsg("PL/Python: %s", emsg.data),
(xmsg) ? errdetail("%s", xmsg) : 0));
else
ereport(elevel,
! (errmsg("PL/Python: %s", xmsg)));
}
PG_CATCH();
{
--- 3596,3606 ----
{
if (fmt)
ereport(elevel,
! (errmsg("%s", emsg.data),
(xmsg) ? errdetail("%s", xmsg) : 0));
else
ereport(elevel,
! (errmsg("%s", xmsg)));
}
PG_CATCH();
{
*************** PLy_traceback(int *xlevel)
*** 3635,3653 ****
/* some dumb utility functions */
static void *
! PLy_malloc(size_t bytes)
{
! void *ptr = malloc(bytes);
!
! if (ptr == NULL)
! ereport(FATAL,
! (errcode(ERRCODE_OUT_OF_MEMORY),
! errmsg("out of memory")));
! return ptr;
}
static void *
! PLy_malloc0(size_t bytes)
{
void *ptr = PLy_malloc(bytes);
--- 3703,3716 ----
/* some dumb utility functions */
static void *
! PLy_malloc(Size bytes)
{
! /* We need our allocations to be long-lived, so use TopMemoryContext */
! return MemoryContextAlloc(TopMemoryContext, bytes);
}
static void *
! PLy_malloc0(Size bytes)
{
void *ptr = PLy_malloc(bytes);
*************** PLy_strdup(const char *str)
*** 3672,3678 ****
static void
PLy_free(void *ptr)
{
! free(ptr);
}
/*
--- 3735,3741 ----
static void
PLy_free(void *ptr)
{
! pfree(ptr);
}
/*
diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql
index 7cae124..298411a 100644
*** a/src/pl/plpython/sql/plpython_test.sql
--- b/src/pl/plpython/sql/plpython_test.sql
*************** return words'
*** 25,30 ****
--- 25,39 ----
select argument_test_one(users, fname, lname) from users where lname = 'doe' order by 1;
+ -- check module contents
+ CREATE FUNCTION module_contents() RETURNS text AS
+ $$
+ contents = list(filter(lambda x: not x.startswith("__"), dir(plpy)))
+ contents.sort()
+ return ", ".join(contents)
+ $$ LANGUAGE plpythonu;
+
+ select module_contents();
CREATE FUNCTION elog_test() RETURNS void
AS $$