Compare commits

..

3 Commits

Author SHA1 Message Date
Michael Hoennig
ac33f99222 remove table tx_history and use table tx_context instead 2024-08-27 09:55:05 +02:00
Michael Hoennig
f973868bf9 remove column tx_contextid and use txId as primary key 2024-08-27 09:55:04 +02:00
Michael Hoennig
36c21505a3 working historization.sql but with separate tx_history table 2024-08-27 06:16:53 +02:00
8 changed files with 399 additions and 233 deletions

View File

@ -0,0 +1,37 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImportHostingAssets into local" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_POSTGRES_ADMIN_PASSWORD" value="password" />
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="postgres" />
<entry key="HSADMINNG_POSTGRES_JDBC_URL" value="jdbc:postgresql://localhost:5432/postgres" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importHostingAssets" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.migration.ImportHostingAssets&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -33,37 +33,4 @@
<RunAsTest>true</RunAsTest> <RunAsTest>true</RunAsTest>
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration default="false" name="ImportHostingAssets" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importHostingAssets" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.migration.ImportHostingAssets&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component> </component>

View File

@ -1,53 +0,0 @@
-- ========================================================
-- First Example Entity with History
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS customer (
"id" SERIAL PRIMARY KEY,
"reference" int not null unique, -- 10000-99999
"prefix" character(3) unique
);
CALL create_historicization('customer');
-- ========================================================
-- Second Example Entity with History
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS package_type (
"id" serial PRIMARY KEY,
"name" character varying(8)
);
CALL create_historicization('package_type');
-- ========================================================
-- Third Example Entity with History
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS package (
"id" serial PRIMARY KEY,
"name" character varying(5),
"customer_id" INTEGER REFERENCES customer(id)
);
CALL create_historicization('package');
-- ========================================================
-- query historical data
-- --------------------------------------------------------
ABORT;
BEGIN TRANSACTION;
SET LOCAL hsadminng.currentUser TO 'mih42_customer_aaa';
SET LOCAL hsadminng.currentTask TO 'adding customer_aaa';
INSERT INTO package (customer_id, name) VALUES (10000, 'aaa00');
COMMIT;
-- Usage:
SET hsadminng.timestamp TO '2022-07-12 08:53:27.723315';
SET hsadminng.timestamp TO '2022-07-12 11:38:27.723315';
SELECT * FROM customer_hv p WHERE prefix = 'aaa';

174
sql/historization-ng.sql Normal file
View File

