I have couple temporal tables in my db.
CREATE TABLE temporal (
version_id SERIAL PRIMARY KEY,
some_id TEXT,
some_data TEXT,
valid_from TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
valid_to TIMESTAMP DEFAULT NULL,
is_current BOOLEAN NOT NULL DEFAULT TRUE
);
I have created a trigger that acts on inserts
CREATE OR REPLACE TRIGGER my_trigger
BEFORE INSERT ON temporal
FOR EACH ROW
EXECUTE manage_temporal_version();
The plpgsql function the trigger calls is quite usual temporal versioning trigger that basically does two things:
- It checks against some unique fields whether the table already has that row and if it's currently the active row or not
- If such row is found, it deprecates the previous
- Continues to insert a new row with the new data
CREATE OR REPLACE FUNCTION manage_record_version()
RETURNS TRIGGER AS $$
DECLARE
existing_row RECORD;
where_clause TEXT;
col_name TEXT;
i INTEGER;
BEGIN
-- Build WHERE clause dynamically based on trigger arguments
where_clause := 'is_current = TRUE';
-- TG_ARGV contains the column names passed to the trigger
FOR i IN 0 .. TG_NARGS - 1 LOOP
col_name := TG_ARGV[i];
-- Add condition for each key column
where_clause := where_clause || format(' AND %I = $1.%I', col_name, col_name);
END LOOP;
-- Find existing current row with matching key columns
EXECUTE format('SELECT * FROM %I WHERE %s FOR UPDATE', TG_TABLE_NAME, where_clause)
INTO existing_row
USING NEW;
-- If a current row exists, version it
IF FOUND THEN
-- Close the existing version
EXECUTE format(
'UPDATE %I SET valid_to = CURRENT_TIMESTAMP, is_current = FALSE WHERE version_id = $1',
TG_TABLE_NAME
) USING existing_row.version_id;
-- Set up NEW record for the new version
NEW.version_id := nextval(format('%s_version_id_seq', TG_TABLE_NAME));
NEW.valid_from := CURRENT_TIMESTAMP;
NEW.valid_to := NULL;
NEW.is_current := TRUE;
RETURN NEW;
END IF;
-- If no existing current row, proceed with normal insert
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
I was thinking about that I wouldn't like to let people run direct update queries on the table, because the table rows shouldn't be updated directly due to this temporal versioning. I was trying an approach that I will change the trigger to act before insert or update and then during insert I'll do the above procedure and during updates I will simply check whether the update is happening on already closed row or on the currently active row. If updating closed row an exception is thrown preventing the update and if updating the current row the update would follow similar procedure as the insert. Sounds good in theory, but there's one big problem:
The inserts and updates inside the triggered function will invoke the trigger as well leading to infinitely cascading triggers.
Is there any way to make this work? Or is there any commonly agreed good way to handle inserts and updates on temporal tables?