From 81ff88c18eca475f26701c6ac1af13628322cb9f Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Tue, 3 Dec 2024 13:20:25 +0100 Subject: [PATCH] Add hypertable support for transition table This adds support for using statement-level triggers with transition tables to hypertables. We deliberately do not support statement-level nor row-level triggers with transition tables on chunks and error out if attempts are made to either add such triggers to a chunk or create a hypertable from a table with such triggers. We also change the error messages to look more like the corresponding PostgreSQL error message. Co-Authored-By: James Sewell --- .unreleased/pr_6901 | 1 + src/copy.c | 46 +++++-- src/hypertable.c | 9 +- src/planner/planner.c | 12 +- src/process_utility.c | 25 +++- src/trigger.c | 29 +++-- src/trigger.h | 2 +- test/expected/triggers.out | 42 ++++--- test/sql/triggers.sql | 26 ++-- tsl/test/expected/hypercore_trigger.out | 152 ++++++++++++++++++++---- tsl/test/sql/hypercore_trigger.sql | 118 +++++++++++++++--- 11 files changed, 359 insertions(+), 103 deletions(-) create mode 100644 .unreleased/pr_6901 diff --git a/.unreleased/pr_6901 b/.unreleased/pr_6901 new file mode 100644 index 00000000000..6a8c0583b05 --- /dev/null +++ b/.unreleased/pr_6901 @@ -0,0 +1 @@ +Implements: #6901 Add hypertable support for transition tables diff --git a/src/copy.c b/src/copy.c index a1847f70f8e..5e8fb581f38 100644 --- a/src/copy.c +++ b/src/copy.c @@ -698,6 +698,7 @@ copyfrom(CopyChunkState *ccstate, ParseState *pstate, Hypertable *ht, MemoryCont uint64 processed = 0; bool has_before_insert_row_trig; bool has_instead_insert_row_trig; + bool has_after_insert_statement_trig; ExprState *qualexpr = NULL; ChunkDispatch *dispatch = ccstate->dispatch; @@ -808,6 +809,19 @@ copyfrom(CopyChunkState *ccstate, ParseState *pstate, Hypertable *ht, MemoryCont /* Prepare to catch AFTER triggers. */ AfterTriggerBeginQuery(); + /* + * If there are any triggers with transition tables on the named relation, + * we need to be prepared to capture transition tuples. Note that + * ccstate->cstate is null when we migrate from an existing table in a + * call from create_hypertable(), so we do not need a transition capture + * state in this case. + */ + if (ccstate->cstate) + ccstate->cstate->transition_capture = + MakeTransitionCaptureState(ccstate->rel->trigdesc, + RelationGetRelid(ccstate->rel), + CMD_INSERT); + if (ccstate->where_clause) qualexpr = ExecInitQual(castNode(List, ccstate->where_clause), NULL); @@ -848,8 +862,12 @@ copyfrom(CopyChunkState *ccstate, ParseState *pstate, Hypertable *ht, MemoryCont has_instead_insert_row_trig = (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_instead_row); + has_after_insert_statement_trig = + (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_new_table); + /* Depending on the configured trigger, enable or disable the multi-insert buffers */ - if (has_before_insert_row_trig || has_instead_insert_row_trig) + if (has_after_insert_statement_trig || has_before_insert_row_trig || + has_instead_insert_row_trig) { insertMethod = CIM_SINGLE; ereport(DEBUG1, @@ -1043,12 +1061,15 @@ copyfrom(CopyChunkState *ccstate, ParseState *pstate, Hypertable *ht, MemoryCont NULL, NIL, false); - /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, - resultRelInfo, - myslot, - recheckIndexes, - NULL /* transition capture */); + /* AFTER ROW INSERT Triggers. We do not need to do this if we + * are migrating data from an existing table in a call from + * create_hypertable(). */ + if (ccstate->cstate) + ExecARInsertTriggers(estate, + resultRelInfo, + myslot, + recheckIndexes, + ccstate->cstate->transition_capture); } else { @@ -1105,8 +1126,15 @@ copyfrom(CopyChunkState *ccstate, ParseState *pstate, Hypertable *ht, MemoryCont MemoryContextSwitchTo(oldcontext); - /* Execute AFTER STATEMENT insertion triggers */ - ExecASInsertTriggers(estate, resultRelInfo, NULL); + /* + * Execute AFTER STATEMENT insertion triggers. + * + * We do not need to do this ccstate->cstate is NULL, which is the case + * when migrating data from an existing table in a call from + * create_hypertable(). + */ + if (ccstate->cstate) + ExecASInsertTriggers(estate, resultRelInfo, ccstate->cstate->transition_capture); /* Handle queued AFTER triggers */ AfterTriggerEndQuery(estate); diff --git a/src/hypertable.c b/src/hypertable.c index 449c675b0ea..ac3c2c5e765 100644 --- a/src/hypertable.c +++ b/src/hypertable.c @@ -1925,13 +1925,10 @@ ts_hypertable_create_from_info(Oid table_relid, int32 hypertable_id, uint32 flag hypertable_create_schema(NameStr(*associated_schema_name)); /* - * Hypertables do not support transition tables in triggers, so if the - * table already has such triggers we bail out + * Hypertables do not support arbitrary triggers, so if the table already + * has unsupported triggers we bail out */ - if (ts_relation_has_transition_table_trigger(table_relid)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("hypertables do not support transition tables in triggers"))); + ts_check_unsupported_triggers(table_relid); if (NULL == chunk_sizing_info) chunk_sizing_info = ts_chunk_sizing_info_get_default_disabled(table_relid); diff --git a/src/planner/planner.c b/src/planner/planner.c index c70a6c88ec9..9f9c9133dc7 100644 --- a/src/planner/planner.c +++ b/src/planner/planner.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -1272,8 +1273,15 @@ timescaledb_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, Rang TsRelType reltype; Hypertable *ht; - /* Quick exit if this is a relation we're not interested in */ - if (!valid_hook_call() || !OidIsValid(rte->relid) || IS_DUMMY_REL(rel)) + /* + * Quick exit if this is a relation we're not interested in. + * + * If the rtekind is a named tuple store, it is a named tuple store *for* + * the relation rte->relid (e.g., a transition table for a trigger), but + * not the relation itself. + */ + if (!valid_hook_call() || rte->rtekind == RTE_NAMEDTUPLESTORE || !OidIsValid(rte->relid) || + IS_DUMMY_REL(rel)) { if (prev_set_rel_pathlist_hook != NULL) (*prev_set_rel_pathlist_hook)(root, rel, rti, rte); diff --git a/src/process_utility.c b/src/process_utility.c index 54701b5e2c6..1aee1d67243 100644 --- a/src/process_utility.c +++ b/src/process_utility.c @@ -4440,6 +4440,13 @@ process_create_trigger_start(ProcessUtilityArgs *args) Hypertable *ht; ObjectAddress PG_USED_FOR_ASSERTS_ONLY address; Oid relid = RangeVarGetRelid(stmt->relation, NoLock, true); + int16 tgtype; + + TRIGGER_CLEAR_TYPE(tgtype); + if (stmt->row) + TRIGGER_SETT_ROW(tgtype); + tgtype |= stmt->timing; + tgtype |= stmt->events; hcache = ts_hypertable_cache_pin(); ht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK); @@ -4456,21 +4463,31 @@ process_create_trigger_start(ProcessUtilityArgs *args) if (ts_chunk_get_by_relid(relid, false) != NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg( - "trigger with transition tables not supported on hypertable chunks"))); + errmsg("triggers with transition tables are not supported on " + "hypertable chunks"))); return DDL_CONTINUE; } - if (stmt->transitionRels) + /* + * We do not support ROW triggers with transition tables on hypertables + * since these are not supported on inheritance children, and we use + * inheritance for our chunks (it is actually not supported for + * declarative partition tables either). + */ + if (stmt->transitionRels && TRIGGER_FOR_ROW(tgtype)) { ts_cache_release(hcache); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("trigger with transition tables not supported on hypertables"))); + errmsg("ROW triggers with transition tables are not supported on hypertables"))); } add_hypertable_to_process_args(args, ht); + /* + * If it is not a ROW trigger, we do not need to create the ROW triggers + * on the chunks, so we can return early. + */ if (!stmt->row) { ts_cache_release(hcache); diff --git a/src/trigger.c b/src/trigger.c index 316d89bbddc..43d7f27ca36 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -110,11 +110,13 @@ create_trigger_handler(const Trigger *trigger, void *arg) { const Chunk *chunk = arg; - if (TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable) || - TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable)) + if ((TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable) || + TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable)) && + TRIGGER_FOR_ROW(trigger->tgtype)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("hypertables do not support transition tables in triggers"))); + errmsg("ROW triggers with transition tables are not supported on hypertable " + "chunks"))); if (trigger_is_chunk_trigger(trigger)) ts_trigger_create_on_chunk(trigger->tgoid, @@ -164,24 +166,21 @@ ts_trigger_create_all_on_chunk(const Chunk *chunk) static bool check_for_transition_table(const Trigger *trigger, void *arg) { - bool *found = arg; - - if (TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable) || - TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable)) + if ((TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable) || + TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable)) && + TRIGGER_FOR_ROW(trigger->tgtype)) { - *found = true; + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ROW triggers with transition tables are not supported on hypertables"))); return false; } return true; } -bool -ts_relation_has_transition_table_trigger(Oid relid) +void +ts_check_unsupported_triggers(Oid relid) { - bool found = false; - - for_each_trigger(relid, check_for_transition_table, &found); - - return found; + for_each_trigger(relid, check_for_transition_table, NULL); } diff --git a/src/trigger.h b/src/trigger.h index 00ed2c12ece..9e26951c180 100644 --- a/src/trigger.h +++ b/src/trigger.h @@ -17,4 +17,4 @@ extern void ts_trigger_create_on_chunk(Oid trigger_oid, const char *chunk_schema_name, const char *chunk_table_name); extern TSDLLEXPORT void ts_trigger_create_all_on_chunk(const Chunk *chunk); -extern bool ts_relation_has_transition_table_trigger(Oid relid); +extern void ts_check_unsupported_triggers(Oid relid); diff --git a/test/expected/triggers.out b/test/expected/triggers.out index 56bc3ba925b..9cea6941894 100644 --- a/test/expected/triggers.out +++ b/test/expected/triggers.out @@ -416,12 +416,15 @@ DROP TABLE location; -- test triggers with transition tables -- test creating hypertable from table with triggers with transition tables CREATE TABLE transition_test(time timestamptz NOT NULL); -CREATE TRIGGER t1 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t1_stmt AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t1_row AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +-- We do not support ROW triggers with transition tables, so we need +-- to remove it to be able to create the hypertable. \set ON_ERROR_STOP 0 SELECT create_hypertable('transition_test','time'); -ERROR: hypertables do not support transition tables in triggers +ERROR: ROW triggers with transition tables are not supported on hypertables \set ON_ERROR_STOP 1 -DROP TRIGGER t1 ON transition_test; +DROP TRIGGER t1_row ON transition_test; SELECT create_hypertable('transition_test','time'); create_hypertable ------------------------------ @@ -430,27 +433,30 @@ SELECT create_hypertable('transition_test','time'); -- Insert some rows to create a chunk INSERT INTO transition_test values ('2020-01-10'); +WARNING: FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1_stmt SELECT chunk FROM show_chunks('transition_test') tbl(chunk) limit 1 \gset -- test creating trigger with transition tables on existing hypertable -\set ON_ERROR_STOP 0 -CREATE TRIGGER t2 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertables CREATE TRIGGER t3 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertables CREATE TRIGGER t4 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertables -CREATE TRIGGER t2 AFTER INSERT ON :chunk REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertable chunks +INSERT INTO transition_test values ('2020-01-11'); +WARNING: FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1_stmt +COPY transition_test FROM STDIN; +WARNING: FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1_stmt +UPDATE transition_test SET time = '2020-01-12' WHERE time = '2020-01-11'; +WARNING: FIRING trigger when: AFTER level: STATEMENT op: UPDATE cnt: 0 trigger_name t3 +DELETE FROM transition_test WHERE time = '2020-01-12'; +WARNING: FIRING trigger when: AFTER level: STATEMENT op: DELETE cnt: 0 trigger_name t4 +\set ON_ERROR_STOP 0 CREATE TRIGGER t3 AFTER UPDATE ON :chunk REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertable chunks +ERROR: triggers with transition tables are not supported on hypertable chunks CREATE TRIGGER t4 AFTER DELETE ON :chunk REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertable chunks -CREATE TRIGGER t2 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertables -CREATE TRIGGER t3 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertables -CREATE TRIGGER t4 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); -ERROR: trigger with transition tables not supported on hypertables +ERROR: triggers with transition tables are not supported on hypertable chunks +CREATE TRIGGER t5 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +ERROR: ROW triggers with transition tables are not supported on hypertables +CREATE TRIGGER t6 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +ERROR: ROW triggers with transition tables are not supported on hypertables +CREATE TRIGGER t7 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +ERROR: ROW triggers with transition tables are not supported on hypertables -- Test insert blocker trigger does not crash when called directly SELECT _timescaledb_functions.insert_blocker(); ERROR: insert_blocker: not called by trigger manager diff --git a/test/sql/triggers.sql b/test/sql/triggers.sql index 949e1e5fd48..3ad4b65cfd8 100644 --- a/test/sql/triggers.sql +++ b/test/sql/triggers.sql @@ -305,12 +305,15 @@ DROP TABLE location; -- test triggers with transition tables -- test creating hypertable from table with triggers with transition tables CREATE TABLE transition_test(time timestamptz NOT NULL); -CREATE TRIGGER t1 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t1_stmt AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t1_row AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +-- We do not support ROW triggers with transition tables, so we need +-- to remove it to be able to create the hypertable. \set ON_ERROR_STOP 0 SELECT create_hypertable('transition_test','time'); \set ON_ERROR_STOP 1 -DROP TRIGGER t1 ON transition_test; +DROP TRIGGER t1_row ON transition_test; SELECT create_hypertable('transition_test','time'); -- Insert some rows to create a chunk @@ -318,17 +321,22 @@ INSERT INTO transition_test values ('2020-01-10'); SELECT chunk FROM show_chunks('transition_test') tbl(chunk) limit 1 \gset -- test creating trigger with transition tables on existing hypertable -\set ON_ERROR_STOP 0 -CREATE TRIGGER t2 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); CREATE TRIGGER t3 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); CREATE TRIGGER t4 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); -CREATE TRIGGER t2 AFTER INSERT ON :chunk REFERENCING NEW TABLE AS new_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); + +INSERT INTO transition_test values ('2020-01-11'); +COPY transition_test FROM STDIN; +2020-01-09 +\. +UPDATE transition_test SET time = '2020-01-12' WHERE time = '2020-01-11'; +DELETE FROM transition_test WHERE time = '2020-01-12'; + +\set ON_ERROR_STOP 0 CREATE TRIGGER t3 AFTER UPDATE ON :chunk REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); CREATE TRIGGER t4 AFTER DELETE ON :chunk REFERENCING OLD TABLE AS old_trans FOR EACH STATEMENT EXECUTE FUNCTION test_trigger(); - -CREATE TRIGGER t2 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); -CREATE TRIGGER t3 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); -CREATE TRIGGER t4 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t5 AFTER INSERT ON transition_test REFERENCING NEW TABLE AS new_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t6 AFTER UPDATE ON transition_test REFERENCING NEW TABLE AS new_trans OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); +CREATE TRIGGER t7 AFTER DELETE ON transition_test REFERENCING OLD TABLE AS old_trans FOR EACH ROW EXECUTE FUNCTION test_trigger(); -- Test insert blocker trigger does not crash when called directly SELECT _timescaledb_functions.insert_blocker(); diff --git a/tsl/test/expected/hypercore_trigger.out b/tsl/test/expected/hypercore_trigger.out index 4dfeae2a897..3de60ad3cdd 100644 --- a/tsl/test/expected/hypercore_trigger.out +++ b/tsl/test/expected/hypercore_trigger.out @@ -129,6 +129,7 @@ begin insert into saved_rows select n.*, true, tg_op from new_table n; insert into saved_rows select o.*, false, tg_op from old_table o; end case; + return null; end; $$ language plpgsql; create function count_ops() returns trigger as $$ @@ -199,8 +200,12 @@ insert into sample(created_at, location_id, device_id, owner_id, temp, humidity) -- this case, the trigger will just save away the rows into a separate -- table and check that we get the same number of rows with the same -- values. -create trigger save_insert_row_trg before insert on :chunk1 for each row execute function save_row(); -create trigger count_inserts_trg before insert on :chunk1 for each statement execute function count_ops(); +create trigger save_insert_row_trg + before insert on :chunk1 + for each row execute function save_row(); +create trigger count_inserts_trg + before insert on :chunk1 + for each statement execute function count_ops(); insert into :chunk1(created_at, location_id, device_id, owner_id, temp, humidity) select created_at, location_id, device_id, owner_id, temp, humidity from sample limit 2; select * from saved_rows where kind = 'INSERT'; @@ -233,9 +238,15 @@ select sum(inserts), sum(updates), sum(deletes) from count_stmt; (1 row) truncate saved_rows, count_stmt; +drop trigger save_insert_row_trg on :chunk1; +drop trigger count_inserts_trg on :chunk1; -- Run update and upsert tests -create trigger save_update_row_trg before update on :chunk1 for each row execute function save_row(); -create trigger count_update_trg before update on :chunk1 for each statement execute function count_ops(); +create trigger save_update_row_trg + before update on :chunk1 + for each row execute function save_row(); +create trigger count_update_trg + before update on :chunk1 + for each statement execute function count_ops(); update :chunk1 set temp = 9.99 where device_id = 666; select * from saved_rows where kind = 'UPDATE'; metric_id | created_at | location_id | owner_id | device_id | temp | humidity | new_row | kind @@ -277,13 +288,19 @@ select * from saved_rows where kind = 'UPDATE'; select sum(inserts), sum(updates), sum(deletes) from count_stmt; sum | sum | sum -----+-----+----- - 1 | 1 | 0 + 0 | 1 | 0 (1 row) truncate saved_rows, count_stmt; +drop trigger save_update_row_trg on :chunk1; +drop trigger count_update_trg on :chunk1; -- Run delete tests -create trigger save_delete_row_trg before delete on :chunk1 for each row execute function save_row(); -create trigger count_delete_trg before delete on :chunk1 for each statement execute function count_ops(); +create trigger save_delete_row_trg + before delete on :chunk1 + for each row execute function save_row(); +create trigger count_delete_trg + before delete on :chunk1 + for each statement execute function count_ops(); delete from :chunk1 where device_id = 666; select * from saved_rows where kind = 'DELETE'; metric_id | created_at | location_id | owner_id | device_id | temp | humidity | new_row | kind @@ -300,29 +317,120 @@ select sum(inserts), sum(updates), sum(deletes) from count_stmt; 0 | 0 | 1 (1 row) -truncate saved_rows; --- TODO(#1084): Transition tables do not work currently for chunks at --- all. Once this is implemented, we should get values saved in the --- saved_rows table, so keeping the function around for the time being --- and right now just to test that we get a proper error. +truncate saved_rows, count_stmt; +drop trigger save_delete_row_trg on :chunk1; +drop trigger count_delete_trg on :chunk1; +-- Checking that transition tables on triggers is not supported on +-- individual chunks. \set ON_ERROR_STOP 0 -create trigger save_insert_transition_table_trg +create trigger row_insert_transition_table_trg after insert on :chunk1 referencing new table as new_table for each statement execute function save_transition_table(); -ERROR: trigger with transition tables not supported on hypertable chunks -create trigger save_update_transition_table_trg +ERROR: triggers with transition tables are not supported on hypertable chunks +create trigger row_update_transition_table_trg after update on :chunk1 + referencing new table as new_table + for each statement execute function save_transition_table(); +ERROR: triggers with transition tables are not supported on hypertable chunks +create trigger row_delete_transition_table_trg + after delete on :chunk1 + referencing new table as new_table + for each statement execute function save_transition_table(); +ERROR: triggers with transition tables are not supported on hypertable chunks +create trigger row_insert_transition_table_trg + after insert on :chunk1 + referencing new table as new_table + for each row execute function save_transition_table(); +ERROR: triggers with transition tables are not supported on hypertable chunks +create trigger row_update_transition_table_trg + after update on :chunk1 + referencing new table as new_table + for each row execute function save_transition_table(); +ERROR: triggers with transition tables are not supported on hypertable chunks +create trigger row_delete_transition_table_trg + after delete on :chunk1 + referencing new table as new_table + for each row execute function save_transition_table(); +ERROR: triggers with transition tables are not supported on hypertable chunks +\set ON_ERROR_STOP 1 +-- Remove duplicates from readings table so that we can run the tests +-- below. +delete from readings where created_at in (select created_at from sample); +-- Test transition tables on hypertables using hypercore access method +create trigger save_insert_transition_table_trg + after insert on readings + referencing new table as new_table + for each statement execute function save_transition_table(); +insert into readings(created_at, location_id, device_id, owner_id, temp, humidity) +select created_at, location_id, device_id, owner_id, temp, humidity from sample +order by created_at limit 2; +select * from saved_rows; + metric_id | created_at | location_id | owner_id | device_id | temp | humidity | new_row | kind +-----------+------------------------------+-------------+----------+-----------+------+----------+---------+-------- + 11533 | Wed Jun 01 00:01:23 2022 PDT | 999 | 111 | 666 | 3.14 | 3.14 | t | INSERT + 11534 | Wed Jun 01 00:02:23 2022 PDT | 999 | 112 | 666 | 3.14 | 3.14 | t | INSERT +(2 rows) + +truncate saved_rows; +copy readings(created_at, location_id, device_id, owner_id, temp, humidity) from stdin with (format csv); +select * from saved_rows; + metric_id | created_at | location_id | owner_id | device_id | temp | humidity | new_row | kind +-----------+------------------------------+-------------+----------+-----------+------+----------+---------+-------- + 11535 | Wed Jun 01 00:01:35 2022 PDT | 999 | 111 | 666 | 3.14 | 3.14 | t | INSERT +(1 row) + +truncate saved_rows; +create trigger save_update_transition_table_trg + after update on readings referencing new table as new_table old table as old_table for each statement execute function save_transition_table(); -ERROR: trigger with transition tables not supported on hypertable chunks +select * from readings where location_id = 999; + metric_id | created_at | location_id | owner_id | device_id | temp | humidity +-----------+------------------------------+-------------+----------+-----------+------+---------- + 11533 | Wed Jun 01 00:01:23 2022 PDT | 999 | 111 | 666 | 3.14 | 3.14 + 11534 | Wed Jun 01 00:02:23 2022 PDT | 999 | 112 | 666 | 3.14 | 3.14 + 11535 | Wed Jun 01 00:01:35 2022 PDT | 999 | 111 | 666 | 3.14 | 3.14 +(3 rows) + +update readings set humidity = 99.99 where location_id = 999; +select * from saved_rows; + metric_id | created_at | location_id | owner_id | device_id | temp | humidity | new_row | kind +-----------+------------------------------+-------------+----------+-----------+------+----------+---------+-------- + 11533 | Wed Jun 01 00:01:23 2022 PDT | 999 | 111 | 666 | 3.14 | 99.99 | t | UPDATE + 11534 | Wed Jun 01 00:02:23 2022 PDT | 999 | 112 | 666 | 3.14 | 99.99 | t | UPDATE + 11535 | Wed Jun 01 00:01:35 2022 PDT | 999 | 111 | 666 | 3.14 | 99.99 | t | UPDATE + 11533 | Wed Jun 01 00:01:23 2022 PDT | 999 | 111 | 666 | 3.14 | 3.14 | f | UPDATE + 11534 | Wed Jun 01 00:02:23 2022 PDT | 999 | 112 | 666 | 3.14 | 3.14 | f | UPDATE + 11535 | Wed Jun 01 00:01:35 2022 PDT | 999 | 111 | 666 | 3.14 | 3.14 | f | UPDATE +(6 rows) + +truncate saved_rows; create trigger save_delete_transition_table_trg - after delete on :chunk1 + after delete on readings referencing old table as old_table for each statement execute function save_transition_table(); -ERROR: trigger with transition tables not supported on hypertable chunks -\set ON_ERROR_STOP 1 +select * from readings where location_id = 999; + metric_id | created_at | location_id | owner_id | device_id | temp | humidity +-----------+------------------------------+-------------+----------+-----------+------+---------- + 11533 | Wed Jun 01 00:01:23 2022 PDT | 999 | 111 | 666 | 3.14 | 99.99 + 11534 | Wed Jun 01 00:02:23 2022 PDT | 999 | 112 | 666 | 3.14 | 99.99 + 11535 | Wed Jun 01 00:01:35 2022 PDT | 999 | 111 | 666 | 3.14 | 99.99 +(3 rows) + +delete from readings where location_id = 999; +select * from saved_rows; + metric_id | created_at | location_id | owner_id | device_id | temp | humidity | new_row | kind +-----------+------------------------------+-------------+----------+-----------+------+----------+---------+-------- + 11533 | Wed Jun 01 00:01:23 2022 PDT | 999 | 111 | 666 | 3.14 | 99.99 | f | DELETE + 11534 | Wed Jun 01 00:02:23 2022 PDT | 999 | 112 | 666 | 3.14 | 99.99 | f | DELETE + 11535 | Wed Jun 01 00:01:35 2022 PDT | 999 | 111 | 666 | 3.14 | 99.99 | f | DELETE +(3 rows) + +truncate saved_rows; -- Check truncate trigger -create trigger notify_truncate after truncate on :chunk1 for each statement execute function notify_action(); -truncate :chunk1; -NOTICE: table _hyper_1_1_chunk was truncated +create trigger notify_truncate + after truncate on readings + for each statement execute function notify_action(); +truncate readings; +NOTICE: table readings was truncated diff --git a/tsl/test/sql/hypercore_trigger.sql b/tsl/test/sql/hypercore_trigger.sql index 48bf043d689..7c590961ae8 100644 --- a/tsl/test/sql/hypercore_trigger.sql +++ b/tsl/test/sql/hypercore_trigger.sql @@ -30,6 +30,7 @@ begin insert into saved_rows select n.*, true, tg_op from new_table n; insert into saved_rows select o.*, false, tg_op from old_table o; end case; + return null; end; $$ language plpgsql; @@ -87,8 +88,12 @@ insert into sample(created_at, location_id, device_id, owner_id, temp, humidity) -- this case, the trigger will just save away the rows into a separate -- table and check that we get the same number of rows with the same -- values. -create trigger save_insert_row_trg before insert on :chunk1 for each row execute function save_row(); -create trigger count_inserts_trg before insert on :chunk1 for each statement execute function count_ops(); +create trigger save_insert_row_trg + before insert on :chunk1 + for each row execute function save_row(); +create trigger count_inserts_trg + before insert on :chunk1 + for each statement execute function count_ops(); insert into :chunk1(created_at, location_id, device_id, owner_id, temp, humidity) select created_at, location_id, device_id, owner_id, temp, humidity from sample limit 2; @@ -106,9 +111,16 @@ select sum(inserts), sum(updates), sum(deletes) from count_stmt; truncate saved_rows, count_stmt; +drop trigger save_insert_row_trg on :chunk1; +drop trigger count_inserts_trg on :chunk1; + -- Run update and upsert tests -create trigger save_update_row_trg before update on :chunk1 for each row execute function save_row(); -create trigger count_update_trg before update on :chunk1 for each statement execute function count_ops(); +create trigger save_update_row_trg + before update on :chunk1 + for each row execute function save_row(); +create trigger count_update_trg + before update on :chunk1 + for each statement execute function count_ops(); update :chunk1 set temp = 9.99 where device_id = 666; @@ -127,39 +139,111 @@ select sum(inserts), sum(updates), sum(deletes) from count_stmt; truncate saved_rows, count_stmt; +drop trigger save_update_row_trg on :chunk1; +drop trigger count_update_trg on :chunk1; + -- Run delete tests -create trigger save_delete_row_trg before delete on :chunk1 for each row execute function save_row(); -create trigger count_delete_trg before delete on :chunk1 for each statement execute function count_ops(); +create trigger save_delete_row_trg + before delete on :chunk1 + for each row execute function save_row(); +create trigger count_delete_trg + before delete on :chunk1 + for each statement execute function count_ops(); delete from :chunk1 where device_id = 666; select * from saved_rows where kind = 'DELETE'; select sum(inserts), sum(updates), sum(deletes) from count_stmt; -truncate saved_rows; +truncate saved_rows, count_stmt; + +drop trigger save_delete_row_trg on :chunk1; +drop trigger count_delete_trg on :chunk1; --- TODO(#1084): Transition tables do not work currently for chunks at --- all. Once this is implemented, we should get values saved in the --- saved_rows table, so keeping the function around for the time being --- and right now just to test that we get a proper error. +-- Checking that transition tables on triggers is not supported on +-- individual chunks. \set ON_ERROR_STOP 0 -create trigger save_insert_transition_table_trg +create trigger row_insert_transition_table_trg + after insert on :chunk1 + referencing new table as new_table + for each statement execute function save_transition_table(); +create trigger row_update_transition_table_trg + after update on :chunk1 + referencing new table as new_table + for each statement execute function save_transition_table(); +create trigger row_delete_transition_table_trg + after delete on :chunk1 + referencing new table as new_table + for each statement execute function save_transition_table(); +create trigger row_insert_transition_table_trg after insert on :chunk1 referencing new table as new_table + for each row execute function save_transition_table(); +create trigger row_update_transition_table_trg + after update on :chunk1 + referencing new table as new_table + for each row execute function save_transition_table(); +create trigger row_delete_transition_table_trg + after delete on :chunk1 + referencing new table as new_table + for each row execute function save_transition_table(); +\set ON_ERROR_STOP 1 + +-- Remove duplicates from readings table so that we can run the tests +-- below. +delete from readings where created_at in (select created_at from sample); + +-- Test transition tables on hypertables using hypercore access method +create trigger save_insert_transition_table_trg + after insert on readings + referencing new table as new_table for each statement execute function save_transition_table(); +insert into readings(created_at, location_id, device_id, owner_id, temp, humidity) +select created_at, location_id, device_id, owner_id, temp, humidity from sample +order by created_at limit 2; + +select * from saved_rows; + +truncate saved_rows; + +copy readings(created_at, location_id, device_id, owner_id, temp, humidity) from stdin with (format csv); +"2022-06-01 00:01:35",999,666,111,3.14,3.14 +\. + +select * from saved_rows; + +truncate saved_rows; + create trigger save_update_transition_table_trg - after update on :chunk1 + after update on readings referencing new table as new_table old table as old_table for each statement execute function save_transition_table(); +select * from readings where location_id = 999; + +update readings set humidity = 99.99 where location_id = 999; + +select * from saved_rows; + +truncate saved_rows; + create trigger save_delete_transition_table_trg - after delete on :chunk1 + after delete on readings referencing old table as old_table for each statement execute function save_transition_table(); -\set ON_ERROR_STOP 1 + +select * from readings where location_id = 999; + +delete from readings where location_id = 999; + +select * from saved_rows; + +truncate saved_rows; -- Check truncate trigger -create trigger notify_truncate after truncate on :chunk1 for each statement execute function notify_action(); +create trigger notify_truncate + after truncate on readings + for each statement execute function notify_action(); -truncate :chunk1; +truncate readings;