historic-view (#92)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/92 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
		
							
								
								
									
										37
									
								
								.run/ImportHostingAssets into local.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.run/ImportHostingAssets into local.run.xml
									
									
									
									
									
										Normal 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=""net.hostsharing.hsadminng.hs.migration.ImportHostingAssets"" /> | ||||||
|  |         </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> | ||||||
| @@ -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=""net.hostsharing.hsadminng.hs.migration.ImportHostingAssets"" /> |  | ||||||
|         </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> | ||||||
| @@ -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'; |  | ||||||
| @@ -1,166 +1,39 @@ | |||||||
|  |  | ||||||
| -- ======================================================== | -- ======================================================== | ||||||
| -- Historization | -- Historization twiddle | ||||||
| -- -------------------------------------------------------- | -- -------------------------------------------------------- | ||||||
|  |  | ||||||
| CREATE TABLE "tx_history" ( | rollback; | ||||||
|                               "tx_id" BIGINT NOT NULL UNIQUE, | begin transaction; | ||||||
|                               "tx_timestamp" TIMESTAMP NOT NULL, | call defineContext('historization testing', null, 'superuser-alex@hostsharing.net', | ||||||
|                               "user" VARCHAR(64) NOT NULL, -- references postgres user | --                    'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); -- prod+test | ||||||
|                               "task" VARCHAR NOT NULL |                    'hs_booking_project#D-1000313-D-1000313defaultproject: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='hsh00 A ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test | ||||||
|  | -- update hs_hosting_asset set caption='hsh00 B ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test | ||||||
|  |  | ||||||
| CREATE TYPE "operation" AS ENUM ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'); | -- insert into hs_hosting_asset | ||||||
|  | --     (uuid, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, identifier, caption, config, alarmcontactuuid) | ||||||
|  | --     values | ||||||
|  | --     (uuid_generate_v4(), null, 'EMAIL_ADDRESS', 'bbda5895-0569-4e20-bb4c-34f3a38f3f63'::uuid, null, | ||||||
|  | --         'new@thi.example.org', 'some new E-Mail-Address', '{}'::jsonb, null); | ||||||
|  |  | ||||||
| -- see https://www.postgresql.org/docs/current/plpgsql-trigger.html | delete from hs_hosting_asset where uuid='5aea68d2-3b55-464f-8362-b05c76c5a681'::uuid; | ||||||
|  | commit; | ||||||
|  |  | ||||||
| CREATE OR REPLACE FUNCTION historicize() | -- single version at point in time | ||||||
|     RETURNS trigger | -- set hsadminng.tx_history_txid to (select max(txid) from tx_context where txtimestamp<='2024-08-27 12:13:13.450821'); | ||||||
|     LANGUAGE plpgsql STRICT AS $$ | set hsadminng.tx_history_txid to ''; | ||||||
| DECLARE | set hsadminng.tx_history_timestamp to '2024-08-29 12:42'; | ||||||
|     currentUser VARCHAR(63); | -- all versions | ||||||
|     currentTask VARCHAR(127); | select tx_history_txid(), txc.txtimestamp, txc.currentUser, txc.currentTask, haex.* | ||||||
|     "row" RECORD; |     from hs_hosting_asset_ex haex | ||||||
|     "alive" BOOLEAN; |              join tx_context txc on haex.txid=txc.txid | ||||||
|     "sql" varchar; |     where haex.identifier = 'test@thi.example.org'; | ||||||
| BEGIN |  | ||||||
|     -- determine user_id |  | ||||||
| BEGIN |  | ||||||
|         currentUser := current_setting('hsadminng.currentUser'); |  | ||||||
| EXCEPTION WHEN OTHERS THEN |  | ||||||
|         currentUser := NULL; |  | ||||||
| END; |  | ||||||
|     IF (currentUser IS NULL OR currentUser = '') THEN |  | ||||||
|         RAISE EXCEPTION 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"'; |  | ||||||
| END IF; |  | ||||||
|     RAISE NOTICE 'currentUser: %', currentUser; |  | ||||||
|  |  | ||||||
|     -- determine task | select uuid, version, type, identifier, caption from hs_hosting_asset_hv p where identifier = 'test@thi.example.org'; | ||||||
|     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 | select pg_current_xact_id(); | ||||||
|         "row" := NEW; |  | ||||||
|         "alive" := TRUE; |  | ||||||
|     ELSE -- DELETE or TRUNCATE |  | ||||||
|             "row" := OLD; |  | ||||||
|             "alive" := FALSE; |  | ||||||
|     END IF; |  | ||||||
|  |  | ||||||
|     sql := format('INSERT INTO tx_history VALUES (txid_current(), now(), %1L, %2L) ON CONFLICT DO NOTHING', currentUser, currentTask); |  | ||||||
|     RAISE NOTICE 'sql: %', sql; |  | ||||||
|     EXECUTE sql; |  | ||||||
|     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"; |  | ||||||
| END; $$; |  | ||||||
|  |  | ||||||
| CREATE OR REPLACE PROCEDURE 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_versions', 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.timestamp'')::timestamp' || |  | ||||||
|                 '              GROUP BY id' || |  | ||||||
|                 '         )' || |  | ||||||
|                 ')', |  | ||||||
|                 viewName, baseCols, versionsTable |  | ||||||
|         ); |  | ||||||
|     RAISE NOTICE 'sql: %', createViewSQL; |  | ||||||
| EXECUTE createViewSQL; |  | ||||||
|  |  | ||||||
| createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize' || |  | ||||||
|                     ' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable || |  | ||||||
|                     '   FOR EACH ROW EXECUTE PROCEDURE historicize()'; |  | ||||||
|     RAISE NOTICE 'sql: %', createTriggerSQL; |  | ||||||
| EXECUTE createTriggerSQL; |  | ||||||
|  |  | ||||||
| END; $$; |  | ||||||
|  |  | ||||||
| CREATE OR REPLACE PROCEDURE 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 || '_versions (' || |  | ||||||
|         '   version_id serial PRIMARY KEY,' || |  | ||||||
|         '   tx_id bigint NOT NULL REFERENCES tx_history(tx_id),' || |  | ||||||
|         '   trigger_op 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)); |  | ||||||
|         versionsTable = quote_ident(format('%s_versions', 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.timestamp'')::timestamp' || |  | ||||||
|             '              GROUP BY id' || |  | ||||||
|             '         )' || |  | ||||||
|             ')', |  | ||||||
|             viewName, baseCols, versionsTable |  | ||||||
|             ); |  | ||||||
|         RAISE NOTICE 'sql: %', createViewSQL; |  | ||||||
|     EXECUTE createViewSQL; |  | ||||||
|  |  | ||||||
|     createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize' || |  | ||||||
|                            ' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable || |  | ||||||
|                            '   FOR EACH ROW EXECUTE PROCEDURE historicize()'; |  | ||||||
|         RAISE NOTICE 'sql: %', createTriggerSQL; |  | ||||||
|     EXECUTE createTriggerSQL; |  | ||||||
|  |  | ||||||
| END; $$; |  | ||||||
|   | |||||||
| @@ -23,8 +23,7 @@ do $$ | |||||||
|  */ |  */ | ||||||
| create table tx_context | create table tx_context | ||||||
| ( | ( | ||||||
|     contextId       bigint primary key not null, |     txId            xid8            primary key     not null, | ||||||
|     txId            bigint             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 +42,7 @@ create index on tx_context using brin (txTimestamp); | |||||||
|  */ |  */ | ||||||
| create table tx_journal | create table tx_journal | ||||||
| ( | ( | ||||||
|     contextId   bigint    not null references tx_context (contextId), |     txId        xid8      not null references tx_context (txId), | ||||||
|     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 +61,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) | ||||||
|     order by txc.txtimestamp; |     order by txc.txtimestamp; | ||||||
| --// | --// | ||||||
|  |  | ||||||
| @@ -77,31 +76,31 @@ create or replace function tx_journal_trigger() | |||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     curTask text; |     curTask text; | ||||||
|     curContextId bigint; |     curTxId xid8; | ||||||
| begin | begin | ||||||
|     curTask := currentTask(); |     curTask := currentTask(); | ||||||
|     curContextId := txid_current()+bigIntHash(curTask); |     curTxId := pg_current_xact_id(); | ||||||
|  |  | ||||||
|     insert |     insert | ||||||
|         into tx_context (contextId, txId, txTimestamp, currentUser, assumedRoles, currentTask, currentRequest) |         into tx_context (txId, txTimestamp, currentUser, assumedRoles, currentTask, currentRequest) | ||||||
|         values (curContextId, txid_current(), now(), |             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, | ||||||
|                                        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, | ||||||
|                                        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, | ||||||
|                                        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; | ||||||
|   | |||||||
							
								
								
									
										160
									
								
								src/main/resources/db/changelog/0-basis/030-historization.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/main/resources/db/changelog/0-basis/030-historization.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | |||||||
|  | --liquibase formatted sql | ||||||
|  |  | ||||||
|  | -- ============================================================================ | ||||||
|  | --changeset hs-global-historization-tx-history-txid:1 endDelimiter:--// | ||||||
|  | -- ---------------------------------------------------------------------------- | ||||||
|  | create or replace function 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 'tx_history_txid() called with: (%, %)', historicalTxIdSetting, historicalTimestampSetting; | ||||||
|  |  | ||||||
|  |     if historicalTxIdSetting is null or historicalTxIdSetting = '' then | ||||||
|  |         select historicalTimestampSetting::timestamp into historicalTimestamp; | ||||||
|  |         select max(txc.txid) from tx_context txc where txc.txtimestamp <= historicalTimestamp into historicalTxId; | ||||||
|  |     else | ||||||
|  |         historicalTxId = historicalTxIdSetting::xid8; | ||||||
|  |     end if; | ||||||
|  |     return historicalTxId; | ||||||
|  | end; $$; | ||||||
|  | --// | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- ============================================================================ | ||||||
|  | --changeset hs-global-historization-tx-historicize-tf:1 endDelimiter:--// | ||||||
|  | -- ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | create type "tx_operation" as enum ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'); | ||||||
|  |  | ||||||
|  | create or replace function tx_historicize_tf() | ||||||
|  |     returns trigger | ||||||
|  |     language plpgsql | ||||||
|  |     strict as $$ | ||||||
|  | declare | ||||||
|  |     currentUser varchar(63); | ||||||
|  |     currentTask varchar(127); | ||||||
|  |     "row"       record; | ||||||
|  |     "alive"     boolean; | ||||||
|  |     "sql"       varchar; | ||||||
|  | begin | ||||||
|  |     -- determine user_id | ||||||
|  |     begin | ||||||
|  |         currentUser := current_setting('hsadminng.currentUser'); | ||||||
|  |     exception | ||||||
|  |         when others then | ||||||
|  |             currentUser := null; | ||||||
|  |     end; | ||||||
|  |     if (currentUser is null or currentUser = '') then | ||||||
|  |         raise exception 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"'; | ||||||
|  |     end if; | ||||||
|  |     raise notice 'currentUser: %', currentUser; | ||||||
|  |  | ||||||
|  |     -- 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 hs-global-historization-tx-create-historicization:1 endDelimiter:--// | ||||||
|  | -- ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  | create or replace procedure 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 tx_context(txid),' || | ||||||
|  |                          '   trigger_op 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 tx_context | ||||||
|  |             '  WITH txh AS (SELECT 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 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; | ||||||
|  |  | ||||||
|  |     createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_tx_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; $$; | ||||||
|  | --// | ||||||
| @@ -25,16 +25,11 @@ create or replace procedure createTestCustomerTestData( | |||||||
| ) | ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask   varchar; |  | ||||||
|     custRowId     uuid; |     custRowId     uuid; | ||||||
|     custAdminName varchar; |     custAdminName varchar; | ||||||
|     custAdminUuid uuid; |     custAdminUuid uuid; | ||||||
|     newCust       test_customer; |     newCust       test_customer; | ||||||
| begin | begin | ||||||
|     currentTask = 'creating RBAC test customer #' || custReference || '/' || custPrefix; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     custRowId = uuid_generate_v4(); |     custRowId = uuid_generate_v4(); | ||||||
|     custAdminName = 'customer-admin@' || custPrefix || '.example.com'; |     custAdminName = 'customer-admin@' || custPrefix || '.example.com'; | ||||||
|     custAdminUuid = createRbacUser(custAdminName); |     custAdminUuid = createRbacUser(custAdminName); | ||||||
| @@ -77,6 +72,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating RBAC test customer', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createTestCustomerTestData(99901, 'xxx'); |         call createTestCustomerTestData(99901, 'xxx'); | ||||||
|         call createTestCustomerTestData(99902, 'yyy'); |         call createTestCustomerTestData(99902, 'yyy'); | ||||||
|         call createTestCustomerTestData(99903, 'zzz'); |         call createTestCustomerTestData(99903, 'zzz'); | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ declare | |||||||
|     custAdminUser varchar; |     custAdminUser varchar; | ||||||
|     custAdminRole varchar; |     custAdminRole varchar; | ||||||
|     pacName       varchar; |     pacName       varchar; | ||||||
|     currentTask   varchar; |  | ||||||
|     pac           test_package; |     pac           test_package; | ||||||
| begin | begin | ||||||
|     select * from test_customer where test_customer.prefix = customerPrefix into cust; |     select * from test_customer where test_customer.prefix = customerPrefix into cust; | ||||||
| @@ -21,13 +20,9 @@ begin | |||||||
|     for t in 0..(pacCount-1) |     for t in 0..(pacCount-1) | ||||||
|         loop |         loop | ||||||
|             pacName = cust.prefix || to_char(t, 'fm00'); |             pacName = cust.prefix || to_char(t, 'fm00'); | ||||||
|             currentTask = 'creating RBAC test package #' || pacName || ' for customer ' || cust.prefix || ' #' || |  | ||||||
|                           cust.uuid; |  | ||||||
|  |  | ||||||
|             custAdminUser = 'customer-admin@' || cust.prefix || '.example.com'; |             custAdminUser = 'customer-admin@' || cust.prefix || '.example.com'; | ||||||
|             custAdminRole = 'test_customer#' || cust.prefix || ':ADMIN'; |             custAdminRole = 'test_customer#' || cust.prefix || ':ADMIN'; | ||||||
|             call defineContext(currentTask, null, 'superuser-fran@hostsharing.net', custAdminRole); |             call defineContext('creating RBAC test package', null, 'superuser-fran@hostsharing.net', custAdminRole); | ||||||
|             raise notice 'task: % by % as %', currentTask, custAdminUser, custAdminRole; |  | ||||||
|  |  | ||||||
|             insert |             insert | ||||||
|                 into test_package (customerUuid, name, description) |                 into test_package (customerUuid, name, description) | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ create or replace procedure createdomainTestData( packageName varchar, domainCou | |||||||
| declare | declare | ||||||
|     pac         record; |     pac         record; | ||||||
|     pacAdmin    varchar; |     pacAdmin    varchar; | ||||||
|     currentTask varchar; |  | ||||||
| begin | begin | ||||||
|     select p.uuid, p.name, c.prefix as custPrefix |     select p.uuid, p.name, c.prefix as custPrefix | ||||||
|         from test_package p |         from test_package p | ||||||
| @@ -21,10 +20,8 @@ begin | |||||||
|  |  | ||||||
|     for t in 0..(domainCount-1) |     for t in 0..(domainCount-1) | ||||||
|         loop |         loop | ||||||
|             currentTask = 'creating RBAC test domain #' || t || ' for package ' || pac.name || ' #' || pac.uuid; |  | ||||||
|             raise notice 'task: %', currentTask; |  | ||||||
|             pacAdmin = 'pac-admin-' || pac.name || '@' || pac.custPrefix || '.example.com'; |             pacAdmin = 'pac-admin-' || pac.name || '@' || pac.custPrefix || '.example.com'; | ||||||
|             call defineContext(currentTask, null, pacAdmin, null); |             call defineContext('creating RBAC test domain', null, pacAdmin, null); | ||||||
|  |  | ||||||
|             insert |             insert | ||||||
|                 into test_domain (name, packageUuid) |                 into test_domain (name, packageUuid) | ||||||
|   | |||||||
| @@ -11,17 +11,13 @@ | |||||||
| create or replace procedure createHsOfficeContactTestData(contCaption varchar) | create or replace procedure createHsOfficeContactTestData(contCaption varchar) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask     varchar; |  | ||||||
|     postalAddr      varchar; |     postalAddr      varchar; | ||||||
|     emailAddr       varchar; |     emailAddr       varchar; | ||||||
| begin | begin | ||||||
|     currentTask = 'creating contact test-data ' || contCaption; |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     emailAddr = 'contact-admin@' || cleanIdentifier(contCaption) || '.example.com'; |     emailAddr = 'contact-admin@' || cleanIdentifier(contCaption) || '.example.com'; | ||||||
|     call defineContext(currentTask); |     call defineContext('creating contact test-data'); | ||||||
|     perform createRbacUser(emailAddr); |     perform createRbacUser(emailAddr); | ||||||
|     call defineContext(currentTask, null, emailAddr); |     call defineContext('creating contact test-data', null, emailAddr); | ||||||
|  |  | ||||||
|     postalAddr := E'Vorname Nachname\nStraße Hnr\nPLZ Stadt'; |     postalAddr := E'Vorname Nachname\nStraße Hnr\nPLZ Stadt'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,16 +17,13 @@ create or replace procedure createHsOfficePersonTestData( | |||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     fullName    varchar; |     fullName    varchar; | ||||||
|     currentTask varchar; |  | ||||||
|     emailAddr   varchar; |     emailAddr   varchar; | ||||||
| begin | begin | ||||||
|     fullName := concat_ws(', ', newTradeName, newFamilyName, newGivenName); |     fullName := concat_ws(', ', newTradeName, newFamilyName, newGivenName); | ||||||
|     currentTask = 'creating person test-data ' || fullName; |  | ||||||
|     emailAddr = 'person-' || left(cleanIdentifier(fullName), 32) || '@example.com'; |     emailAddr = 'person-' || left(cleanIdentifier(fullName), 32) || '@example.com'; | ||||||
|     call defineContext(currentTask); |     call defineContext('creating person test-data'); | ||||||
|     perform createRbacUser(emailAddr); |     perform createRbacUser(emailAddr); | ||||||
|     call defineContext(currentTask, null, emailAddr); |     call defineContext('creating person test-data', null, emailAddr); | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     raise notice 'creating test person: % by %', fullName, emailAddr; |     raise notice 'creating test person: % by %', fullName, emailAddr; | ||||||
|     insert |     insert | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ create or replace procedure createHsOfficeRelationTestData( | |||||||
|         mark varchar default null) |         mark varchar default null) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask     varchar; |  | ||||||
|     idName          varchar; |     idName          varchar; | ||||||
|     anchorPerson    hs_office_person; |     anchorPerson    hs_office_person; | ||||||
|     holderPerson    hs_office_person; |     holderPerson    hs_office_person; | ||||||
| @@ -24,9 +23,6 @@ declare | |||||||
|  |  | ||||||
| begin | begin | ||||||
|     idName := cleanIdentifier( anchorPersonName || '-' || holderPersonName); |     idName := cleanIdentifier( anchorPersonName || '-' || holderPersonName); | ||||||
|     currentTask := 'creating relation test-data ' || idName; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select p.* |     select p.* | ||||||
|             into anchorPerson |             into anchorPerson | ||||||
| @@ -89,6 +85,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating relation test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact'); |         call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact'); | ||||||
|         call createHsOfficeRelationTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact'); |         call createHsOfficeRelationTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact'); | ||||||
|         call createHsOfficeRelationTestData('First GmbH', 'DEBITOR', 'First GmbH', 'first contact'); |         call createHsOfficeRelationTestData('First GmbH', 'DEBITOR', 'First GmbH', 'first contact'); | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ create or replace procedure createHsOfficePartnerTestData( | |||||||
|         contactCaption      varchar ) |         contactCaption      varchar ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask         varchar; |  | ||||||
|     idName              varchar; |     idName              varchar; | ||||||
|     mandantPerson       hs_office_person; |     mandantPerson       hs_office_person; | ||||||
|     partnerRel         hs_office_relation; |     partnerRel         hs_office_relation; | ||||||
| @@ -23,9 +22,6 @@ declare | |||||||
|     relatedDetailsUuid  uuid; |     relatedDetailsUuid  uuid; | ||||||
| begin | begin | ||||||
|     idName := cleanIdentifier( partnerPersonName|| '-' || contactCaption); |     idName := cleanIdentifier( partnerPersonName|| '-' || contactCaption); | ||||||
|     currentTask := 'creating partner test-data ' || idName; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select p.* from hs_office_person p |     select p.* from hs_office_person p | ||||||
|                where p.tradeName = mandantTradeName |                where p.tradeName = mandantTradeName | ||||||
| @@ -69,13 +65,14 @@ end; $$; | |||||||
| --// | --// | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| -- ============================================================================ | -- ============================================================================ | ||||||
| --changeset hs-office-partner-TEST-DATA-GENERATION:1 –context=dev,tc endDelimiter:--// | --changeset hs-office-partner-TEST-DATA-GENERATION:1 –context=dev,tc endDelimiter:--// | ||||||
| -- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating partner test-data ', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsOfficePartnerTestData('Hostsharing eG', 10001, 'First GmbH', 'first contact'); |         call createHsOfficePartnerTestData('Hostsharing eG', 10001, 'First GmbH', 'first contact'); | ||||||
|         call createHsOfficePartnerTestData('Hostsharing eG', 10002, 'Second e.K.', 'second contact'); |         call createHsOfficePartnerTestData('Hostsharing eG', 10002, 'Second e.K.', 'second contact'); | ||||||
|         call createHsOfficePartnerTestData('Hostsharing eG', 10003, 'Third OHG', 'third contact'); |         call createHsOfficePartnerTestData('Hostsharing eG', 10003, 'Third OHG', 'third contact'); | ||||||
|   | |||||||
| @@ -11,16 +11,11 @@ | |||||||
| create or replace procedure createHsOfficeBankAccountTestData(givenHolder varchar, givenIBAN varchar, givenBIC varchar) | create or replace procedure createHsOfficeBankAccountTestData(givenHolder varchar, givenIBAN varchar, givenBIC varchar) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask   varchar; |  | ||||||
|     emailAddr varchar; |     emailAddr varchar; | ||||||
| begin | begin | ||||||
|     currentTask = 'creating bankaccount test-data ' || givenHolder; |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     emailAddr = 'bankaccount-admin@' || cleanIdentifier(givenHolder) || '.example.com'; |     emailAddr = 'bankaccount-admin@' || cleanIdentifier(givenHolder) || '.example.com'; | ||||||
|     call defineContext(currentTask); |  | ||||||
|     perform createRbacUser(emailAddr); |     perform createRbacUser(emailAddr); | ||||||
|     call defineContext(currentTask, null, emailAddr); |     call defineContext('creating bankaccount test-data', null, emailAddr); | ||||||
|  |  | ||||||
|     raise notice 'creating test bankaccount: %', givenHolder; |     raise notice 'creating test bankaccount: %', givenHolder; | ||||||
|     insert |     insert | ||||||
| @@ -36,6 +31,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating bankaccount test-data'); | ||||||
|  |  | ||||||
|         -- IBANs+BICs taken from https://ibanvalidieren.de/beispiele.html |         -- IBANs+BICs taken from https://ibanvalidieren.de/beispiele.html | ||||||
|         call createHsOfficeBankAccountTestData('First GmbH', 'DE02120300000000202051', 'BYLADEM1001'); |         call createHsOfficeBankAccountTestData('First GmbH', 'DE02120300000000202051', 'BYLADEM1001'); | ||||||
|         call createHsOfficeBankAccountTestData('Peter Smith', 'DE02500105170137075030', 'INGDDEFF'); |         call createHsOfficeBankAccountTestData('Peter Smith', 'DE02500105170137075030', 'INGDDEFF'); | ||||||
|   | |||||||
| @@ -16,15 +16,11 @@ create or replace procedure createHsOfficeDebitorTestData( | |||||||
|     ) |     ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask             varchar; |  | ||||||
|     idName                  varchar; |     idName                  varchar; | ||||||
|     relatedDebitorRelUuid   uuid; |     relatedDebitorRelUuid   uuid; | ||||||
|     relatedBankAccountUuid  uuid; |     relatedBankAccountUuid  uuid; | ||||||
| begin | begin | ||||||
|     idName := cleanIdentifier( forPartnerPersonName|| '-' || forBillingContactCaption); |     idName := cleanIdentifier( forPartnerPersonName|| '-' || forBillingContactCaption); | ||||||
|     currentTask := 'creating debitor test-data ' || idName; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select debitorRel.uuid |     select debitorRel.uuid | ||||||
|             into relatedDebitorRelUuid |             into relatedDebitorRelUuid | ||||||
| @@ -54,6 +50,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating debitor test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsOfficeDebitorTestData(11, 'First GmbH', 'first contact', 'fir'); |         call createHsOfficeDebitorTestData(11, 'First GmbH', 'first contact', 'fir'); | ||||||
|         call createHsOfficeDebitorTestData(12, 'Second e.K.', 'second contact', 'sec'); |         call createHsOfficeDebitorTestData(12, 'Second e.K.', 'second contact', 'sec'); | ||||||
|         call createHsOfficeDebitorTestData(13, 'Third OHG', 'third contact', 'thi'); |         call createHsOfficeDebitorTestData(13, 'Third OHG', 'third contact', 'thi'); | ||||||
|   | |||||||
| @@ -15,14 +15,9 @@ create or replace procedure createHsOfficeSepaMandateTestData( | |||||||
|         withReference varchar) |         withReference varchar) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask         varchar; |  | ||||||
|     relatedDebitor      hs_office_debitor; |     relatedDebitor      hs_office_debitor; | ||||||
|     relatedBankAccount  hs_office_bankAccount; |     relatedBankAccount  hs_office_bankAccount; | ||||||
| begin | begin | ||||||
|     currentTask := 'creating SEPA-mandate test-data ' || forPartnerNumber::text || forDebitorSuffix::text; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select debitor.* into relatedDebitor |     select debitor.* into relatedDebitor | ||||||
|         from hs_office_debitor debitor |         from hs_office_debitor debitor | ||||||
|         join hs_office_relation debitorRel on debitorRel.uuid = debitor.debitorRelUuid |         join hs_office_relation debitorRel on debitorRel.uuid = debitor.debitorRelUuid | ||||||
| @@ -48,6 +43,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating SEPA-mandate test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsOfficeSepaMandateTestData(10001, '11', 'DE02120300000000202051', 'ref-10001-11'); |         call createHsOfficeSepaMandateTestData(10001, '11', 'DE02120300000000202051', 'ref-10001-11'); | ||||||
|         call createHsOfficeSepaMandateTestData(10002, '12', 'DE02100500000054540402', 'ref-10002-12'); |         call createHsOfficeSepaMandateTestData(10002, '12', 'DE02100500000054540402', 'ref-10002-12'); | ||||||
|         call createHsOfficeSepaMandateTestData(10003, '13', 'DE02300209000106531065', 'ref-10003-13'); |         call createHsOfficeSepaMandateTestData(10003, '13', 'DE02300209000106531065', 'ref-10003-13'); | ||||||
|   | |||||||
| @@ -13,15 +13,8 @@ create or replace procedure createHsOfficeMembershipTestData( | |||||||
|         newMemberNumberSuffix char(2) ) |         newMemberNumberSuffix char(2) ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask             varchar; |  | ||||||
|     relatedPartner          hs_office_partner; |     relatedPartner          hs_office_partner; | ||||||
| begin | begin | ||||||
|     currentTask := 'creating Membership test-data ' || |  | ||||||
|                     'P-' || forPartnerNumber::text || |  | ||||||
|                     'M-...' || newMemberNumberSuffix; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select partner.* from hs_office_partner partner |     select partner.* from hs_office_partner partner | ||||||
|                      where partner.partnerNumber = forPartnerNumber into relatedPartner; |                      where partner.partnerNumber = forPartnerNumber into relatedPartner; | ||||||
|  |  | ||||||
| @@ -40,6 +33,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating Membership test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsOfficeMembershipTestData(10001, '01'); |         call createHsOfficeMembershipTestData(10001, '01'); | ||||||
|         call createHsOfficeMembershipTestData(10002, '02'); |         call createHsOfficeMembershipTestData(10002, '02'); | ||||||
|         call createHsOfficeMembershipTestData(10003, '03'); |         call createHsOfficeMembershipTestData(10003, '03'); | ||||||
|   | |||||||
| @@ -14,15 +14,9 @@ create or replace procedure createHsOfficeCoopSharesTransactionTestData( | |||||||
| ) | ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask             varchar; |  | ||||||
|     membership              hs_office_membership; |     membership              hs_office_membership; | ||||||
|     subscriptionEntryUuid   uuid; |     subscriptionEntryUuid   uuid; | ||||||
| begin | begin | ||||||
|     currentTask = 'creating coopSharesTransaction test-data ' || givenPartnerNumber::text || givenMemberNumberSuffix; |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|     SET CONSTRAINTS ALL DEFERRED; |  | ||||||
|  |  | ||||||
|     call defineContext(currentTask); |  | ||||||
|     select m.uuid |     select m.uuid | ||||||
|         from hs_office_membership m |         from hs_office_membership m | ||||||
|         join hs_office_partner p on p.uuid = m.partneruuid |         join hs_office_partner p on p.uuid = m.partneruuid | ||||||
| @@ -49,6 +43,9 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating coopSharesTransaction test-data'); | ||||||
|  |         SET CONSTRAINTS ALL DEFERRED; | ||||||
|  |  | ||||||
|         call createHsOfficeCoopSharesTransactionTestData(10001, '01'); |         call createHsOfficeCoopSharesTransactionTestData(10001, '01'); | ||||||
|         call createHsOfficeCoopSharesTransactionTestData(10002, '02'); |         call createHsOfficeCoopSharesTransactionTestData(10002, '02'); | ||||||
|         call createHsOfficeCoopSharesTransactionTestData(10003, '03'); |         call createHsOfficeCoopSharesTransactionTestData(10003, '03'); | ||||||
|   | |||||||
| @@ -14,15 +14,9 @@ create or replace procedure createHsOfficeCoopAssetsTransactionTestData( | |||||||
|     ) |     ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask             varchar; |  | ||||||
|     membership              hs_office_membership; |     membership              hs_office_membership; | ||||||
|     lossEntryUuid           uuid; |     lossEntryUuid           uuid; | ||||||
| begin | begin | ||||||
|     currentTask = 'creating coopAssetsTransaction test-data ' || givenPartnerNumber || givenMemberNumberSuffix; |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|     SET CONSTRAINTS ALL DEFERRED; |  | ||||||
|  |  | ||||||
|     call defineContext(currentTask); |  | ||||||
|     select m.uuid |     select m.uuid | ||||||
|         from hs_office_membership m |         from hs_office_membership m | ||||||
|                  join hs_office_partner p on p.uuid = m.partneruuid |                  join hs_office_partner p on p.uuid = m.partneruuid | ||||||
| @@ -49,6 +43,9 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating coopAssetsTransaction test-data'); | ||||||
|  |         SET CONSTRAINTS ALL DEFERRED; | ||||||
|  |  | ||||||
|         call createHsOfficeCoopAssetsTransactionTestData(10001, '01'); |         call createHsOfficeCoopAssetsTransactionTestData(10001, '01'); | ||||||
|         call createHsOfficeCoopAssetsTransactionTestData(10002, '02'); |         call createHsOfficeCoopAssetsTransactionTestData(10002, '02'); | ||||||
|         call createHsOfficeCoopAssetsTransactionTestData(10003, '03'); |         call createHsOfficeCoopAssetsTransactionTestData(10003, '03'); | ||||||
|   | |||||||
| @@ -20,3 +20,10 @@ create table if not exists hs_booking_project | |||||||
|  |  | ||||||
| call create_journal('hs_booking_project'); | call create_journal('hs_booking_project'); | ||||||
| --// | --// | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- ============================================================================ | ||||||
|  | --changeset hs-booking-project-MAIN-TABLE-HISTORIZATION:1 endDelimiter:--// | ||||||
|  | -- ---------------------------------------------------------------------------- | ||||||
|  | call tx_create_historicization('hs_booking_project'); | ||||||
|  | --// | ||||||
|   | |||||||
| @@ -14,12 +14,8 @@ create or replace procedure createHsBookingProjectTransactionTestData( | |||||||
|     ) |     ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask         varchar; |  | ||||||
|     relatedDebitor      hs_office_debitor; |     relatedDebitor      hs_office_debitor; | ||||||
| begin | begin | ||||||
|     currentTask := 'creating booking-project test-data ' || givenPartnerNumber::text || givenDebitorSuffix; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select debitor.* into relatedDebitor |     select debitor.* into relatedDebitor | ||||||
|                      from hs_office_debitor debitor |                      from hs_office_debitor debitor | ||||||
| @@ -43,6 +39,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating booking-project test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsBookingProjectTransactionTestData(10001, '11'); |         call createHsBookingProjectTransactionTestData(10001, '11'); | ||||||
|         call createHsBookingProjectTransactionTestData(10002, '12'); |         call createHsBookingProjectTransactionTestData(10002, '12'); | ||||||
|         call createHsBookingProjectTransactionTestData(10003, '13'); |         call createHsBookingProjectTransactionTestData(10003, '13'); | ||||||
|   | |||||||
| @@ -36,3 +36,11 @@ create table if not exists hs_booking_item | |||||||
|  |  | ||||||
| call create_journal('hs_booking_item'); | call create_journal('hs_booking_item'); | ||||||
| --// | --// | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- ============================================================================ | ||||||
|  | --changeset hs-booking-item-MAIN-TABLE-HISTORIZATION:1 endDelimiter:--// | ||||||
|  | -- ---------------------------------------------------------------------------- | ||||||
|  | call tx_create_historicization('hs_booking_item'); | ||||||
|  | --// | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,15 +14,10 @@ create or replace procedure createHsBookingItemTransactionTestData( | |||||||
|     ) |     ) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask         varchar; |  | ||||||
|     relatedProject      hs_booking_project; |     relatedProject      hs_booking_project; | ||||||
|     privateCloudUuid    uuid; |     privateCloudUuid    uuid; | ||||||
|     managedServerUuid   uuid; |     managedServerUuid   uuid; | ||||||
| begin | begin | ||||||
|     currentTask := 'creating booking-item test-data ' || givenPartnerNumber::text || givenDebitorSuffix; |  | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select project.* into relatedProject |     select project.* into relatedProject | ||||||
|                      from hs_booking_project project |                      from hs_booking_project project | ||||||
|                      where project.caption = 'D-' || givenPartnerNumber || givenDebitorSuffix || ' default project'; |                      where project.caption = 'D-' || givenPartnerNumber || givenDebitorSuffix || ' default project'; | ||||||
| @@ -49,7 +44,11 @@ end; $$; | |||||||
| -- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|  |     declare | ||||||
|  |         currentTask text; | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating booking-item test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsBookingItemTransactionTestData(10001, '11'); |         call createHsBookingItemTransactionTestData(10001, '11'); | ||||||
|         call createHsBookingItemTransactionTestData(10002, '12'); |         call createHsBookingItemTransactionTestData(10002, '12'); | ||||||
|         call createHsBookingItemTransactionTestData(10003, '13'); |         call createHsBookingItemTransactionTestData(10003, '13'); | ||||||
|   | |||||||
| @@ -166,6 +166,14 @@ execute procedure hs_hosting_asset_booking_item_hierarchy_check_tf(); | |||||||
| -- ============================================================================ | -- ============================================================================ | ||||||
| --changeset hs-hosting-asset-MAIN-TABLE-JOURNAL:1 endDelimiter:--// | --changeset hs-hosting-asset-MAIN-TABLE-JOURNAL:1 endDelimiter:--// | ||||||
| -- ---------------------------------------------------------------------------- | -- ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| call create_journal('hs_hosting_asset'); | call create_journal('hs_hosting_asset'); | ||||||
| --// | --// | ||||||
|  |  | ||||||
|  |  | ||||||
|  | -- ============================================================================ | ||||||
|  | --changeset hs-hosting-asset-MAIN-TABLE-HISTORIZATION:1 endDelimiter:--// | ||||||
|  | -- ---------------------------------------------------------------------------- | ||||||
|  | call tx_create_historicization('hs_hosting_asset'); | ||||||
|  | --// | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
| create or replace procedure createHsHostingAssetTestData(givenProjectCaption varchar) | create or replace procedure createHsHostingAssetTestData(givenProjectCaption varchar) | ||||||
|     language plpgsql as $$ |     language plpgsql as $$ | ||||||
| declare | declare | ||||||
|     currentTask                         varchar; |  | ||||||
|     relatedProject                      hs_booking_project; |     relatedProject                      hs_booking_project; | ||||||
|     relatedDebitor                      hs_office_debitor; |     relatedDebitor                      hs_office_debitor; | ||||||
|     privateCloudBI                      hs_booking_item; |     privateCloudBI                      hs_booking_item; | ||||||
| @@ -31,9 +30,7 @@ declare | |||||||
|     pgSqlInstanceUuid                   uuid; |     pgSqlInstanceUuid                   uuid; | ||||||
|     PgSqlUserUuid                       uuid; |     PgSqlUserUuid                       uuid; | ||||||
| begin | begin | ||||||
|     currentTask := 'creating hosting-asset test-data ' || givenProjectCaption; |     call defineContext('creating hosting-asset test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|     call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); |  | ||||||
|     execute format('set local hsadminng.currentTask to %L', currentTask); |  | ||||||
|  |  | ||||||
|     select project.* into relatedProject |     select project.* into relatedProject | ||||||
|                   from hs_booking_project project |                   from hs_booking_project project | ||||||
| @@ -113,6 +110,8 @@ end; $$; | |||||||
|  |  | ||||||
| do language plpgsql $$ | do language plpgsql $$ | ||||||
|     begin |     begin | ||||||
|  |         call defineContext('creating hosting-asset test-data', null, 'superuser-alex@hostsharing.net', 'global#global:ADMIN'); | ||||||
|  |  | ||||||
|         call createHsHostingAssetTestData('D-1000111 default project'); |         call createHsHostingAssetTestData('D-1000111 default project'); | ||||||
|         call createHsHostingAssetTestData('D-1000212 default project'); |         call createHsHostingAssetTestData('D-1000212 default project'); | ||||||
|         call createHsHostingAssetTestData('D-1000313 default project'); |         call createHsHostingAssetTestData('D-1000313 default project'); | ||||||
|   | |||||||
| @@ -21,6 +21,8 @@ databaseChangeLog: | |||||||
|         file: db/changelog/0-basis/010-context.sql |         file: db/changelog/0-basis/010-context.sql | ||||||
|     - include: |     - include: | ||||||
|         file: db/changelog/0-basis/020-audit-log.sql |         file: db/changelog/0-basis/020-audit-log.sql | ||||||
|  |     - include: | ||||||
|  |         file: db/changelog/0-basis/030-historization.sql | ||||||
|     - include: |     - include: | ||||||
|         file: db/changelog/0-basis/090-log-slow-queries-extensions.sql |         file: db/changelog/0-basis/090-log-slow-queries-extensions.sql | ||||||
|     - include: |     - include: | ||||||
|   | |||||||
| @@ -20,7 +20,9 @@ import org.springframework.orm.jpa.JpaSystemException; | |||||||
| import jakarta.persistence.EntityManager; | import jakarta.persistence.EntityManager; | ||||||
| import jakarta.persistence.PersistenceContext; | import jakarta.persistence.PersistenceContext; | ||||||
| import jakarta.servlet.http.HttpServletRequest; | import jakarta.servlet.http.HttpServletRequest; | ||||||
|  | import java.sql.Timestamp; | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
|  | import java.time.ZonedDateTime; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| @@ -62,6 +64,54 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup | |||||||
|     @MockBean |     @MockBean | ||||||
|     HttpServletRequest request; |     HttpServletRequest request; | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void auditJournalLogIsAvailable() { | ||||||
|  |         // given | ||||||
|  |         final var query = em.createNativeQuery(""" | ||||||
|  |                 select currentTask, targetTable, targetOp, targetdelta->>'caption' | ||||||
|  |                     from tx_journal_v | ||||||
|  |                     where targettable = 'hs_booking_item'; | ||||||
|  |                 """); | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, prod CloudServer]", | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, separate ManagedServer]", | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, separate ManagedWebspace]", | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, some ManagedServer]", | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, some ManagedWebspace]", | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, some PrivateCloud]", | ||||||
|  |                 "[creating booking-item test-data, hs_booking_item, INSERT, test CloudServer]"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void historizationIsAvailable() { | ||||||
|  |         // given | ||||||
|  |         final String nativeQuerySql = """ | ||||||
|  |                 select count(*) | ||||||
|  |                     from hs_booking_item_hv ha; | ||||||
|  |                 """; | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant())); | ||||||
|  |         final var query = em.createNativeQuery(nativeQuerySql, Integer.class); | ||||||
|  |         @SuppressWarnings("unchecked") final var countBefore = (Integer) query.getSingleResult(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(countBefore).as("hs_booking_item should not contain rows for a timestamp in the past").isEqualTo(0); | ||||||
|  |  | ||||||
|  |         // and when | ||||||
|  |         historicalContext(Timestamp.from(ZonedDateTime.now().plusHours(1).toInstant())); | ||||||
|  |         em.createNativeQuery(nativeQuerySql, Integer.class); | ||||||
|  |         @SuppressWarnings("unchecked") final var countAfter = (Integer) query.getSingleResult(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(countAfter).as("hs_booking_item should contain rows for a timestamp in the future").isGreaterThan(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Nested |     @Nested | ||||||
|     class CreateBookingItem { |     class CreateBookingItem { | ||||||
|  |  | ||||||
| @@ -304,25 +354,6 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void auditJournalLogIsAvailable() { |  | ||||||
|         // given |  | ||||||
|         final var query = em.createNativeQuery(""" |  | ||||||
|                 select currentTask, targetTable, targetOp |  | ||||||
|                     from tx_journal_v |  | ||||||
|                     where targettable = 'hs_booking_item'; |  | ||||||
|                 """); |  | ||||||
|  |  | ||||||
|         // when |  | ||||||
|         @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); |  | ||||||
|  |  | ||||||
|         // then |  | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |  | ||||||
|                 "[creating booking-item test-data 1000111, hs_booking_item, INSERT]", |  | ||||||
|                 "[creating booking-item test-data 1000212, hs_booking_item, INSERT]", |  | ||||||
|                 "[creating booking-item test-data 1000313, hs_booking_item, INSERT]"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private HsBookingItem givenSomeTemporaryBookingItem(final String projectCaption) { |     private HsBookingItem givenSomeTemporaryBookingItem(final String projectCaption) { | ||||||
|         return jpaAttempt.transacted(() -> { |         return jpaAttempt.transacted(() -> { | ||||||
|             context("superuser-alex@hostsharing.net"); |             context("superuser-alex@hostsharing.net"); | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ import org.springframework.orm.jpa.JpaSystemException; | |||||||
| import jakarta.persistence.EntityManager; | import jakarta.persistence.EntityManager; | ||||||
| import jakarta.persistence.PersistenceContext; | import jakarta.persistence.PersistenceContext; | ||||||
| import jakarta.servlet.http.HttpServletRequest; | import jakarta.servlet.http.HttpServletRequest; | ||||||
|  | import java.sql.Timestamp; | ||||||
|  | import java.time.ZonedDateTime; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| @@ -57,6 +59,50 @@ class HsBookingProjectRepositoryIntegrationTest extends ContextBasedTestWithClea | |||||||
|     @MockBean |     @MockBean | ||||||
|     HttpServletRequest request; |     HttpServletRequest request; | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void auditJournalLogIsAvailable() { | ||||||
|  |         // given | ||||||
|  |         final var query = em.createNativeQuery(""" | ||||||
|  |                 select currentTask, targetTable, targetOp, targetdelta->>'caption' | ||||||
|  |                     from tx_journal_v | ||||||
|  |                     where targettable = 'hs_booking_project'; | ||||||
|  |                     """); | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|  |                 "[creating booking-project test-data, hs_booking_project, INSERT, D-1000111 default project]", | ||||||
|  |                 "[creating booking-project test-data, hs_booking_project, INSERT, D-1000212 default project]", | ||||||
|  |                 "[creating booking-project test-data, hs_booking_project, INSERT, D-1000313 default project]"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void historizationIsAvailable() { | ||||||
|  |         // given | ||||||
|  |         final String nativeQuerySql = """ | ||||||
|  |                 select count(*) | ||||||
|  |                     from hs_booking_project_hv ha; | ||||||
|  |                 """; | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant())); | ||||||
|  |         final var query = em.createNativeQuery(nativeQuerySql, Integer.class); | ||||||
|  |         @SuppressWarnings("unchecked") final var countBefore = (Integer) query.getSingleResult(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(countBefore).as("hs_booking_project_hv should not contain rows for a timestamp in the past").isEqualTo(0); | ||||||
|  |  | ||||||
|  |         // and when | ||||||
|  |         historicalContext(Timestamp.from(ZonedDateTime.now().plusHours(1).toInstant())); | ||||||
|  |         em.createNativeQuery(nativeQuerySql, Integer.class); | ||||||
|  |         @SuppressWarnings("unchecked") final var countAfter = (Integer) query.getSingleResult(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(countAfter).as("hs_booking_project_hv should contain rows for a timestamp in the future").isGreaterThan(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Nested |     @Nested | ||||||
|     class CreateBookingProject { |     class CreateBookingProject { | ||||||
|  |  | ||||||
| @@ -283,25 +329,6 @@ class HsBookingProjectRepositoryIntegrationTest extends ContextBasedTestWithClea | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void auditJournalLogIsAvailable() { |  | ||||||
|         // given |  | ||||||
|         final var query = em.createNativeQuery(""" |  | ||||||
|                 select currentTask, targetTable, targetOp |  | ||||||
|                     from tx_journal_v |  | ||||||
|                     where targettable = 'hs_booking_project'; |  | ||||||
|                     """); |  | ||||||
|  |  | ||||||
|         // when |  | ||||||
|         @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); |  | ||||||
|  |  | ||||||
|         // then |  | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |  | ||||||
|                 "[creating booking-project test-data 1000111, hs_booking_project, INSERT]", |  | ||||||
|                 "[creating booking-project test-data 1000212, hs_booking_project, INSERT]", |  | ||||||
|                 "[creating booking-project test-data 1000313, hs_booking_project, INSERT]"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private HsBookingProjectRealEntity givenSomeTemporaryBookingProject(final int debitorNumber) { |     private HsBookingProjectRealEntity givenSomeTemporaryBookingProject(final int debitorNumber) { | ||||||
|         return jpaAttempt.transacted(() -> { |         return jpaAttempt.transacted(() -> { | ||||||
|             context("superuser-alex@hostsharing.net"); |             context("superuser-alex@hostsharing.net"); | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ import org.springframework.orm.jpa.JpaSystemException; | |||||||
| import jakarta.persistence.EntityManager; | import jakarta.persistence.EntityManager; | ||||||
| import jakarta.persistence.PersistenceContext; | import jakarta.persistence.PersistenceContext; | ||||||
| import jakarta.servlet.http.HttpServletRequest; | import jakarta.servlet.http.HttpServletRequest; | ||||||
|  | import java.sql.Timestamp; | ||||||
|  | import java.time.ZonedDateTime; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| @@ -70,6 +72,66 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu | |||||||
|     @MockBean |     @MockBean | ||||||
|     HttpServletRequest request; |     HttpServletRequest request; | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void auditJournalLogIsAvailable() { | ||||||
|  |         // given | ||||||
|  |         final var query = em.createNativeQuery(""" | ||||||
|  |                 select currentTask, targetTable, targetOp, targetdelta->>'caption' | ||||||
|  |                     from tx_journal_v | ||||||
|  |                     where targettable = 'hs_hosting_asset'; | ||||||
|  |                 """); | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, another CloudServer]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some Domain-DNS-Setup]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some Domain-HTTP-Setup]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some Domain-MBOX-Setup]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some Domain-SMTP-Setup]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some Domain-Setup]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some E-Mail-Address]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some E-Mail-Alias]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some ManagedServer]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some UnixUser for E-Mail]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some UnixUser for Website]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some Webspace]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some default MariaDB instance]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some default MariaDB user]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some default MariaDB database]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some default Postgresql instance]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some default Postgresql user]", | ||||||
|  |                 "[creating hosting-asset test-data, hs_hosting_asset, INSERT, some default Postgresql database]" | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     public void historizationIsAvailable() { | ||||||
|  |         // given | ||||||
|  |         final String nativeQuerySql = """ | ||||||
|  |                 select count(*) | ||||||
|  |                     from hs_hosting_asset_hv ha; | ||||||
|  |                 """; | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         historicalContext(Timestamp.from(ZonedDateTime.now().minusDays(1).toInstant())); | ||||||
|  |         final var query = em.createNativeQuery(nativeQuerySql, Integer.class); | ||||||
|  |         @SuppressWarnings("unchecked") final var countBefore = (Integer) query.getSingleResult(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(countBefore).as("hs_hosting_asset_hv should not contain rows for a timestamp in the past").isEqualTo(0); | ||||||
|  |  | ||||||
|  |         // and when | ||||||
|  |         historicalContext(Timestamp.from(ZonedDateTime.now().plusHours(1).toInstant())); | ||||||
|  |         em.createNativeQuery(nativeQuerySql, Integer.class); | ||||||
|  |         @SuppressWarnings("unchecked") final var countAfter = (Integer) query.getSingleResult(); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(countAfter).as("hs_hosting_asset_hv should contain rows for a timestamp in the future").isGreaterThan(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Nested |     @Nested | ||||||
|     class CreateAsset { |     class CreateAsset { | ||||||
|  |  | ||||||
| @@ -391,25 +453,6 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void auditJournalLogIsAvailable() { |  | ||||||
|         // given |  | ||||||
|         final var query = em.createNativeQuery(""" |  | ||||||
|                 select currentTask, targetTable, targetOp |  | ||||||
|                     from tx_journal_v |  | ||||||
|                     where targettable = 'hs_hosting_asset'; |  | ||||||
|                 """); |  | ||||||
|  |  | ||||||
|         // when |  | ||||||
|         @SuppressWarnings("unchecked") final List<Object[]> customerLogEntries = query.getResultList(); |  | ||||||
|  |  | ||||||
|         // then |  | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |  | ||||||
|                 "[creating hosting-asset test-data D-1000111 default project, hs_hosting_asset, INSERT]", |  | ||||||
|                 "[creating hosting-asset test-data D-1000212 default project, hs_hosting_asset, INSERT]", |  | ||||||
|                 "[creating hosting-asset test-data D-1000313 default project, hs_hosting_asset, INSERT]"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private HsHostingAssetRealEntity givenSomeTemporaryAsset(final String projectCaption, final String identifier) { |     private HsHostingAssetRealEntity givenSomeTemporaryAsset(final String projectCaption, final String identifier) { | ||||||
|         return jpaAttempt.transacted(() -> { |         return jpaAttempt.transacted(() -> { | ||||||
|             context("superuser-alex@hostsharing.net"); // needed to determine creator |             context("superuser-alex@hostsharing.net"); // needed to determine creator | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -249,8 +249,11 @@ 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(); | ||||||
|  |             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_item_ex 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_booking_project_ex where true").executeUpdate(); | ||||||
|             em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); |             em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); | ||||||
|             em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate(); |             em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate(); | ||||||
|             em.createNativeQuery("delete from hs_office_coopsharestransaction where true").executeUpdate(); |             em.createNativeQuery("delete from hs_office_coopsharestransaction where true").executeUpdate(); | ||||||
| @@ -292,7 +295,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(); | ||||||
|   | |||||||
| @@ -271,7 +271,7 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'iban' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_bankaccount'; |                     where targettable = 'hs_office_bankaccount'; | ||||||
|                     """); |                     """); | ||||||
| @@ -281,8 +281,9 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating bankaccount test-data First GmbH, hs_office_bankaccount, INSERT]", |                 "[creating bankaccount test-data, hs_office_bankaccount, INSERT, DE02120300000000202051]", | ||||||
|                 "[creating bankaccount test-data Second e.K., hs_office_bankaccount, INSERT]"); |                 "[creating bankaccount test-data, hs_office_bankaccount, INSERT, DE02500105170137075030]", | ||||||
|  |                 "[creating bankaccount test-data, hs_office_bankaccount, INSERT, DE02100500000054540402]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficeBankAccountEntity givenSomeTemporaryBankAccount(final String createdByUser) { |     private HsOfficeBankAccountEntity givenSomeTemporaryBankAccount(final String createdByUser) { | ||||||
|   | |||||||
| @@ -256,7 +256,7 @@ class HsOfficeContactRbacRepositoryIntegrationTest extends ContextBasedTestWithC | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'caption' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_contact'; |                     where targettable = 'hs_office_contact'; | ||||||
|                     """); |                     """); | ||||||
| @@ -266,8 +266,9 @@ class HsOfficeContactRbacRepositoryIntegrationTest extends ContextBasedTestWithC | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating contact test-data first contact, hs_office_contact, INSERT]", |                 "[creating contact test-data, hs_office_contact, INSERT, first contact]", | ||||||
|                 "[creating contact test-data second contact, hs_office_contact, INSERT]"); |                 "[creating contact test-data, hs_office_contact, INSERT, second contact]", | ||||||
|  |                 "[creating contact test-data, hs_office_contact, INSERT, third contact]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficeContactRbacEntity givenSomeTemporaryContact( |     private HsOfficeContactRbacEntity givenSomeTemporaryContact( | ||||||
|   | |||||||
| @@ -220,7 +220,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'reference' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_coopassetstransaction'; |                     where targettable = 'hs_office_coopassetstransaction'; | ||||||
|                     """); |                     """); | ||||||
| @@ -230,8 +230,18 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating coopAssetsTransaction test-data 1000101, hs_office_coopassetstransaction, INSERT]", |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000101-1]", | ||||||
|                 "[creating coopAssetsTransaction test-data 1000202, hs_office_coopassetstransaction, INSERT]"); |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000101-2]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000101-3]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000101-3]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000202-1]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000202-2]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000202-3]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000202-3]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000303-1]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000303-2]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000303-3]", | ||||||
|  |                 "[creating coopAssetsTransaction test-data, hs_office_coopassetstransaction, INSERT, ref 1000303-3]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @BeforeEach |     @BeforeEach | ||||||
|   | |||||||
| @@ -219,7 +219,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'reference' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_coopsharestransaction'; |                     where targettable = 'hs_office_coopsharestransaction'; | ||||||
|                     """); |                     """); | ||||||
| @@ -229,8 +229,18 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating coopSharesTransaction test-data 1000101, hs_office_coopsharestransaction, INSERT]", |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000101-1]", | ||||||
|                 "[creating coopSharesTransaction test-data 1000202, hs_office_coopsharestransaction, INSERT]"); |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000101-2]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000101-3]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000101-4]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000202-1]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000202-2]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000202-3]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000202-4]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000303-1]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000303-2]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000303-3]", | ||||||
|  |                 "[creating coopSharesTransaction test-data, hs_office_coopsharestransaction, INSERT, ref 1000303-4]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @BeforeEach |     @BeforeEach | ||||||
|   | |||||||
| @@ -589,7 +589,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'defaultprefix' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_debitor'; |                     where targettable = 'hs_office_debitor'; | ||||||
|                     """); |                     """); | ||||||
| @@ -599,8 +599,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating debitor test-data FirstGmbH-firstcontact, hs_office_debitor, INSERT]", |                 "[creating debitor test-data, hs_office_debitor, INSERT, fir]", | ||||||
|                 "[creating debitor test-data Seconde.K.-secondcontact, hs_office_debitor, INSERT]"); |                 "[creating debitor test-data, hs_office_debitor, INSERT, sec]", | ||||||
|  |                 "[creating debitor test-data, hs_office_debitor, INSERT, thi]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficeDebitorEntity givenSomeTemporaryDebitor( |     private HsOfficeDebitorEntity givenSomeTemporaryDebitor( | ||||||
|   | |||||||
| @@ -336,7 +336,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'membernumbersuffix' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_membership'; |                     where targettable = 'hs_office_membership'; | ||||||
|                     """); |                     """); | ||||||
| @@ -346,9 +346,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating Membership test-data P-10001M-...01, hs_office_membership, INSERT]", |                 "[creating Membership test-data, hs_office_membership, INSERT, 01]", | ||||||
|                 "[creating Membership test-data P-10002M-...02, hs_office_membership, INSERT]", |                 "[creating Membership test-data, hs_office_membership, INSERT, 02]", | ||||||
|                 "[creating Membership test-data P-10003M-...03, hs_office_membership, INSERT]"); |                 "[creating Membership test-data, hs_office_membership, INSERT, 03]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String memberNumberSuffix) { |     private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String memberNumberSuffix) { | ||||||
|   | |||||||
| @@ -433,7 +433,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'partnernumber' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_partner'; |                     where targettable = 'hs_office_partner'; | ||||||
|                     """); |                     """); | ||||||
| @@ -443,8 +443,11 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating partner test-data FirstGmbH-firstcontact, hs_office_partner, INSERT]", |                 "[creating partner test-data , hs_office_partner, INSERT, 10001]", | ||||||
|                 "[creating partner test-data Seconde.K.-secondcontact, hs_office_partner, INSERT]"); |                 "[creating partner test-data , hs_office_partner, INSERT, 10002]", | ||||||
|  |                 "[creating partner test-data , hs_office_partner, INSERT, 10003]", | ||||||
|  |                 "[creating partner test-data , hs_office_partner, INSERT, 10004]", | ||||||
|  |                 "[creating partner test-data , hs_office_partner, INSERT, 10010]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficePartnerEntity givenSomeTemporaryHostsharingPartner( |     private HsOfficePartnerEntity givenSomeTemporaryHostsharingPartner( | ||||||
|   | |||||||
| @@ -260,7 +260,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'tradename', targetdelta->>'lastname' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_person'; |                     where targettable = 'hs_office_person'; | ||||||
|                     """); |                     """); | ||||||
| @@ -270,8 +270,10 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating person test-data First GmbH, hs_office_person, INSERT]", |                 "[creating person test-data, hs_office_person, INSERT, Hostsharing eG, null]", | ||||||
|                 "[creating person test-data Second e.K., Smith, Peter, hs_office_person, INSERT]"); |                 "[creating person test-data, hs_office_person, INSERT, First GmbH, null]", | ||||||
|  |                 "[creating person test-data, hs_office_person, INSERT, Second e.K., null]", | ||||||
|  |                 "[creating person test-data, hs_office_person, INSERT, Third OHG, null]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficePersonEntity givenSomeTemporaryPerson( |     private HsOfficePersonEntity givenSomeTemporaryPerson( | ||||||
|   | |||||||
| @@ -394,7 +394,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'mark' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_relation'; |                     where targettable = 'hs_office_relation'; | ||||||
|                     """); |                     """); | ||||||
| @@ -404,8 +404,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating relation test-data HostsharingeG-FirstGmbH, hs_office_relation, INSERT]", |                 "[creating relation test-data, hs_office_relation, INSERT, members-announce]"); | ||||||
|                 "[creating relation test-data FirstGmbH-Firby, hs_office_relation, INSERT]"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficeRelationRbacEntity givenSomeTemporaryRelationBessler(final String holderPerson, final String contact) { |     private HsOfficeRelationRbacEntity givenSomeTemporaryRelationBessler(final String holderPerson, final String contact) { | ||||||
|   | |||||||
| @@ -379,7 +379,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC | |||||||
|     public void auditJournalLogIsAvailable() { |     public void auditJournalLogIsAvailable() { | ||||||
|         // given |         // given | ||||||
|         final var query = em.createNativeQuery(""" |         final var query = em.createNativeQuery(""" | ||||||
|                 select currentTask, targetTable, targetOp |                 select currentTask, targetTable, targetOp, targetdelta->>'reference' | ||||||
|                     from tx_journal_v |                     from tx_journal_v | ||||||
|                     where targettable = 'hs_office_sepamandate'; |                     where targettable = 'hs_office_sepamandate'; | ||||||
|                     """); |                     """); | ||||||
| @@ -389,9 +389,9 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC | |||||||
|  |  | ||||||
|         // then |         // then | ||||||
|         assertThat(customerLogEntries).map(Arrays::toString).contains( |         assertThat(customerLogEntries).map(Arrays::toString).contains( | ||||||
|                 "[creating SEPA-mandate test-data 1000111, hs_office_sepamandate, INSERT]", |                 "[creating SEPA-mandate test-data, hs_office_sepamandate, INSERT, ref-10001-11]", | ||||||
|                 "[creating SEPA-mandate test-data 1000212, hs_office_sepamandate, INSERT]", |                 "[creating SEPA-mandate test-data, hs_office_sepamandate, INSERT, ref-10002-12]", | ||||||
|                 "[creating SEPA-mandate test-data 1000313, hs_office_sepamandate, INSERT]"); |                 "[creating SEPA-mandate test-data, hs_office_sepamandate, INSERT, ref-10003-13]"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandate(final String iban) { |     private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandate(final String iban) { | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import org.springframework.context.annotation.Import; | |||||||
|  |  | ||||||
| import jakarta.persistence.EntityManager; | import jakarta.persistence.EntityManager; | ||||||
| import jakarta.persistence.PersistenceContext; | import jakarta.persistence.PersistenceContext; | ||||||
|  | import java.sql.Timestamp; | ||||||
|  |  | ||||||
| @Import(RbacGrantsDiagramService.class) | @Import(RbacGrantsDiagramService.class) | ||||||
| public abstract class ContextBasedTest { | public abstract class ContextBasedTest { | ||||||
| @@ -47,4 +48,26 @@ public abstract class ContextBasedTest { | |||||||
|     protected void context(final String currentUser) { |     protected void context(final String currentUser) { | ||||||
|         context(currentUser, null); |         context(currentUser, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     protected void historicalContext(final Long txId) { | ||||||
|  |         // set local cannot be used with query parameters | ||||||
|  |         em.createNativeQuery(""" | ||||||
|  |                 set local hsadminng.tx_history_txid to ':txid'; | ||||||
|  |                 """.replace(":txid", txId.toString())).executeUpdate(); | ||||||
|  |         em.createNativeQuery(""" | ||||||
|  |                 set local hsadminng.tx_history_timestamp to ''; | ||||||
|  |                 """).executeUpdate(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     protected void historicalContext(final Timestamp txTimestamp) { | ||||||
|  |         // set local cannot be used with query parameters | ||||||
|  |         em.createNativeQuery(""" | ||||||
|  |                 set local hsadminng.tx_history_timestamp to ':timestamp'; | ||||||
|  |                 """.replace(":timestamp", txTimestamp.toString())).executeUpdate(); | ||||||
|  |         em.createNativeQuery(""" | ||||||
|  |                 set local hsadminng.tx_history_txid to ''; | ||||||
|  |                 """).executeUpdate(); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user