|
|
|
|
-- these triggers are dedicated to HPHC of RI who
|
|
|
|
|
-- decided that my kid's name was william not willem, and
|
|
|
|
|
-- vigorously resisted all efforts at correction. they have
|
|
|
|
|
-- since gone bankrupt...
|
|
|
|
|
CREATE FUNCTION users_insert() returns trigger
|
|
|
|
|
AS
|
|
|
|
|
'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
|
|
|
|
|
return "SKIP"
|
|
|
|
|
if TD["new"]["username"] == None:
|
|
|
|
|
TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
|
|
|
|
|
rv = "MODIFY"
|
|
|
|
|
else:
|
|
|
|
|
rv = None
|
|
|
|
|
if TD["new"]["fname"] == "william":
|
|
|
|
|
TD["new"]["fname"] = TD["args"][0]
|
|
|
|
|
rv = "MODIFY"
|
|
|
|
|
return rv'
|
|
|
|
|
LANGUAGE plpythonu;
|
|
|
|
|
CREATE FUNCTION users_update() returns trigger
|
|
|
|
|
AS
|
|
|
|
|
'if TD["event"] == "UPDATE":
|
|
|
|
|
if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
|
|
|
|
|
return "SKIP"
|
|
|
|
|
return None'
|
|
|
|
|
LANGUAGE plpythonu;
|
|
|
|
|
CREATE FUNCTION users_delete() RETURNS trigger
|
|
|
|
|
AS
|
|
|
|
|
'if TD["old"]["fname"] == TD["args"][0]:
|
|
|
|
|
return "SKIP"
|
|
|
|
|
return None'
|
|
|
|
|
LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
|
|
|
|
|
EXECUTE PROCEDURE users_insert ('willem');
|
|
|
|
|
CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
|
|
|
|
|
EXECUTE PROCEDURE users_update ('willem');
|
|
|
|
|
CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
|
|
|
|
|
EXECUTE PROCEDURE users_delete ('willem');
|
|
|
|
|
-- quick peek at the table
|
|
|
|
|
--
|
|
|
|
|
SELECT * FROM users;
|
|
|
|
|
fname | lname | username | userid
|
|
|
|
|
--------+-------+----------+--------
|
|
|
|
|
jane | doe | j_doe | 1
|
|
|
|
|
john | doe | johnd | 2
|
|
|
|
|
willem | doe | w_doe | 3
|
|
|
|
|
rick | smith | slash | 4
|
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
|
|
-- should fail
|
|
|
|
|
--
|
|
|
|
|
UPDATE users SET fname = 'william' WHERE fname = 'willem';
|
|
|
|
|
-- should modify william to willem and create username
|
|
|
|
|
--
|
|
|
|
|
INSERT INTO users (fname, lname) VALUES ('william', 'smith');
|
|
|
|
|
INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
|
|
|
|
|
SELECT * FROM users;
|
|
|
|
|
fname | lname | username | userid
|
|
|
|
|
---------+--------+----------+--------
|
|
|
|
|
jane | doe | j_doe | 1
|
|
|
|
|
john | doe | johnd | 2
|
|
|
|
|
willem | doe | w_doe | 3
|
|
|
|
|
rick | smith | slash | 4
|
|
|
|
|
willem | smith | w_smith | 5
|
|
|
|
|
charles | darwin | beagle | 6
|
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
|
|
-- dump trigger data
|
|
|
|
|
CREATE TABLE trigger_test
|
|
|
|
|
(i int, v text );
|
|
|
|
|
CREATE TABLE trigger_test_generated (
|
|
|
|
|
i int,
|
|
|
|
|
j int GENERATED ALWAYS AS (i * 2) STORED
|
|
|
|
|
);
|
|
|
|
|
CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$
|
|
|
|
|
|
|
|
|
|
if 'relid' in TD:
|
|
|
|
|
TD['relid'] = "bogus:12345"
|
|
|
|
|
|
|
|
|
|
skeys = list(TD.keys())
|
|
|
|
|
skeys.sort()
|
|
|
|
|
for key in skeys:
|
|
|
|
|
val = TD[key]
|
|
|
|
|
if not isinstance(val, dict):
|
|
|
|
|
plpy.notice("TD[" + key + "] => " + str(val))
|
|
|
|
|
else:
|
|
|
|
|
# print dicts the hard way because otherwise the order is implementation-dependent
|
|
|
|
|
valkeys = list(val.keys())
|
|
|
|
|
valkeys.sort()
|
|
|
|
|
plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}')
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
$$;
|
|
|
|
|
CREATE TRIGGER show_trigger_data_trig_before
|
|
|
|
|
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
|
|
|
|
CREATE TRIGGER show_trigger_data_trig_after
|
|
|
|
|
AFTER INSERT OR UPDATE OR DELETE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
|
|
|
|
CREATE TRIGGER show_trigger_data_trig_stmt
|
|
|
|
|
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
|
|
|
|
|
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo');
|
|
|
|
|
insert into trigger_test values(1,'insert');
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => INSERT
|
|
|
|
|
NOTICE: TD[level] => STATEMENT
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_stmt
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => INSERT
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_before
|
|
|
|
|
NOTICE: TD[new] => {'i': 1, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => INSERT
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_after
|
|
|
|
|
NOTICE: TD[new] => {'i': 1, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => AFTER
|
|
|
|
|
update trigger_test set v = 'update' where i = 1;
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => UPDATE
|
|
|
|
|
NOTICE: TD[level] => STATEMENT
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_stmt
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => UPDATE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_before
|
|
|
|
|
NOTICE: TD[new] => {'i': 1, 'v': 'update'}
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => UPDATE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_after
|
|
|
|
|
NOTICE: TD[new] => {'i': 1, 'v': 'update'}
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => AFTER
|
|
|
|
|
delete from trigger_test;
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => DELETE
|
|
|
|
|
NOTICE: TD[level] => STATEMENT
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_stmt
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => DELETE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_before
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'v': 'update'}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => DELETE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_after
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'v': 'update'}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => AFTER
|
|
|
|
|
truncate table trigger_test;
|
|
|
|
|
NOTICE: TD[args] => ['23', 'skidoo']
|
|
|
|
|
NOTICE: TD[event] => TRUNCATE
|
|
|
|
|
NOTICE: TD[level] => STATEMENT
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_stmt
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
|
|
|
|
|
DROP TRIGGER show_trigger_data_trig_before on trigger_test;
|
|
|
|
|
DROP TRIGGER show_trigger_data_trig_after on trigger_test;
|
|
|
|
|
CREATE TRIGGER show_trigger_data_trig_before
|
|
|
|
|
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
|
|
|
|
|
CREATE TRIGGER show_trigger_data_trig_after
|
|
|
|
|
AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data();
|
|
|
|
|
insert into trigger_test_generated (i) values (1);
|
|
|
|
|
NOTICE: TD[args] => None
|
|
|
|
|
NOTICE: TD[event] => INSERT
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_before
|
|
|
|
|
NOTICE: TD[new] => {'i': 1}
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_generated
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => None
|
|
|
|
|
NOTICE: TD[event] => INSERT
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_after
|
|
|
|
|
NOTICE: TD[new] => {'i': 1, 'j': 2}
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_generated
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => AFTER
|
|
|
|
|
update trigger_test_generated set i = 11 where i = 1;
|
|
|
|
|
NOTICE: TD[args] => None
|
|
|
|
|
NOTICE: TD[event] => UPDATE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_before
|
|
|
|
|
NOTICE: TD[new] => {'i': 11}
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'j': 2}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_generated
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => None
|
|
|
|
|
NOTICE: TD[event] => UPDATE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_after
|
|
|
|
|
NOTICE: TD[new] => {'i': 11, 'j': 22}
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'j': 2}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_generated
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => AFTER
|
|
|
|
|
delete from trigger_test_generated;
|
|
|
|
|
NOTICE: TD[args] => None
|
|
|
|
|
NOTICE: TD[event] => DELETE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_before
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => {'i': 11, 'j': 22}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_generated
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => BEFORE
|
|
|
|
|
NOTICE: TD[args] => None
|
|
|
|
|
NOTICE: TD[event] => DELETE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig_after
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => {'i': 11, 'j': 22}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_generated
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => AFTER
|
|
|
|
|
DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
|
|
|
|
|
DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
|
|
|
|
|
insert into trigger_test values(1,'insert');
|
|
|
|
|
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
|
|
|
|
|
CREATE TRIGGER show_trigger_data_trig
|
|
|
|
|
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
|
|
|
|
|
insert into trigger_test_view values(2,'insert');
|
|
|
|
|
NOTICE: TD[args] => ['24', 'skidoo view']
|
|
|
|
|
NOTICE: TD[event] => INSERT
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig
|
|
|
|
|
NOTICE: TD[new] => {'i': 2, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[old] => None
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_view
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => INSTEAD OF
|
|
|
|
|
update trigger_test_view set v = 'update' where i = 1;
|
|
|
|
|
NOTICE: TD[args] => ['24', 'skidoo view']
|
|
|
|
|
NOTICE: TD[event] => UPDATE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig
|
|
|
|
|
NOTICE: TD[new] => {'i': 1, 'v': 'update'}
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_view
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => INSTEAD OF
|
|
|
|
|
delete from trigger_test_view;
|
|
|
|
|
NOTICE: TD[args] => ['24', 'skidoo view']
|
|
|
|
|
NOTICE: TD[event] => DELETE
|
|
|
|
|
NOTICE: TD[level] => ROW
|
|
|
|
|
NOTICE: TD[name] => show_trigger_data_trig
|
|
|
|
|
NOTICE: TD[new] => None
|
|
|
|
|
NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
|
|
|
|
|
NOTICE: TD[relid] => bogus:12345
|
|
|
|
|
NOTICE: TD[table_name] => trigger_test_view
|
|
|
|
|
NOTICE: TD[table_schema] => public
|
|
|
|
|
NOTICE: TD[when] => INSTEAD OF
|
|
|
|
|
DROP FUNCTION trigger_data() CASCADE;
|
|
|
|
|
NOTICE: drop cascades to trigger show_trigger_data_trig on view trigger_test_view
|
|
|
|
|
DROP VIEW trigger_test_view;
|
|
|
|
|
delete from trigger_test;
|
|
|
|
|
--
|
|
|
|
|
-- trigger error handling
|
|
|
|
|
--
|
|
|
|
|
INSERT INTO trigger_test VALUES (0, 'zero');
|
|
|
|
|
-- returning non-string from trigger function
|
|
|
|
|
CREATE FUNCTION stupid1() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
return 37
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger1
|
|
|
|
|
BEFORE INSERT ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid1();
|
|
|
|
|
INSERT INTO trigger_test VALUES (1, 'one');
|
|
|
|
|
ERROR: unexpected return value from trigger procedure
|
|
|
|
|
DETAIL: Expected None or a string.
|
|
|
|
|
CONTEXT: PL/Python function "stupid1"
|
|
|
|
|
DROP TRIGGER stupid_trigger1 ON trigger_test;
|
|
|
|
|
-- returning MODIFY from DELETE trigger
|
|
|
|
|
CREATE FUNCTION stupid2() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
return "MODIFY"
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger2
|
|
|
|
|
BEFORE DELETE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid2();
|
|
|
|
|
DELETE FROM trigger_test WHERE i = 0;
|
|
|
|
|
WARNING: PL/Python trigger function returned "MODIFY" in a DELETE trigger -- ignored
|
|
|
|
|
DROP TRIGGER stupid_trigger2 ON trigger_test;
|
|
|
|
|
INSERT INTO trigger_test VALUES (0, 'zero');
|
|
|
|
|
-- returning unrecognized string from trigger function
|
|
|
|
|
CREATE FUNCTION stupid3() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
return "foo"
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger3
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid3();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: unexpected return value from trigger procedure
|
|
|
|
|
DETAIL: Expected None, "OK", "SKIP", or "MODIFY".
|
|
|
|
|
CONTEXT: PL/Python function "stupid3"
|
|
|
|
|
DROP TRIGGER stupid_trigger3 ON trigger_test;
|
|
|
|
|
-- Unicode variant
|
|
|
|
|
CREATE FUNCTION stupid3u() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
return u"foo"
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger3
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid3u();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: unexpected return value from trigger procedure
|
|
|
|
|
DETAIL: Expected None, "OK", "SKIP", or "MODIFY".
|
|
|
|
|
CONTEXT: PL/Python function "stupid3u"
|
|
|
|
|
DROP TRIGGER stupid_trigger3 ON trigger_test;
|
|
|
|
|
-- deleting the TD dictionary
|
|
|
|
|
CREATE FUNCTION stupid4() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
del TD["new"]
|
|
|
|
|
return "MODIFY";
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger4
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid4();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: TD["new"] deleted, cannot modify row
|
|
|
|
|
CONTEXT: while modifying trigger row
|
|
|
|
|
PL/Python function "stupid4"
|
|
|
|
|
DROP TRIGGER stupid_trigger4 ON trigger_test;
|
|
|
|
|
-- TD not a dictionary
|
|
|
|
|
CREATE FUNCTION stupid5() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
TD["new"] = ['foo', 'bar']
|
|
|
|
|
return "MODIFY";
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger5
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid5();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: TD["new"] is not a dictionary
|
|
|
|
|
CONTEXT: while modifying trigger row
|
|
|
|
|
PL/Python function "stupid5"
|
|
|
|
|
DROP TRIGGER stupid_trigger5 ON trigger_test;
|
|
|
|
|
-- TD not having string keys
|
|
|
|
|
CREATE FUNCTION stupid6() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
TD["new"] = {1: 'foo', 2: 'bar'}
|
|
|
|
|
return "MODIFY";
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger6
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid6();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: TD["new"] dictionary key at ordinal position 0 is not a string
|
|
|
|
|
CONTEXT: while modifying trigger row
|
|
|
|
|
PL/Python function "stupid6"
|
|
|
|
|
DROP TRIGGER stupid_trigger6 ON trigger_test;
|
|
|
|
|
-- TD keys not corresponding to row columns
|
|
|
|
|
CREATE FUNCTION stupid7() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
TD["new"] = {'v': 'foo', 'a': 'bar'}
|
|
|
|
|
return "MODIFY";
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger7
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid7();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row
|
|
|
|
|
CONTEXT: while modifying trigger row
|
|
|
|
|
PL/Python function "stupid7"
|
|
|
|
|
DROP TRIGGER stupid_trigger7 ON trigger_test;
|
|
|
|
|
-- Unicode variant
|
|
|
|
|
CREATE FUNCTION stupid7u() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
TD["new"] = {u'v': 'foo', u'a': 'bar'}
|
|
|
|
|
return "MODIFY"
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER stupid_trigger7
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE stupid7u();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row
|
|
|
|
|
CONTEXT: while modifying trigger row
|
|
|
|
|
PL/Python function "stupid7u"
|
|
|
|
|
DROP TRIGGER stupid_trigger7 ON trigger_test;
|
|
|
|
|
-- calling a trigger function directly
|
|
|
|
|
SELECT stupid7();
|
|
|
|
|
ERROR: trigger functions can only be called as triggers
|
|
|
|
|
--
|
|
|
|
|
-- Null values
|
|
|
|
|
--
|
|
|
|
|
SELECT * FROM trigger_test;
|
|
|
|
|
i | v
|
|
|
|
|
---+------
|
|
|
|
|
0 | zero
|
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
|
|
CREATE FUNCTION test_null() RETURNS trigger
|
|
|
|
|
AS $$
|
|
|
|
|
TD["new"]['v'] = None
|
|
|
|
|
return "MODIFY"
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER test_null_trigger
|
|
|
|
|
BEFORE UPDATE ON trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE test_null();
|
|
|
|
|
UPDATE trigger_test SET v = 'null' WHERE i = 0;
|
|
|
|
|
DROP TRIGGER test_null_trigger ON trigger_test;
|
|
|
|
|
SELECT * FROM trigger_test;
|
|
|
|
|
i | v
|
|
|
|
|
---+---
|
|
|
|
|
0 |
|
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
|
|
--
|
|
|
|
|
-- Test that triggers honor typmod when assigning to tuple fields,
|
|
|
|
|
-- as per an early 9.0 bug report
|
|
|
|
|
--
|
|
|
|
|
SET DateStyle = 'ISO';
|
|
|
|
|
CREATE FUNCTION set_modif_time() RETURNS trigger AS $$
|
|
|
|
|
TD['new']['modif_time'] = '2010-10-13 21:57:28.930486'
|
|
|
|
|
return 'MODIFY'
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE);
|
|
|
|
|
CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE set_modif_time();
|
|
|
|
|
INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486');
|
|
|
|
|
SELECT * FROM pb;
|
|
|
|
|
a | modif_time
|
|
|
|
|
---+---------------------
|
|
|
|
|
a | 2010-10-09 21:57:34
|
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
|
|
UPDATE pb SET a = 'b';
|
|
|
|
|
SELECT * FROM pb;
|
|
|
|
|
a | modif_time
|
|
|
|
|
---+---------------------
|
|
|
|
|
b | 2010-10-13 21:57:29
|
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
|
|
-- triggers for tables with composite types
|
|
|
|
|
CREATE TABLE comp1 (i integer, j boolean);
|
|
|
|
|
CREATE TYPE comp2 AS (k integer, l boolean);
|
|
|
|
|
CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2);
|
|
|
|
|
CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$
|
|
|
|
|
TD['new']['f1'] = (3, False)
|
|
|
|
|
TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10}
|
|
|
|
|
return 'MODIFY'
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f();
|
|
|
|
|
INSERT INTO composite_trigger_test VALUES (NULL, NULL);
|
|
|
|
|
SELECT * FROM composite_trigger_test;
|
|
|
|
|
f1 | f2
|
|
|
|
|
-------+-------
|
|
|
|
|
(3,f) | (7,t)
|
|
|
|
|
(1 row)
|
|
|
|
|
|
PL/Python: Accept strings in functions returning composite types
Before 9.1, PL/Python functions returning composite types could return
a string and it would be parsed using record_in. The 9.1 changes made
PL/Python only expect dictionaries, tuples, or objects supporting
getattr as output of composite functions, resulting in a regression
and a confusing error message, as the strings were interpreted as
sequences and the code for transforming lists to database tuples was
used. Fix this by treating strings separately as before, before
checking for the other types.
The reason why it's important to support string to database tuple
conversion is that trigger functions on tables with composite columns
get the composite row passed in as a string (from record_out).
Without supporting converting this back using record_in, this makes it
impossible to implement pass-through behavior for these columns, as
PL/Python no longer accepts strings for composite values.
A better solution would be to fix the code that transforms composite
inputs into Python objects to produce dictionaries that would then be
correctly interpreted by the Python->PostgreSQL counterpart code. But
that would be too invasive to backpatch to 9.1, and it is too late in
the 9.2 cycle to attempt it. It should be revisited in the future,
though.
Reported as bug #6559 by Kirill Simonov.
Jan Urbański
14 years ago
|
|
|
-- triggers with composite type columns (bug #6559)
|
|
|
|
|
CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2);
|
|
|
|
|
CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$
|
|
|
|
|
return 'MODIFY'
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f();
|
|
|
|
|
INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL);
|
|
|
|
|
INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL);
|
|
|
|
|
INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f'));
|
|
|
|
|
SELECT * FROM composite_trigger_noop_test;
|
|
|
|
|
f1 | f2
|
|
|
|
|
-------+-------
|
|
|
|
|
|
|
|
|
|
|
(1,f) |
|
|
|
|
|
(,t) | (1,f)
|
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
|
|
-- nested composite types
|
|
|
|
|
CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer);
|
|
|
|
|
CREATE TABLE composite_trigger_nested_test(c comp3);
|
|
|
|
|
CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$
|
|
|
|
|
return 'MODIFY'
|
|
|
|
|
$$ LANGUAGE plpythonu;
|
|
|
|
|
CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f();
|
|
|
|
|
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(NULL, 't'), ROW(1, 'f'), NULL));
|
|
|
|
|
SELECT * FROM composite_trigger_nested_test;
|
|
|
|
|
c
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
("(1,f)",,3)
|
|
|
|
|
("(,t)","(1,f)",)
|
|
|
|
|
(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)
|
|
|
|
|
|
|
|
|
|
-- check that SQL run in trigger code can see transition tables
|
|
|
|
|
CREATE TABLE transition_table_test (id int, name text);
|
|
|
|
|
INSERT INTO transition_table_test VALUES (1, 'a');
|
|
|
|
|
CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpythonu AS
|
|
|
|
|
$$
|
|
|
|
|
rv = plpy.execute("SELECT * FROM old_table")
|
|
|
|
|
assert(rv.nrows() == 1)
|
|
|
|
|
plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
|
|
|
|
|
rv = plpy.execute("SELECT * FROM new_table")
|
|
|
|
|
assert(rv.nrows() == 1)
|
|
|
|
|
plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
|
|
|
|
|
return None
|
|
|
|
|
$$;
|
|
|
|
|
CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
|
|
|
|
|
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
|
|
|
|
|
FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
|
|
|
|
|
UPDATE transition_table_test SET name = 'b';
|
|
|
|
|
INFO: old: 1 -> a
|
|
|
|
|
INFO: new: 1 -> b
|
|
|
|
|
DROP TABLE transition_table_test;
|
|
|
|
|
DROP FUNCTION transition_table_test_f();
|
|
|
|
|
-- dealing with generated columns
|
|
|
|
|
CREATE FUNCTION generated_test_func1() RETURNS trigger
|
|
|
|
|
LANGUAGE plpythonu
|
|
|
|
|
AS $$
|
|
|
|
|
TD['new']['j'] = 5 # not allowed
|
|
|
|
|
return 'MODIFY'
|
|
|
|
|
$$;
|
|
|
|
|
CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
|
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
|
|
|
|
|
TRUNCATE trigger_test_generated;
|
|
|
|
|
INSERT INTO trigger_test_generated (i) VALUES (1);
|
|
|
|
|
ERROR: cannot set generated column "j"
|
|
|
|
|
CONTEXT: while modifying trigger row
|
|
|
|
|
PL/Python function "generated_test_func1"
|
|
|
|
|
SELECT * FROM trigger_test_generated;
|
|
|
|
|
i | j
|
|
|
|
|
---+---
|
|
|
|
|
(0 rows)
|
|
|
|
|
|