Skip to content

Commit

Permalink
Add hypertable support for transition table
Browse files Browse the repository at this point in the history
This adds support for using statement-level triggers with transition
tables to hypertables and chunks of hypertables.

Co-Authored-By: James Sewell <[email protected]>
  • Loading branch information
mkindahl and James Sewell committed Dec 3, 2024
1 parent 8d29760 commit 26fcff1
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 99 deletions.
1 change: 1 addition & 0 deletions .unreleased/pr_6901
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implements: #6901 Add hypertable support for transition tables
45 changes: 36 additions & 9 deletions src/copy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -1105,8 +1126,14 @@ 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 if we are 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);
Expand Down
9 changes: 0 additions & 9 deletions src/hypertable.c
Original file line number Diff line number Diff line change
Expand Up @@ -1924,15 +1924,6 @@ ts_hypertable_create_from_info(Oid table_relid, int32 hypertable_id, uint32 flag
if (!OidIsValid(associated_schema_oid))
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
*/
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")));

if (NULL == chunk_sizing_info)
chunk_sizing_info = ts_chunk_sizing_info_get_default_disabled(table_relid);

Expand Down
12 changes: 10 additions & 2 deletions src/planner/planner.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <miscadmin.h>
#include <nodes/makefuncs.h>
#include <nodes/nodeFuncs.h>
#include <nodes/parsenodes.h>
#include <nodes/plannodes.h>
#include <optimizer/appendinfo.h>
#include <optimizer/clauses.h>
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 1 addition & 14 deletions src/process_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -4443,6 +4443,7 @@ process_create_trigger_start(ProcessUtilityArgs *args)

hcache = ts_hypertable_cache_pin();
ht = ts_hypertable_cache_get_entry(hcache, relid, CACHE_FLAG_MISSING_OK);

if (ht == NULL)
{
ts_cache_release(hcache);
Expand All @@ -4452,23 +4453,9 @@ process_create_trigger_start(ProcessUtilityArgs *args)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("triggers are not supported on continuous aggregate")));

if (stmt->transitionRels)
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")));
return DDL_CONTINUE;
}

if (stmt->transitionRels)
{
ts_cache_release(hcache);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger with transition tables not supported on hypertables")));
}

add_hypertable_to_process_args(args, ht);

if (!stmt->row)
Expand Down
8 changes: 5 additions & 3 deletions src/trigger.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 16 additions & 20 deletions test/expected/triggers.out
Original file line number Diff line number Diff line change
Expand Up @@ -417,11 +417,6 @@ DROP TABLE location;
-- 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();
\set ON_ERROR_STOP 0
SELECT create_hypertable('transition_test','time');
ERROR: hypertables do not support transition tables in triggers
\set ON_ERROR_STOP 1
DROP TRIGGER t1 ON transition_test;
SELECT create_hypertable('transition_test','time');
create_hypertable
------------------------------
Expand All @@ -430,27 +425,28 @@ 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
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
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
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
INSERT INTO transition_test values ('2020-01-11');
WARNING: FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1
COPY transition_test FROM STDIN;
WARNING: FIRING trigger when: AFTER level: STATEMENT op: INSERT cnt: 0 trigger_name t1
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 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 inheritance children
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 inheritance children
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 inheritance children
-- Test insert blocker trigger does not crash when called directly
SELECT _timescaledb_functions.insert_blocker();
ERROR: insert_blocker: not called by trigger manager
Expand Down
21 changes: 11 additions & 10 deletions test/sql/triggers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -307,28 +307,29 @@ DROP TABLE location;
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();

\set ON_ERROR_STOP 0
SELECT create_hypertable('transition_test','time');
\set ON_ERROR_STOP 1
DROP TRIGGER t1 ON transition_test;
SELECT create_hypertable('transition_test','time');

-- Insert some rows to create a chunk
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();
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();
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 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();
Expand Down
Loading

0 comments on commit 26fcff1

Please sign in to comment.