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. 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 <[email protected]>
  • Loading branch information
mkindahl and James Sewell committed Dec 9, 2024
1 parent 1a291aa commit 25604c3
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 103 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
46 changes: 37 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,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);
Expand Down
9 changes: 3 additions & 6 deletions src/hypertable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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
25 changes: 21 additions & 4 deletions src/process_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
29 changes: 14 additions & 15 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 Expand Up @@ -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);
}
2 changes: 1 addition & 1 deletion src/trigger.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
42 changes: 24 additions & 18 deletions test/expected/triggers.out
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------------------
Expand All @@ -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
Expand Down
26 changes: 17 additions & 9 deletions test/sql/triggers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -305,30 +305,38 @@ 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
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();
Expand Down
Loading

0 comments on commit 25604c3

Please sign in to comment.