Commit 08be00fa authored by Tom Lane's avatar Tom Lane

Fix plpython's handling of functions used as triggers on multiple tables.

plpython tried to use a single cache entry for a trigger function, but it
needs a separate cache entry for each table the trigger is applied to,
because there is table-dependent data in there.  This was done correctly
before 9.1, but commit 46211da1 broke it
by simplifying the lookup key from "function OID and triggered table OID"
to "function OID and is-trigger boolean".  Go back to using both OIDs
as the lookup key.  Per bug report from Sandro Santilli.

Andres Freund
parent bb1e5049
...@@ -610,3 +610,27 @@ SELECT * FROM composite_trigger_nested_test; ...@@ -610,3 +610,27 @@ SELECT * FROM composite_trigger_nested_test;
("(,t)","(1,f)",) ("(,t)","(1,f)",)
(3 rows) (3 rows)
-- check that using a function as a trigger over two tables works correctly
CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpythonu AS $$
TD["new"]["data"] = '1234'
return 'MODIFY'
$$;
CREATE TABLE a(data text);
CREATE TABLE b(data int); -- different type conversion
CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234();
CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234();
INSERT INTO a DEFAULT VALUES;
SELECT * FROM a;
data
------
1234
(1 row)
DROP TABLE a;
INSERT INTO b DEFAULT VALUES;
SELECT * FROM b;
data
------
1234
(1 row)
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "utils/guc.h" #include "utils/guc.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h" #include "utils/syscache.h"
#include "plpython.h" #include "plpython.h"
...@@ -174,7 +175,8 @@ plpython_validator(PG_FUNCTION_ARGS) ...@@ -174,7 +175,8 @@ plpython_validator(PG_FUNCTION_ARGS)
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
PLy_procedure_get(funcoid, is_trigger); /* We can't validate triggers against any particular table ... */
PLy_procedure_get(funcoid, InvalidOid, is_trigger);
PG_RETURN_VOID(); PG_RETURN_VOID();
} }
...@@ -215,20 +217,22 @@ plpython_call_handler(PG_FUNCTION_ARGS) ...@@ -215,20 +217,22 @@ plpython_call_handler(PG_FUNCTION_ARGS)
PG_TRY(); PG_TRY();
{ {
Oid funcoid = fcinfo->flinfo->fn_oid;
PLyProcedure *proc; PLyProcedure *proc;
if (CALLED_AS_TRIGGER(fcinfo)) if (CALLED_AS_TRIGGER(fcinfo))
{ {
Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
HeapTuple trv; HeapTuple trv;
proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true); proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
exec_ctx->curr_proc = proc; exec_ctx->curr_proc = proc;
trv = PLy_exec_trigger(fcinfo, proc); trv = PLy_exec_trigger(fcinfo, proc);
retval = PointerGetDatum(trv); retval = PointerGetDatum(trv);
} }
else else
{ {
proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false); proc = PLy_procedure_get(funcoid, InvalidOid, false);
exec_ctx->curr_proc = proc; exec_ctx->curr_proc = proc;
retval = PLy_exec_function(fcinfo, proc); retval = PLy_exec_function(fcinfo, proc);
} }
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
static HTAB *PLy_procedure_cache = NULL; static HTAB *PLy_procedure_cache = NULL;
static HTAB *PLy_trigger_cache = NULL;
static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger); static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg); static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
...@@ -38,18 +37,11 @@ init_procedure_caches(void) ...@@ -38,18 +37,11 @@ init_procedure_caches(void)
HASHCTL hash_ctl; HASHCTL hash_ctl;
memset(&hash_ctl, 0, sizeof(hash_ctl)); memset(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = sizeof(Oid); hash_ctl.keysize = sizeof(PLyProcedureKey);
hash_ctl.entrysize = sizeof(PLyProcedureEntry); hash_ctl.entrysize = sizeof(PLyProcedureEntry);
hash_ctl.hash = oid_hash; hash_ctl.hash = tag_hash;
PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl, PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
HASH_ELEM | HASH_FUNCTION); 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);
} }
/* /*
...@@ -69,61 +61,74 @@ PLy_procedure_name(PLyProcedure *proc) ...@@ -69,61 +61,74 @@ PLy_procedure_name(PLyProcedure *proc)
/* /*
* PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and * PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and
* returns a new PLyProcedure. fcinfo is the call info, tgreloid is the * returns a new PLyProcedure.
* relation OID when calling a trigger, or InvalidOid (zero) for ordinary *
* function calls. * fn_oid is the OID of the function requested
* fn_rel is InvalidOid or the relation this function triggers on
* is_trigger denotes whether the function is a trigger function
*
* The reason that both fn_rel and is_trigger need to be passed is that when
* trigger functions get validated we don't know which relation(s) they'll
* be used with, so no sensible fn_rel can be passed.
*/ */
PLyProcedure * PLyProcedure *
PLy_procedure_get(Oid fn_oid, bool is_trigger) PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
{ {
bool use_cache = !(is_trigger && fn_rel == InvalidOid);
HeapTuple procTup; HeapTuple procTup;
PLyProcedureEntry *volatile entry; PLyProcedureKey key;
bool found; PLyProcedureEntry *volatile entry = NULL;
PLyProcedure *volatile proc = NULL;
bool found = false;
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
if (!HeapTupleIsValid(procTup)) if (!HeapTupleIsValid(procTup))
elog(ERROR, "cache lookup failed for function %u", fn_oid); elog(ERROR, "cache lookup failed for function %u", fn_oid);
/* Look for the function in the corresponding cache */ /*
if (is_trigger) * Look for the function in the cache, unless we don't have the necessary
entry = hash_search(PLy_trigger_cache, * information (e.g. during validation). In that case we just don't cache
&fn_oid, HASH_ENTER, &found); * anything.
else */
entry = hash_search(PLy_procedure_cache, if (use_cache)
&fn_oid, HASH_ENTER, &found); {
key.fn_oid = fn_oid;
key.fn_rel = fn_rel;
entry = hash_search(PLy_procedure_cache, &key, HASH_ENTER, &found);
proc = entry->proc;
}
PG_TRY(); PG_TRY();
{ {
if (!found) if (!found)
{ {
/* Haven't found it, create a new cache entry */ /* Haven't found it, create a new procedure */
entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger); proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
if (use_cache)
entry->proc = proc;
} }
else if (!PLy_procedure_valid(entry->proc, procTup)) else if (!PLy_procedure_valid(proc, procTup))
{ {
/* Found it, but it's invalid, free and reuse the cache entry */ /* Found it, but it's invalid, free and reuse the cache entry */
PLy_procedure_delete(entry->proc); PLy_procedure_delete(proc);
PLy_free(entry->proc); PLy_free(proc);
entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger); proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
entry->proc = proc;
} }
/* Found it and it's valid, it's fine to use it */ /* Found it and it's valid, it's fine to use it */
} }
PG_CATCH(); PG_CATCH();
{ {
/* Do not leave an uninitialised entry in the cache */ /* Do not leave an uninitialised entry in the cache */
if (is_trigger) if (use_cache)
hash_search(PLy_trigger_cache, hash_search(PLy_procedure_cache, &key, HASH_REMOVE, NULL);
&fn_oid, HASH_REMOVE, NULL);
else
hash_search(PLy_procedure_cache,
&fn_oid, HASH_REMOVE, NULL);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
ReleaseSysCache(procTup); ReleaseSysCache(procTup);
return entry->proc; return proc;
} }
/* /*
......
...@@ -32,16 +32,23 @@ typedef struct PLyProcedure ...@@ -32,16 +32,23 @@ typedef struct PLyProcedure
PyObject *globals; /* data saved across calls, global scope */ PyObject *globals; /* data saved across calls, global scope */
} PLyProcedure; } PLyProcedure;
/* the procedure cache key */
typedef struct PLyProcedureKey
{
Oid fn_oid; /* function OID */
Oid fn_rel; /* triggered-on relation or InvalidOid */
} PLyProcedureKey;
/* the procedure cache entry */ /* the procedure cache entry */
typedef struct PLyProcedureEntry typedef struct PLyProcedureEntry
{ {
Oid fn_oid; /* hash key */ PLyProcedureKey key; /* hash key */
PLyProcedure *proc; PLyProcedure *proc;
} PLyProcedureEntry; } PLyProcedureEntry;
/* PLyProcedure manipulation */ /* PLyProcedure manipulation */
extern char *PLy_procedure_name(PLyProcedure *proc); extern char *PLy_procedure_name(PLyProcedure *proc);
extern PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger); extern PLyProcedure *PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger);
extern void PLy_procedure_compile(PLyProcedure *proc, const char *src); extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
extern void PLy_procedure_delete(PLyProcedure *proc); extern void PLy_procedure_delete(PLyProcedure *proc);
......
...@@ -388,3 +388,21 @@ INSERT INTO composite_trigger_nested_test VALUES (NULL); ...@@ -388,3 +388,21 @@ INSERT INTO composite_trigger_nested_test VALUES (NULL);
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3));
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL));
SELECT * FROM composite_trigger_nested_test; SELECT * FROM composite_trigger_nested_test;
-- check that using a function as a trigger over two tables works correctly
CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpythonu AS $$
TD["new"]["data"] = '1234'
return 'MODIFY'
$$;
CREATE TABLE a(data text);
CREATE TABLE b(data int); -- different type conversion
CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234();
CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234();
INSERT INTO a DEFAULT VALUES;
SELECT * FROM a;
DROP TABLE a;
INSERT INTO b DEFAULT VALUES;
SELECT * FROM b;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment