introduce separate database-schemas base+rbac (#103)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Co-authored-by: Michael Hönnig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/103 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
161
src/main/resources/db/changelog/0-base/030-historization.sql
Normal file
161
src/main/resources/db/changelog/0-base/030-historization.sql
Normal file
@@ -0,0 +1,161 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-global-historization-tx-history-txid endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
create or replace function base.tx_history_txid()
|
||||
returns xid8 stable
|
||||
language plpgsql as $$
|
||||
declare
|
||||
historicalTxIdSetting text;
|
||||
historicalTimestampSetting text;
|
||||
historicalTxId xid8;
|
||||
historicalTimestamp timestamp;
|
||||
begin
|
||||
select coalesce(current_setting('hsadminng.tx_history_txid', true), '') into historicalTxIdSetting;
|
||||
select coalesce(current_setting('hsadminng.tx_history_timestamp', true), '') into historicalTimestampSetting;
|
||||
if historicalTxIdSetting > '' and historicalTimestampSetting > '' then
|
||||
raise exception 'either hsadminng.tx_history_txid or hsadminng.tx_history_timestamp must be set, but both are set: (%, %)',
|
||||
historicalTxIdSetting, historicalTimestampSetting;
|
||||
end if;
|
||||
if historicalTxIdSetting = '' and historicalTimestampSetting = '' then
|
||||
raise exception 'either hsadminng.tx_history_txid or hsadminng.tx_history_timestamp must be set, but both are unset or empty: (%, %)',
|
||||
historicalTxIdSetting, historicalTimestampSetting;
|
||||
end if;
|
||||
-- just for debugging / making sure the function is only called once per query
|
||||
-- raise notice 'base.tx_history_txid() called with: (%, %)', historicalTxIdSetting, historicalTimestampSetting;
|
||||
|
||||
if historicalTxIdSetting is null or historicalTxIdSetting = '' then
|
||||
select historicalTimestampSetting::timestamp into historicalTimestamp;
|
||||
select max(txc.txid) from base.tx_context txc where txc.txtimestamp <= historicalTimestamp into historicalTxId;
|
||||
else
|
||||
historicalTxId = historicalTxIdSetting::xid8;
|
||||
end if;
|
||||
return historicalTxId;
|
||||
end; $$;
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-global-historization-tx-historicize-tf endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
-- create type base.tx_operation as enum ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE');
|
||||
|
||||
create or replace function base.tx_historicize_tf()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
strict as $$
|
||||
declare
|
||||
currentSubject varchar(63);
|
||||
currentTask varchar(127);
|
||||
"row" record;
|
||||
"alive" boolean;
|
||||
"sql" varchar;
|
||||
begin
|
||||
-- determine user_id
|
||||
begin
|
||||
currentSubject := current_setting('hsadminng.currentSubject');
|
||||
exception
|
||||
when others then
|
||||
currentSubject := null;
|
||||
end;
|
||||
if (currentSubject is null or currentSubject = '') then
|
||||
raise exception 'hsadminng.currentSubject must be defined, please use "SET LOCAL ...;"';
|
||||
end if;
|
||||
raise notice 'currentSubject: %', currentSubject;
|
||||
|
||||
-- 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;
|
||||
|
||||
sql := format('INSERT INTO %3$I_ex VALUES (DEFAULT, pg_current_xact_id(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
|
||||
raise notice 'sql: %', sql;
|
||||
execute sql using "row";
|
||||
|
||||
return "row";
|
||||
end; $$;
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-global-historization-tx-create-historicization endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
create or replace procedure base.tx_create_historicization(baseTable varchar)
|
||||
language plpgsql as $$
|
||||
declare
|
||||
createHistTableSql varchar;
|
||||
createTriggerSQL varchar;
|
||||
viewName varchar;
|
||||
exVersionsTable varchar;
|
||||
createViewSQL varchar;
|
||||
baseCols varchar;
|
||||
begin
|
||||
|
||||
-- create the history table
|
||||
createHistTableSql = '' ||
|
||||
'CREATE TABLE ' || baseTable || '_ex (' ||
|
||||
' version_id serial PRIMARY KEY,' ||
|
||||
' txid xid8 NOT NULL REFERENCES base.tx_context(txid),' ||
|
||||
' trigger_op base.tx_operation NOT NULL,' ||
|
||||
' alive boolean not null,' ||
|
||||
' LIKE ' || baseTable ||
|
||||
' EXCLUDING CONSTRAINTS' ||
|
||||
' EXCLUDING STATISTICS' ||
|
||||
')';
|
||||
raise notice 'sql: %', createHistTableSql;
|
||||
execute createHistTableSql;
|
||||
|
||||
-- create the historical view
|
||||
viewName = quote_ident(format('%s_hv', baseTable));
|
||||
exVersionsTable = 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' ||
|
||||
'(' ||
|
||||
-- make sure the function is only called once, not for every matching row in base.tx_context
|
||||
' WITH txh AS (SELECT base.tx_history_txid() AS txid) ' ||
|
||||
' SELECT %2$s' ||
|
||||
' FROM %3$s' ||
|
||||
' WHERE alive = TRUE' ||
|
||||
' AND version_id IN' ||
|
||||
' (' ||
|
||||
' SELECT max(ex.version_id) AS history_id' ||
|
||||
' FROM %3$s AS ex' ||
|
||||
' JOIN base.tx_context as txc ON ex.txid = txc.txid' ||
|
||||
' WHERE txc.txid <= (SELECT txid FROM txh)' ||
|
||||
' GROUP BY uuid' ||
|
||||
' )' ||
|
||||
')',
|
||||
viewName, baseCols, exVersionsTable
|
||||
);
|
||||
raise notice 'sql: %', createViewSQL;
|
||||
execute createViewSQL;
|
||||
|
||||
-- "-9-" to put the trigger execution after any alphabetically lesser tx-triggers
|
||||
createTriggerSQL = 'CREATE TRIGGER tx_9_historicize_tg' ||
|
||||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
|
||||
' FOR EACH ROW EXECUTE PROCEDURE base.tx_historicize_tf()';
|
||||
raise notice 'sql: %', createTriggerSQL;
|
||||
execute createTriggerSQL;
|
||||
|
||||
end; $$;
|
||||
--//
|
Reference in New Issue
Block a user