@ -0,0 +1,174 @@
-- ========================================================
-- Historization
-- --------------------------------------------------------
drop PROCEDURE if exists tx_create_historical_view;
drop procedure if exists tx_create_historicization;
drop view if exists hs_hosting_asset_hv;
drop table if exists hs_hosting_asset_ex;
drop trigger if exists hs_hosting_asset_historicize_tg on hs_hosting_asset;
drop procedure if exists tx_historicize_tf;
drop type "tx_operation";
CREATE TYPE "tx_operation" AS ENUM ('INSERT', 'UPDATE', 'DELETE');
CREATE OR REPLACE FUNCTION tx_historicize_tf()
RETURNS trigger
LANGUAGE plpgsql STRICT AS $$
DECLARE
currentTask VARCHAR(127);
curContextId bigint;
"row" RECORD;
"alive" BOOLEAN;
"sql" varchar;
BEGIN
-- determine task
currentTask = current_setting('hsadminng.currentTask');
assert currentTask IS NOT NULL AND length(currentTask) >= 12,
format('hsadminng.currentTask (%s) must be defined and min 12 characters long, please use "SET LOCAL ...;"', currentTask);
assert length(currentTask) <= 127,
format('hsadminng.currentTask (%s) must not be longer than 127 characters"', currentTask);
IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE') THEN
"row" := NEW;
"alive" := TRUE;
ELSE -- DELETE or TRUNCATE
"row" := OLD;
"alive" := FALSE;
END IF;
curContextId := txid_current()+bigIntHash(currentTask);
sql := format('INSERT INTO %3$I_ex ' ||
' VALUES (DEFAULT, curContextId, %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
RAISE NOTICE 'sql: %', sql;
EXECUTE sql USING "row";
RETURN "row";
END; $$;
-- ------
CREATE OR REPLACE PROCEDURE tx_create_historical_view(baseTable varchar)
LANGUAGE plpgsql AS $$
DECLARE
createTriggerSQL varchar;
viewName varchar;
versionsTable varchar;
createViewSQL varchar;
baseCols varchar;
BEGIN
viewName = quote_ident(format('%s_hv', baseTable));
versionsTable = quote_ident(format('%s_ex', baseTable));
baseCols = (SELECT string_agg(quote_ident(column_name), ', ')
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = baseTable);
createViewSQL = format(
'CREATE OR REPLACE VIEW %1$s AS' ||
'(' ||
' SELECT %2$s' ||
' FROM %3$s' ||
' WHERE alive = TRUE' ||
' AND version_id IN' ||
' (' ||
' SELECT max(vt.version_id) AS history_id' ||
' FROM %3$s AS vt' ||
' JOIN tx_history as txh ON vt.tx_id = txh.tx_id' ||
' WHERE txh.tx_timestamp <= current_setting(''hsadminng.historical_timestamp'')::timestamp' ||
' GROUP BY id' ||
' )' ||
')',
viewName, baseCols, versionsTable
);
RAISE NOTICE 'sql: %', createViewSQL;
EXECUTE createViewSQL;
createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize_tg' ||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
' FOR EACH ROW EXECUTE PROCEDURE tx_historicize_tf()';
RAISE NOTICE 'sql: %', createTriggerSQL;
EXECUTE createTriggerSQL;
END; $$;
-- ------
CREATE OR REPLACE PROCEDURE tx_create_historicization(baseTable varchar)
LANGUAGE plpgsql AS $$
DECLARE
createHistTableSql varchar;
createTriggerSQL varchar;
viewName varchar;
versionsTable varchar;
createViewSQL varchar;
baseCols varchar;
BEGIN
-- create the history table
createHistTableSql = '' ||
'CREATE TABLE ' || baseTable || '_ex (' ||
' version_id serial PRIMARY KEY,' ||
' context_id bigint NOT NULL REFERENCES tx_context(contextid),' ||
' trigger_op tx_operation NOT NULL,' ||
' alive boolean not null,' ||
-- followed by all columns of the original table
' LIKE ' || baseTable ||
' EXCLUDING CONSTRAINTS' ||
' EXCLUDING STATISTICS' ||
')';
RAISE NOTICE 'sql: %', createHistTableSql;
EXECUTE createHistTableSql;
-- create the historical view
viewName = quote_ident(format('%s_hv', baseTable));
versionsTable = quote_ident(format('%s_ex', baseTable));
baseCols = (SELECT string_agg(quote_ident(column_name), ', ')
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = baseTable);
createViewSQL = format(
'CREATE OR REPLACE VIEW %1$s AS' ||
'(' ||
' SELECT %2$s' ||
' FROM %3$s' ||
' WHERE alive = TRUE' ||
' AND version_id IN' ||
' (' ||
' SELECT max(vt.version_id) AS history_id' ||
' FROM %3$s AS vt' ||
' JOIN tx_context as txc ON vt.context_id = txc.contextid' ||
' WHERE txc.txtimestamp <= current_setting(''hsadminng.historical_timestamp'')::timestamp' ||
' GROUP BY context_id' ||
' )' ||
')',
viewName, baseCols, versionsTable
);
RAISE NOTICE 'sql: %', createViewSQL;
EXECUTE createViewSQL;
createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize_tg' ||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
' FOR EACH ROW EXECUTE PROCEDURE tx_historicize_tf()';
RAISE NOTICE 'sql: %', createTriggerSQL;
EXECUTE createTriggerSQL;
END; $$;
call tx_create_historicization('hs_hosting_asset');
ROLLBACK;
BEGIN TRANSACTION;
call defineContext('historization testing', null, 'superuser-alex@hostsharing.net',
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
update hs_hosting_asset set caption='lug00 a' where identifier='lug00' and type='MANAGED_WEBSPACE';
COMMIT;
SET hsadminng.historical_timestamp TO '2022-07-12 11:38:27.723315';
SELECT * FROM hs_hosting_asset_ex p WHERE identifier = 'lug00';

View File

@ -1,80 +1,80 @@
rollback;
drop table if exists hs_hosting_asset_versions;
drop table if exists hs_hosting_asset_ex;
drop view if exists hs_hosting_asset_hv;
drop procedure if exists tx_create_historicization;
drop view if exists tx_create_historical_view;
drop function if exists tx_historicize_tf();
drop type if exists tx_operation;
-- ======================================================== -- ========================================================
-- Historization -- Historization
-- -------------------------------------------------------- -- --------------------------------------------------------
CREATE TABLE "tx_history" ( create type "tx_operation" as enum ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE');
"tx_id" BIGINT NOT NULL UNIQUE,
"tx_timestamp" TIMESTAMP NOT NULL,
"user" VARCHAR(64) NOT NULL, -- references postgres user
"task" VARCHAR NOT NULL
);
CREATE TYPE "operation" AS ENUM ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'); create or replace function tx_historicize_tf()
returns trigger
-- see https://www.postgresql.org/docs/current/plpgsql-trigger.html language plpgsql
strict as $$
CREATE OR REPLACE FUNCTION historicize() declare
RETURNS trigger currentUser varchar(63);
LANGUAGE plpgsql STRICT AS $$ currentTask varchar(127);
DECLARE "row" record;
currentUser VARCHAR(63); "alive" boolean;
currentTask VARCHAR(127);
"row" RECORD;
"alive" BOOLEAN;
"sql" varchar; "sql" varchar;
BEGIN begin
-- determine user_id -- determine user_id
BEGIN begin
currentUser := current_setting('hsadminng.currentUser'); currentUser := current_setting('hsadminng.currentUser');
EXCEPTION WHEN OTHERS THEN exception
currentUser := NULL; when others then
END; currentUser := null;
IF (currentUser IS NULL OR currentUser = '') THEN end;
RAISE EXCEPTION 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"'; if (currentUser is null or currentUser = '') then
END IF; raise exception 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
RAISE NOTICE 'currentUser: %', currentUser; end if;
raise notice 'currentUser: %', currentUser;
-- determine task -- determine task
currentTask = current_setting('hsadminng.currentTask'); currentTask = current_setting('hsadminng.currentTask');
assert currentTask IS NOT NULL AND length(currentTask) >= 12, assert currentTask is not null and length(currentTask) >= 12,
format('hsadminng.currentTask (%s) must be defined and min 12 characters long, please use "SET LOCAL ...;"', currentTask); format('hsadminng.currentTask (%s) must be defined and min 12 characters long, please use "SET LOCAL ...;"',
currentTask);
assert length(currentTask) <= 127, assert length(currentTask) <= 127,
format('hsadminng.currentTask (%s) must not be longer than 127 characters"', currentTask); format('hsadminng.currentTask (%s) must not be longer than 127 characters"', currentTask);
IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE') THEN if (TG_OP = 'INSERT') or (TG_OP = 'UPDATE') then
"row" := NEW; "row" := NEW;
"alive" := TRUE; "alive" := true;
ELSE -- DELETE or TRUNCATE else -- DELETE or TRUNCATE
"row" := OLD; "row" := OLD;
"alive" := FALSE; "alive" := false;
END IF; end if;
sql := format('INSERT INTO tx_history VALUES (txid_current(), now(), %1L, %2L) ON CONFLICT DO NOTHING', currentUser, currentTask); sql := format('INSERT INTO %3$I_ex VALUES (DEFAULT, txid_current(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
RAISE NOTICE 'sql: %', sql; raise notice 'sql: %', sql;
EXECUTE sql; execute sql using "row";
sql := format('INSERT INTO %3$I_versions VALUES (DEFAULT, txid_current(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
RAISE NOTICE 'sql: %', sql;
EXECUTE sql USING "row";
RETURN "row"; return "row";
END; $$; end; $$;
CREATE OR REPLACE PROCEDURE create_historical_view(baseTable varchar) create or replace procedure tx_create_historical_view(baseTable varchar)
LANGUAGE plpgsql AS $$ language plpgsql as $$
DECLARE declare
createTriggerSQL varchar; createTriggerSQL varchar;
viewName varchar; viewName varchar;
versionsTable varchar; exVersionsTable varchar;
createViewSQL varchar; createViewSQL varchar;
baseCols varchar; baseCols varchar;
BEGIN begin
viewName = quote_ident(format('%s_hv', baseTable)); viewName = quote_ident(format('%s_hv', baseTable));
versionsTable = quote_ident(format('%s_versions', baseTable)); exVersionsTable = quote_ident(format('%s_ex', baseTable));
baseCols = (SELECT string_agg(quote_ident(column_name), ', ') baseCols = (select string_agg(quote_ident(column_name), ', ')
FROM information_schema.columns from information_schema.columns
WHERE table_schema = 'public' AND table_name = baseTable); where table_schema = 'public'
and table_name = baseTable);
createViewSQL = format( createViewSQL = format(
'CREATE OR REPLACE VIEW %1$s AS' || 'CREATE OR REPLACE VIEW %1$s AS' ||
@ -84,58 +84,59 @@ BEGIN
' WHERE alive = TRUE' || ' WHERE alive = TRUE' ||
' AND version_id IN' || ' AND version_id IN' ||
' (' || ' (' ||
' SELECT max(vt.version_id) AS history_id' || ' SELECT max(ex.version_id) AS history_id' ||
' FROM %3$s AS vt' || ' FROM %3$s AS ex' ||
' JOIN tx_history as txh ON vt.tx_id = txh.tx_id' || ' JOIN tx_context as txc ON ex.txid = txc.txid' ||
' WHERE txh.tx_timestamp <= current_setting(''hsadminng.timestamp'')::timestamp' || ' WHERE txc.txtimestamp <= current_setting(''hsadminng.tx_history_timestamp'')::timestamp' ||
' GROUP BY id' || ' OR txc.txid = current_setting(''hsadminng.tx_history_txid'')' ||
' GROUP BY uuid' ||
' )' || ' )' ||
')', ')',
viewName, baseCols, versionsTable viewName, baseCols, exVersionsTable
); );
RAISE NOTICE 'sql: %', createViewSQL; raise notice 'sql: %', createViewSQL;
EXECUTE createViewSQL; execute createViewSQL;
createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize' || createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_tx_historicize_tf' ||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable || ' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
' FOR EACH ROW EXECUTE PROCEDURE historicize()'; ' FOR EACH ROW EXECUTE PROCEDURE tx_historicize_tf()';
RAISE NOTICE 'sql: %', createTriggerSQL; raise notice 'sql: %', createTriggerSQL;
EXECUTE createTriggerSQL; execute createTriggerSQL;
END; $$; end; $$;
CREATE OR REPLACE PROCEDURE create_historicization(baseTable varchar) create or replace procedure tx_create_historicization(baseTable varchar)
LANGUAGE plpgsql AS $$ language plpgsql as $$
DECLARE declare
createHistTableSql varchar; createHistTableSql varchar;
createTriggerSQL varchar; createTriggerSQL varchar;
viewName varchar; viewName varchar;
versionsTable varchar; exVersionsTable varchar;
createViewSQL varchar; createViewSQL varchar;
baseCols varchar; baseCols varchar;
BEGIN begin
-- create the history table -- create the history table
createHistTableSql = '' || createHistTableSql = '' ||
'CREATE TABLE ' || baseTable || '_versions (' || 'CREATE TABLE ' || baseTable || '_ex (' ||
' version_id serial PRIMARY KEY,' || ' version_id serial PRIMARY KEY,' ||
' tx_id bigint NOT NULL REFERENCES tx_history(tx_id),' || ' txid bigint NOT NULL REFERENCES tx_context(txid),' ||
' trigger_op operation NOT NULL,' || ' trigger_op tx_operation NOT NULL,' ||
' alive boolean not null,' || ' alive boolean not null,' ||
' LIKE ' || baseTable || ' LIKE ' || baseTable ||
' EXCLUDING CONSTRAINTS' || ' EXCLUDING CONSTRAINTS' ||
' EXCLUDING STATISTICS' || ' EXCLUDING STATISTICS' ||
')'; ')';
RAISE NOTICE 'sql: %', createHistTableSql; raise notice 'sql: %', createHistTableSql;
EXECUTE createHistTableSql; execute createHistTableSql;
-- create the historical view -- create the historical view
viewName = quote_ident(format('%s_hv', baseTable)); viewName = quote_ident(format('%s_hv', baseTable));
versionsTable = quote_ident(format('%s_versions', baseTable)); exVersionsTable = quote_ident(format('%s_ex', baseTable));
baseCols = (SELECT string_agg(quote_ident(column_name), ', ') baseCols = (select string_agg(quote_ident(column_name), ', ')
FROM information_schema.columns from information_schema.columns
WHERE table_schema = 'public' AND table_name = baseTable); where table_schema = 'public'
and table_name = baseTable);
createViewSQL = format( createViewSQL = format(
'CREATE OR REPLACE VIEW %1$s AS' || 'CREATE OR REPLACE VIEW %1$s AS' ||
@ -145,22 +146,52 @@ BEGIN
' WHERE alive = TRUE' || ' WHERE alive = TRUE' ||
' AND version_id IN' || ' AND version_id IN' ||
' (' || ' (' ||
' SELECT max(vt.version_id) AS history_id' || ' SELECT max(ex.version_id) AS history_id' ||
' FROM %3$s AS vt' || ' FROM %3$s AS ex' ||
' JOIN tx_history as txh ON vt.tx_id = txh.tx_id' || ' JOIN tx_context as txc ON ex.txid = txc.txid' ||
' WHERE txh.tx_timestamp <= current_setting(''hsadminng.timestamp'')::timestamp' || ' WHERE txc.txtimestamp <= current_setting(''hsadminng.tx_history_timestamp'')::timestamp' ||
' GROUP BY id' || ' GROUP BY uuid' ||
' )' || ' )' ||
')', ')',
viewName, baseCols, versionsTable viewName, baseCols, exVersionsTable
); );
RAISE NOTICE 'sql: %', createViewSQL; raise notice 'sql: %', createViewSQL;
EXECUTE createViewSQL; execute createViewSQL;
createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize' || createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_tx_historicize_tf' ||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable || ' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
' FOR EACH ROW EXECUTE PROCEDURE historicize()'; ' FOR EACH ROW EXECUTE PROCEDURE tx_historicize_tf()';
RAISE NOTICE 'sql: %', createTriggerSQL; raise notice 'sql: %', createTriggerSQL;
EXECUTE createTriggerSQL; execute createTriggerSQL;
END; $$; end; $$;
--- ==================================================
call tx_create_historicization('hs_hosting_asset');
-- and expanded:
-- ===========================================================================================
rollback;
begin transaction;
call defineContext('historization testing', null, 'superuser-alex@hostsharing.net',
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); -- prod+test
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN'); -- prod
-- 'hs_booking_project#D-1000300-mimdefaultproject:ADMIN'); -- test
-- update hs_hosting_asset set caption='lug00 b' where identifier = 'lug00' and type = 'MANAGED_WEBSPACE'; -- prod
update hs_hosting_asset set caption='mim00 A ' || now()::text where identifier = 'mim00' and type = 'MANAGED_WEBSPACE'; -- test
update hs_hosting_asset set caption='mim00 B ' || now()::text where identifier = 'mim00' and type = 'MANAGED_WEBSPACE'; -- test
commit;
-- single version at point in time
-- set hsadminng.tx_history_timestamp to '2024-08-27 07:44:03'; -- UTC
set hsadminng.tx_history_timestamp to '2024-08-27 07:44:03'; -- UTC
select uuid, version, identifier, caption from hs_hosting_asset_hv p where identifier in ('lug00', 'mim00');
-- all versions
select txc.txtimestamp, txc.currentUser, txc.currentTask, haex.*
from hs_hosting_asset_ex haex
join tx_context txc on haex.txid=txc.txid
where haex.identifier in ('lug00', 'mim00');

View File

@ -23,8 +23,10 @@ do $$
*/ */
create table tx_context create table tx_context
( (
contextId bigint primary key not null, -- FIXME: what whas the purpose of such a hash(task+txid)?
txId bigint not null, -- contextId bigint primary key not null,
-- txId bigint not null,
txId bigint primary key not null,
txTimestamp timestamp not null, txTimestamp timestamp not null,
currentUser varchar(63) not null, -- not the uuid, because users can be deleted currentUser varchar(63) not null, -- not the uuid, because users can be deleted
assumedRoles varchar(1023) not null, -- not the uuids, because roles can be deleted assumedRoles varchar(1023) not null, -- not the uuids, because roles can be deleted
@ -43,7 +45,8 @@ create index on tx_context using brin (txTimestamp);
*/ */
create table tx_journal create table tx_journal
( (
contextId bigint not null references tx_context (contextId), -- contextId bigint not null references tx_context (contextId), -- FIXME: this ...
txId bigint not null references tx_context (txId), -- FIXME: ... or that?
targetTable text not null, targetTable text not null,
targetUuid uuid not null, -- Assumes that all audited tables have a uuid column. targetUuid uuid not null, -- Assumes that all audited tables have a uuid column.
targetOp operation not null, targetOp operation not null,
@ -62,7 +65,7 @@ create index on tx_journal (targetTable, targetUuid);
create view tx_journal_v as create view tx_journal_v as
select txc.*, txj.targettable, txj.targetop, txj.targetuuid, txj.targetdelta select txc.*, txj.targettable, txj.targetop, txj.targetuuid, txj.targetdelta
from tx_journal txj from tx_journal txj
left join tx_context txc using (contextid) left join tx_context txc using (txId) -- FIXME: or contextId?
order by txc.txtimestamp; order by txc.txtimestamp;
--// --//
@ -77,31 +80,37 @@ create or replace function tx_journal_trigger()
language plpgsql as $$ language plpgsql as $$
declare declare
curTask text; curTask text;
curContextId bigint; -- curContextId bigint; FIXME: needed?
curTxId bigint;
begin begin
curTask := currentTask(); curTask := currentTask();
curContextId := txid_current()+bigIntHash(curTask); -- curContextId := txid_current()+bigIntHash(curTask); FIXME: needed?
curTxId := txid_current();
insert insert
into tx_context (contextId, txId, txTimestamp, currentUser, assumedRoles, currentTask, currentRequest) -- FIXME
values (curContextId, txid_current(), now(), -- into tx_context (contextId, txId, txTimestamp, currentUser, assumedRoles, currentTask, currentRequest)
-- values (curContextId, txid_current(), now(),
-- currentUser(), assumedRoles(), curTask, currentRequest())
into tx_context (txId, txTimestamp, currentUser, assumedRoles, currentTask, currentRequest)
values ( curTxId, now(),
currentUser(), assumedRoles(), curTask, currentRequest()) currentUser(), assumedRoles(), curTask, currentRequest())
on conflict do nothing; on conflict do nothing;
case tg_op case tg_op
when 'INSERT' then insert when 'INSERT' then insert
into tx_journal into tx_journal
values (curContextId, values (curTxId, -- FIXME: curContextId?
tg_table_name, new.uuid, tg_op::operation, tg_table_name, new.uuid, tg_op::operation,
to_jsonb(new)); to_jsonb(new));
when 'UPDATE' then insert when 'UPDATE' then insert
into tx_journal into tx_journal
values (curContextId, values (curTxId, -- FIXME: curContextId?
tg_table_name, old.uuid, tg_op::operation, tg_table_name, old.uuid, tg_op::operation,
jsonb_changes_delta(to_jsonb(old), to_jsonb(new))); jsonb_changes_delta(to_jsonb(old), to_jsonb(new)));
when 'DELETE' then insert when 'DELETE' then insert
into tx_journal into tx_journal
values (curContextId, values (curTxId, -- FIXME: curContextId?
tg_table_name, old.uuid, 'DELETE'::operation, tg_table_name, old.uuid, 'DELETE'::operation,
null::jsonb); null::jsonb);
else raise exception 'Trigger op % not supported for %.', tg_op, tg_table_name; else raise exception 'Trigger op % not supported for %.', tg_op, tg_table_name;

View File

@ -610,7 +610,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
deleteTestDataFromHsOfficeTables(); deleteTestDataFromHsOfficeTables();
resetHsOfficeSequences(); resetHsOfficeSequences();
deleteFromTestTables(); deleteFromTestTables();
deleteFromRbacTables(); deleteFromCommonTables();
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context(rbacSuperuser); context(rbacSuperuser);

View File

@ -249,6 +249,7 @@ public class CsvDataImport extends ContextBasedTest {
context(rbacSuperuser); context(rbacSuperuser);
// TODO.perf: could we instead skip creating test-data based on an env var? // TODO.perf: could we instead skip creating test-data based on an env var?
em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate(); em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate();
// FIXME em.createNativeQuery("delete from hs_hosting_asset_ex where true").executeUpdate();
em.createNativeQuery("delete from hs_booking_item where true").executeUpdate(); em.createNativeQuery("delete from hs_booking_item where true").executeUpdate();
em.createNativeQuery("delete from hs_booking_project where true").executeUpdate(); em.createNativeQuery("delete from hs_booking_project where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate();
@ -292,7 +293,7 @@ public class CsvDataImport extends ContextBasedTest {
}).assertSuccessful(); }).assertSuccessful();
} }
protected void deleteFromRbacTables() { protected void deleteFromCommonTables() {
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context(rbacSuperuser); context(rbacSuperuser);
em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate(); em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate();