1
0

avoid-recursive-rbac-query-for-global-admins in the _rv generator (#216)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/216
Reviewed-by: Marc Sandlus <hsh-marcsandlus@noreply.dev.hostsharing.net>
This commit is contained in:
Michael Hoennig
2026-03-19 10:17:08 +01:00
parent 79d4d8c7f2
commit 3a24e1c726
25 changed files with 1101 additions and 42 deletions
@@ -0,0 +1,325 @@
# PR#216: RBAC Performance Optimization for Global Admins
## The Problem
We have a severe performance problem in SELECT Queries when executed as global-admin.
The cause of the performance problem is, that if a global-admin runs SELECT queries,
they can see all rows but yet the ReBAC filter is still active.
In other words, in the case of a SELECT without a WHERE-condition,
the ReBAC access rights are checked for each row in the target table.
This is horribly expensive because that's a recursive CTE query.
There was some shortcut in the code (see `procedure rbac.generateRbacRestrictedView` before this merge commit),
but which was not really used by the query-optimizer and still the whole recursive CTE query got exectuted.
This can be seen below in [Query-Plan before](#query-plan-before).
## The Solution
To find a solution, we need mass test-data, a query-plan analysis and a refactored rekursive CTE query.
### Test-Data Generation
To be able to do performance-tests, mass test-data was needed.
I estimated the production database contains about 400 partner records and 500 SEPA mandate records, both including old ones.
For performance-tests I needed similar test data, or better even a bit more to be future safe, e.g. about twice the quantity.
The test-data script is a stored procedure `procedure hs_office.contact_create_mass_test_data` which is also part of the test-data Liquibase profile, but just the script, no mass test-data is generated automatically.
You can actually generate mass test-data by running the following SQL commands:
```PostgresSQL
rollback; -- for the case of any previously failed transaction
-- generate test data for partner numbers 20xxx with 80% membership
call hs_office.partner_create_mass_bundle_test_data(20000, 20999, 80);
-- show some statistics about what was generated
select * from hs_statistics_v;
-- we mostly care about SEPA mandates:
select count(*) from hs_office.sepamandate;
```
The last statement will most likely show 1003, 3 from the normal test-data plus 1000 from the mass test data.
Find the statistics for the database after mass test-data generation in attachment [Mass-Data-Statistics](#attachment-mass-data-statistics).
### The Performance-Test Script
Find the performance-script in `procedure hs_office.bench_debitor_sepamandates`,
which is now part of the Liquibase-changesets for the test-data profile.
The test can be run this way:
```PostgreSQL
\o /dev/null
rollback;
call hs_office.bench_debitor_sepamandates(100); -- 100 is the number of loops
```
### Query Plan Analysis
To get hints about what's going wrong, I did a query-plan analysis:
```PostgreSQ
rollback;
begin;
call base.defineContext( 'query debitor', null, 'superuser-alex@hostsharing.net' );
\timing on
explain analyze
select count(*)
from hs_office.debitor d
join hs_office.sepamandate_rv s on (s.debitoruuid = d.uuid)
join hs_office.bankaccount b on (b.uuid = s.bankAccountUuid);
```
#### Query-Plan before
The following was the query plan with the shortcut-optimization which was not picked up by the query-optimizer,
thus on the commit before the merge-commit for this branch.
No need to read the resulting query-plan in details, simply put, it does way too much.
If curious, you can find it [in the attachment](#attachment-query-plan-before)
#### Query-Plan after optimization with isGlobalAdmin-cache
```
Aggregate (cost=90.27..90.28 rows=1 width=8) (actual time=14.961..14.982 rows=1 loops=1)
-> Hash Join (cost=72.50..87.77 rows=1000 width=0) (actual time=7.692..13.800 rows=1003 loops=1)
Hash Cond: (select_hs_office_sepamandate_rv.bankaccountuuid = b.uuid)
-> Hash Join (cost=35.82..48.45 rows=1000 width=16) (actual time=3.945..7.648 rows=1003 loops=1)
Hash Cond: (select_hs_office_sepamandate_rv.debitoruuid = d.uuid)
-> Function Scan on select_hs_office_sepamandate_rv (cost=0.25..10.25 rows=1000 width=32) (actual time=1.018..2.277 rows=1003 loops=1)
-> Hash (cost=23.03..23.03 rows=1003 width=16) (actual time=2.894..2.899 rows=1003 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 56kB
-> Seq Scan on debitor d (cost=0.00..23.03 rows=1003 width=16) (actual time=0.020..1.464 rows=1003 loops=1)
-> Hash (cost=24.08..24.08 rows=1008 width=16) (actual time=3.734..3.738 rows=1008 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 56kB
-> Seq Scan on bankaccount b (cost=0.00..24.08 rows=1008 width=16) (actual time=0.036..1.937 rows=1008 loops=1)
Planning Time: 0.349 ms
Execution Time: 15.063 ms
```
This looks like a sensible tidy query plan for the job.
### Performance-Comparison
#### case A) with old optimization, freshly generated schema
```
limit10 min/avg/max: 2545.297 ms / 2796.658 ms / 4433.851 ms
count all min/avg/max: 2571.942 ms / 2821.460 ms / 3658.856 ms
completed in 9 m 22 s 8 ms
```
This clearly shows the problem; up to almost 3 seconds for a single query is too long.
#### case B) with new optimization, freshly generated schema
```
limit10 min/avg/max: 2418.032 ms / 2656.825 ms / 4212.158 ms
count all min/avg/max: 2443.345 ms / 2680.387 ms / 3475.913 ms
completed in 8 m 53 s 908 ms
```
My new shortcut implementation in the recursive CTE query did not show much improvement,
probably so even no improvement as the difference is statistically just too little.
#### case C) without optimization, but cached isGlobalAdmin, freshly generated schema
Now, I went back to the previous shortcut but store the information
if the current subject is a global admin as a session-variable.
The caching is done right when `global.defineContext()` is called.
```
limit10 min/avg/max: 383.585 ms / 425.593 ms / 1135.419 ms
count all min/avg/max: 374.289 ms / 408.141 ms / 474.026 ms
completed in 1 m 23 s 437 ms
```
As we can see, this showed some progress, but not enough.
#### case D) with new optimization + cache, freshly generated schema
Now I combined both approaches, the new shortcut and cached the isGlobalAdmin information
```
limit10 min/avg/max: 0.806 ms / 1.212 ms / 2.159 ms
count all min/avg/max: 1.305 ms / 1.805 ms / 3.469 ms
completed in 620 ms
```
This brought the breakthrough; we are now down from almost 4 seconds to below 4 milliseconds,
**faster by a factor of 1000**.
#### case E) with new optimization + cache, upgraded schema
So far, I always freshly generated the schema.
But for our production database, we need to upgrade the existing schema.
Unfortunately, parts of the improved implementation are in code that is generated
(by `procedure rbac.generateRbacRestrictedView`), thus,
not just the generator had to be updated, but also be called for each table with RBAC support.
To be on the safe side that it really worked, I ran the performance-tests again:
```
limit10 min/avg/max: 0.515 ms / 1.012 ms / 3.464 ms
count all min/avg/max: 1.116 ms / 1.801 ms / 2.614 ms
completed in 510 ms
```
Which is quite similar to case D, as expected.
### Epilogue
This performance optimization only works if the current subject is a global admin,
the global-admin role may or may not be assumed, but no lower role.
If any lower role gets assumed or the subject is not granted the global-admin role,
the rekursive CTE query still has to be executed.
This might still be a performance problem, but not as bad as in the case of a global-admin,
because normal users cannot see that many objects, nor do they have that many (indirect) grants.
Therefore, both the width and the depth of the recursion are much smaller than for global-admins.
But for users who can see very many objects, e.g. the admin of a large client,
there could still be a severe performance problem.
There are ideas for optimizing the ReBAC-system, which are described in [RBAC Performance Analysis](rbac-performance-analysis.md#the-problematically-huge-join).
But these need major changes in the RBAC system, for which we currently have no financial capacity.
## Attachments
### Attachment: Mass-Data-Statistics
| count | rbac-table | hs-table | type |
| :--- | :--- | :--- | :--- |
| 218 019 | grants | | |
| 168 865 | references | | |
| 94 370 | permissions | | |
| 69 242 | roles | | |
| 29 576 | objects | | |
| 7 021 | objects | hs\_booking.item | |
| 5 253 | login users | | |
| 4 818 | objects | hs\_office.coopassettx | |
| 3 212 | objects | hs\_office.coopsharetx | |
| 3 015 | objects | hs\_office.relation | |
| 2 017 | objects | hs\_office.person | |
| 2 006 | objects | hs\_booking.item | MANAGED\_WEBSPACE |
| 2 006 | objects | hs\_booking.item | MANAGED\_SERVER |
| 2 006 | objects | hs\_booking.item | CLOUD\_SERVER |
| 1 620 | objects | hs\_hosting.asset | |
| 1 012 | objects | hs\_office.contact | |
| 1 008 | objects | hs\_office.bankaccount | |
| 1 005 | objects | hs\_office.partner | |
| 1 005 | objects | hs\_office.partner\_details | |
| 1 003 | objects | hs\_office.debitor | |
| 1 003 | objects | hs\_booking.item | PRIVATE\_CLOUD |
| 1 003 | objects | hs\_booking.project | |
| 1 003 | objects | hs\_office.sepamandate | |
| 803 | objects | hs\_office.membership | |
| 180 | objects | hs\_hosting.asset | UNIX\_USER |
| 90 | objects | hs\_hosting.asset | DOMAIN\_SMTP\_SETUP |
| 90 | objects | hs\_hosting.asset | EMAIL\_ADDRESS |
| 90 | objects | hs\_hosting.asset | CLOUD\_SERVER |
| 90 | objects | hs\_hosting.asset | PGSQL\_DATABASE |
| 90 | objects | hs\_hosting.asset | MANAGED\_WEBSPACE |
| 90 | objects | hs\_hosting.asset | DOMAIN\_SETUP |
| 90 | objects | hs\_hosting.asset | MARIADB\_USER |
| 90 | objects | hs\_hosting.asset | PGSQL\_USER |
| 90 | objects | hs\_hosting.asset | DOMAIN\_MBOX\_SETUP |
| 90 | objects | hs\_hosting.asset | DOMAIN\_HTTP\_SETUP |
| 90 | objects | hs\_hosting.asset | DOMAIN\_DNS\_SETUP |
| 90 | objects | hs\_hosting.asset | MANAGED\_SERVER |
| 90 | objects | hs\_hosting.asset | PGSQL\_INSTANCE |
| 90 | objects | hs\_hosting.asset | EMAIL\_ALIAS |
| 90 | objects | hs\_hosting.asset | MARIADB\_DATABASE |
| 90 | objects | hs\_hosting.asset | MARIADB\_INSTANCE |
| 18 | objects | rbactest.domain | |
| 9 | objects | rbactest.package | |
| 3 | objects | rbactest.customer | |
| 1 | objects | rbac.global | |
### Attachment: Query-Plan before
```
Aggregate (cost=1800394.72..1800394.73 rows=1 width=8) (actual time=801.557..801.594 rows=1 loops=1)
-> Hash Join (cost=1800381.15..1800393.04 rows=669 width=0) (actual time=794.549..800.418 rows=1003 loops=1)
Hash Cond: (target.bankaccountuuid = b.uuid)
-> Hash Join (cost=1800344.47..1800354.60 rows=669 width=16) (actual time=60.423..63.940 rows=1003 loops=1)
Hash Cond: (target.debitoruuid = d.uuid)
-> Sort (cost=1800308.90..1800310.57 rows=669 width=280) (actual time=56.670..57.796 rows=1003 loops=1)
Sort Key: target.validity
Sort Method: quicksort Memory: 87kB
CTE accessible_uuids
-> HashAggregate (cost=1799078.92..1799361.78 rows=28286 width=16) (never executed)
Group Key: perm.objectuuid
CTE recursive_grants
-> Recursive Union (cost=4655.41..1575457.36 rows=3199924 width=37) (never executed)
-> Subquery Scan on "*SELECT* 1" (cost=4655.41..4720.95 rows=6554 width=37) (never executed)
-> HashAggregate (cost=4655.41..4720.95 rows=6554 width=37) (never executed)
Group Key: "grant".descendantuuid, "grant".ascendantuuid
-> Bitmap Heap Scan on "grant" (cost=136.43..4622.27 rows=6629 width=37) (never executed)
Recheck Cond: (ascendantuuid = ANY (rbac.currentsubjectorassumedrolesuuids()))
Filter: assumed
-> Bitmap Index Scan on grant_ascendantuuid_idx (cost=0.00..134.77 rows=6715 width=0) (never executed)
Index Cond: (ascendantuuid = ANY (rbac.currentsubjectorassumedrolesuuids()))
-> Unique (cost=149882.00..153873.72 rows=319337 width=37) (never executed)
-> Sort (cost=149882.00..150680.35 rows=319337 width=37) (never executed)
Sort Key: g.descendantuuid, g.ascendantuuid, ((grants.level + 1)), (base.asserttrue((grants.level < 22), ('too many grant-levels: '::text (grants.level)::text)))
-> Merge Join (cost=6554.45..111954.57 rows=319337 width=37) (never executed)
Merge Cond: (g.ascendantuuid = grants.descendantuuid)
-> Index Scan using grant_ascendantuuid_idx on "grant" g (cost=0.42..16246.48 rows=215214 width=32) (never executed)
Filter: assumed
-> Sort (cost=6554.03..6717.88 rows=65540 width=20) (never executed)
Sort Key: grants.descendantuuid
-> WorkTable Scan on recursive_grants grants (cost=0.00..1310.80 rows=65540 width=20) (never executed)
CTE count_check
-> Result (cost=143996.60..143996.87 rows=1 width=1) (never executed)
InitPlan 2
-> Aggregate (cost=71998.29..71998.30 rows=1 width=8) (never executed)
-> CTE Scan on recursive_grants (cost=0.00..63998.48 rows=3199924 width=0) (never executed)
InitPlan 3
-> Aggregate (cost=71998.29..71998.30 rows=1 width=8) (never executed)
-> CTE Scan on recursive_grants recursive_grants_1 (cost=0.00..63998.48 rows=3199924 width=0) (never executed)
-> Hash Join (cost=2270.14..79353.40 rows=108518 width=16) (never executed)
Hash Cond: (recursive_grants_2.descendantuuid = perm.uuid)
-> CTE Scan on recursive_grants recursive_grants_2 (cost=0.00..63998.48 rows=3199924 width=16) (never executed)
-> Hash (cost=2230.14..2230.14 rows=3200 width=32) (never executed)
-> Hash Join (cost=80.55..2230.14 rows=3200 width=32) (never executed)
Hash Cond: (perm.objectuuid = obj.uuid)
-> Seq Scan on permission perm (cost=0.00..1763.70 rows=94370 width=32) (never executed)
-> Hash (cost=68.02..68.02 rows=1003 width=16) (never executed)
-> Nested Loop (cost=0.41..68.02 rows=1003 width=16) (never executed)
-> CTE Scan on count_check cc (cost=0.00..0.02 rows=1 width=0) (never executed)
Filter: valid
-> Index Only Scan using object_objecttable_uuid_key on object obj (cost=0.41..57.97 rows=1003 width=16) (never executed)
Index Cond: (objecttable = 'hs_office.sepamandate'::text)
Heap Fetches: 0
-> Seq Scan on sepamandate target (cost=636.44..915.72 rows=669 width=280) (actual time=1.189..54.696 rows=1003 loops=1)
Filter: (rbac.hasglobaladminrole() OR (ANY (uuid = (hashed SubPlan 6).col1)))
SubPlan 6
-> CTE Scan on accessible_uuids (cost=0.00..565.72 rows=28286 width=16) (never executed)
-> Hash (cost=23.03..23.03 rows=1003 width=16) (actual time=3.680..3.683 rows=1003 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 56kB
-> Seq Scan on debitor d (cost=0.00..23.03 rows=1003 width=16) (actual time=0.168..1.824 rows=1003 loops=1)
-> Hash (cost=24.08..24.08 rows=1008 width=16) (actual time=734.056..734.059 rows=1008 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 56kB
-> Seq Scan on bankaccount b (cost=0.00..24.08 rows=1008 width=16) (actual time=731.222..732.672 rows=1008 loops=1)
Planning Time: 3.765 ms
JIT:
Functions: 81
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 9.230 ms (Deform 3.886 ms), Inlining 201.353 ms, Optimization 312.388 ms, Emission 217.583 ms, Total 740.554 ms
Execution Time: 888.157 ms
```
@@ -83,7 +83,7 @@ begin
end; $$; end; $$;
-- ============================================================================ -- ============================================================================
--changeset michael.hoennig:rbac-context-CONTEXT-DEFINED endDelimiter:--// --changeset michael.hoennig:rbac-context-CONTEXT-DEFINED runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Callback which is called after the context has been (re-) defined. Callback which is called after the context has been (re-) defined.
@@ -98,6 +98,7 @@ create or replace procedure base.contextDefined(
language plpgsql as $$ language plpgsql as $$
declare declare
currentSubjectUuid uuid; currentSubjectUuid uuid;
currentSubjectHasGlobalAdminRole boolean;
begin begin
execute format('set local hsadminng.currentTask to %L', currentTask); execute format('set local hsadminng.currentTask to %L', currentTask);
@@ -111,6 +112,13 @@ begin
execute format('set local hsadminng.currentSubjectOrAssumedRolesUuids to %L', execute format('set local hsadminng.currentSubjectOrAssumedRolesUuids to %L',
(select array_to_string(rbac.determineCurrentSubjectOrAssumedRolesUuids(currentSubjectUuid, assumedRoles), ';'))); (select array_to_string(rbac.determineCurrentSubjectOrAssumedRolesUuids(currentSubjectUuid, assumedRoles), ';')));
if currentSubjectUuid is null then
currentSubjectHasGlobalAdminRole := false;
else
currentSubjectHasGlobalAdminRole := rbac.isGranted(array[currentSubjectUuid], rbac.findRoleId(rbac.global_ADMIN()));
end if;
execute format('set local hsadminng.isGlobalAdmin to %L', currentSubjectHasGlobalAdminRole::text);
raise notice 'Context defined as: %, %, %, [%]', currentTask, currentRequest, currentSubject, assumedRoles; raise notice 'Context defined as: %, %, %, [%]', currentTask, currentRequest, currentSubject, assumedRoles;
end; $$; end; $$;
@@ -181,4 +189,3 @@ begin
return string_to_array(currentSubjectOrAssumedRolesUuids, ';'); return string_to_array(currentSubjectOrAssumedRolesUuids, ';');
end; $$; end; $$;
--// --//
@@ -179,8 +179,10 @@ create or replace procedure rbac.generateRbacRestrictedView(targetTable text, or
declare declare
sql text; sql text;
newColumns text; newColumns text;
functionName text;
begin begin
targetTable := lower(targetTable); targetTable := lower(targetTable);
functionName := replace(targetTable, '.', '_');
if columnNames = '*' then if columnNames = '*' then
columnNames := base.tableColumnNames(targetTable); columnNames := base.tableColumnNames(targetTable);
end if; end if;
@@ -189,7 +191,18 @@ begin
Creates a restricted view based on the 'SELECT' permission of the current subject. Creates a restricted view based on the 'SELECT' permission of the current subject.
*/ */
sql := format($sql$ sql := format($sql$
create or replace view %1$s_rv as create or replace function rbac.select_%3$s_rv()
returns setof %1$s
language plpgsql
as $f$
begin
if rbac.hasGlobalAdminRole() then
return query
select target.*
from %1$s as target
order by %2$s;
else
return query
with accessible_uuids as ( with accessible_uuids as (
with recursive with recursive
recursive_grants as recursive_grants as
@@ -223,11 +236,17 @@ begin
) )
select target.* select target.*
from %1$s as target from %1$s as target
where rbac.hasGlobalAdminRole() or target.uuid in (select * from accessible_uuids) where target.uuid in (select * from accessible_uuids)
order by %2$s; order by %2$s;
end if;
end;
$f$;
create or replace view %1$s_rv as
select * from rbac.select_%3$s_rv();
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
$sql$, targetTable, orderBy); $sql$, targetTable, orderBy, functionName);
execute sql; execute sql;
/** /**
@@ -23,23 +23,34 @@ grant select on rbac.global to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
-- ============================================================================ -- ============================================================================
--changeset michael.hoennig:rbac-global-IS-GLOBAL-ADMIN endDelimiter:--// --changeset michael.hoennig:rbac-global-IS-GLOBAL-ADMIN runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ------------------------------------------------------------------ -- ------------------------------------------------------------------
create or replace function rbac.isGlobalAdmin() create or replace function rbac.isGlobalAdmin()
returns boolean returns boolean
language plpgsql as $$ language plpgsql as $$
declare
isGlobalAdmin text;
begin begin
return rbac.isGranted(rbac.currentSubjectOrAssumedRolesUuids(), rbac.findRoleId(rbac.global_ADMIN())); isGlobalAdmin := current_setting('hsadminng.isGlobalAdmin', true);
if isGlobalAdmin is not null then
return isGlobalAdmin::boolean;
end if;
raise exception '`hsadminng.isGlobalAdmin` should have been set by `rbac.defineContext()`';
end; $$; end; $$;
--// --//
-- ============================================================================ -- ============================================================================
--changeset michael.hoennig:rbac-global-HAS-GLOBAL-ADMIN-ROLE endDelimiter:--// --changeset michael.hoennig:rbac-global-HAS-GLOBAL-ADMIN-ROLE runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/* /*
Returns true if the current user is a global admin and has no assumed role. Returns true if the current user is a global admin and has no assumed role.
ATTENTION: It's false if the global-admin role is assumed,
because the global admin role does not have the global admin role, but it is the global admin role.
The differentiation is important for the cases where this function is used.
*/ */
create or replace function rbac.hasGlobalAdminRole() create or replace function rbac.hasGlobalAdminRole()
returns boolean returns boolean
@@ -167,6 +167,7 @@ call rbac.generateRbacIdentityViewFromProjection('rbactest.customer',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:rbactest-customer-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:rbactest-customer-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('rbactest.customer', call rbac.generateRbacRestrictedView('rbactest.customer',
$orderBy$ $orderBy$
reference reference
@@ -232,6 +232,7 @@ call rbac.generateRbacIdentityViewFromProjection('rbactest.package',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:rbactest-package-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:rbactest-package-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('rbactest.package', call rbac.generateRbacRestrictedView('rbactest.package',
$orderBy$ $orderBy$
name name
@@ -231,6 +231,7 @@ call rbac.generateRbacIdentityViewFromProjection('rbactest.domain',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:rbactest-domain-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:rbactest-domain-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('rbactest.domain', call rbac.generateRbacRestrictedView('rbactest.domain',
$orderBy$ $orderBy$
name name
@@ -90,6 +90,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.contact',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-contact-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-contact-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.contact', call rbac.generateRbacRestrictedView('hs_office.contact',
$orderBy$ $orderBy$
caption caption
@@ -90,6 +90,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.person',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-person-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-person-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.person', call rbac.generateRbacRestrictedView('hs_office.person',
$orderBy$ $orderBy$
concat(tradeName, familyName, givenName) concat(tradeName, familyName, givenName)
@@ -245,6 +245,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.relation',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-relation-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-relation-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.relation', call rbac.generateRbacRestrictedView('hs_office.relation',
$orderBy$ $orderBy$
(select idName from hs_office.person_iv p where p.uuid = target.holderUuid) (select idName from hs_office.person_iv p where p.uuid = target.holderUuid)
@@ -244,6 +244,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.partner',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-partner-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-partner-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.partner', call rbac.generateRbacRestrictedView('hs_office.partner',
$orderBy$ $orderBy$
'P-' || partnerNumber 'P-' || partnerNumber
@@ -151,6 +151,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.partner_details',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-partner-details-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-partner-details-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.partner_details', call rbac.generateRbacRestrictedView('hs_office.partner_details',
$orderBy$ $orderBy$
uuid uuid
@@ -90,6 +90,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.bankaccount',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-bankaccount-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-bankaccount-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.bankaccount', call rbac.generateRbacRestrictedView('hs_office.bankaccount',
$orderBy$ $orderBy$
iban iban
@@ -226,6 +226,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.debitor',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-debitor-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-debitor-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.debitor', call rbac.generateRbacRestrictedView('hs_office.debitor',
$orderBy$ $orderBy$
defaultPrefix defaultPrefix
@@ -200,6 +200,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.sepamandate',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-sepamandate-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-sepamandate-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.sepamandate', call rbac.generateRbacRestrictedView('hs_office.sepamandate',
$orderBy$ $orderBy$
validity validity
@@ -182,6 +182,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.membership',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-membership-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-membership-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.membership', call rbac.generateRbacRestrictedView('hs_office.membership',
$orderBy$ $orderBy$
validity validity
@@ -155,6 +155,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.coopsharetx',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-coopsharetx-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-coopsharetx-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.coopsharetx', call rbac.generateRbacRestrictedView('hs_office.coopsharetx',
$orderBy$ $orderBy$
reference reference
@@ -155,6 +155,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.coopassettx',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-office-coopassettx-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-office-coopassettx-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_office.coopassettx', call rbac.generateRbacRestrictedView('hs_office.coopassettx',
$orderBy$ $orderBy$
reference reference
@@ -194,6 +194,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_booking.project',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-booking-project-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-booking-project-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_booking.project', call rbac.generateRbacRestrictedView('hs_booking.project',
$orderBy$ $orderBy$
caption caption
@@ -263,6 +263,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_booking.item',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-booking-item-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-booking-item-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_booking.item', call rbac.generateRbacRestrictedView('hs_booking.item',
$orderBy$ $orderBy$
validity validity
@@ -168,6 +168,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_hosting.asset',
-- ============================================================================ -- ============================================================================
--changeset RbacRestrictedViewGenerator:hs-hosting-asset-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// --changeset RbacRestrictedViewGenerator:hs-hosting-asset-rbac-RESTRICTED-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
call rbac.generateRbacRestrictedView('hs_hosting.asset', call rbac.generateRbacRestrictedView('hs_hosting.asset',
$orderBy$ $orderBy$
identifier identifier
@@ -0,0 +1,586 @@
--liquibase formatted sql
-- ============================================================================
--changeset michael.hoennig:hs-mass-test-data-GENERATORS context:!without-test-data endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Loop-based mass test-data generators for office/booking/hosting/accounts.
*/
create or replace procedure hs_office.contact_create_mass_test_data(
startCount integer,
endCount integer,
captionPrefix varchar default 'mass contact '
)
language plpgsql as $$
begin
for t in startCount..endCount
loop
call base.defineContext('mass contact test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
call hs_office.contact_create_test_data(captionPrefix || base.intToVarChar(t, 4));
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.person_create_mass_test_data(
startCount integer,
endCount integer
)
language plpgsql as $$
declare
idx varchar;
begin
for t in startCount..endCount
loop
call base.defineContext('mass person test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
if t % 5 = 0 then
call hs_office.person_create_test_data('NP', null, 'MassFamily' || idx, 'MassGiven' || idx, true);
else
call hs_office.person_create_test_data('LP', 'Mass Partner ' || idx || ' GmbH', null, null, true);
end if;
call hs_office.person_create_test_data('NP', null, 'MassRep' || idx, 'User' || idx, true);
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.relation_create_mass_test_data(
startCount integer,
endCount integer,
mandantTradeName varchar default 'Hostsharing eG',
contactCaptionPrefix varchar default 'mass contact '
)
language plpgsql as $$
declare
idx varchar;
partnerPersonName varchar;
representativeFamilyName varchar;
contactCaption varchar;
mandantPerson hs_office.person;
partnerPerson hs_office.person;
representativePerson hs_office.person;
contact hs_office.contact;
begin
select p.* into mandantPerson from hs_office.person p where p.tradeName = mandantTradeName;
if mandantPerson is null then
raise exception 'mandant "%" not found', mandantTradeName;
end if;
for t in startCount..endCount
loop
call base.defineContext('mass relation test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
partnerPersonName := case when t % 5 = 0 then 'MassFamily' || idx else 'Mass Partner ' || idx || ' GmbH' end;
representativeFamilyName := 'MassRep' || idx;
contactCaption := contactCaptionPrefix || idx;
select p.* into partnerPerson
from hs_office.person p
where p.tradeName = partnerPersonName or p.familyName = partnerPersonName;
select p.* into representativePerson from hs_office.person p where p.familyName = representativeFamilyName;
select c.* into contact from hs_office.contact c where c.caption = contactCaption;
if partnerPerson is null or representativePerson is null or contact is null then
raise exception 'missing mass test base data for index %', idx;
end if;
if not exists (
select 1 from hs_office.relation r
where r.type = 'PARTNER' and r.anchorUuid = mandantPerson.uuid and r.holderUuid = partnerPerson.uuid
) then
call hs_office.relation_create_test_data(partnerPersonName, 'PARTNER', mandantTradeName, contactCaption);
end if;
if not exists (
select 1 from hs_office.relation r
where r.type = 'REPRESENTATIVE' and r.anchorUuid = partnerPerson.uuid and r.holderUuid = representativePerson.uuid and r.contactUuid = contact.uuid
) then
call hs_office.relation_create_test_data(representativeFamilyName, 'REPRESENTATIVE', partnerPersonName, contactCaption);
end if;
if not exists (
select 1 from hs_office.relation r
where r.type = 'DEBITOR' and r.anchorUuid = partnerPerson.uuid and r.holderUuid = partnerPerson.uuid and r.contactUuid = contact.uuid
) then
call hs_office.relation_create_test_data(partnerPersonName, 'DEBITOR', partnerPersonName, contactCaption);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.partner_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5),
mandantTradeName varchar default 'Hostsharing eG',
contactCaptionPrefix varchar default 'mass contact '
)
language plpgsql as $$
declare
t integer;
idx varchar;
partnerPersonName varchar;
contactCaption varchar;
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass partner test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
partnerPersonName := case when t % 5 = 0 then 'MassFamily' || idx else 'Mass Partner ' || idx || ' GmbH' end;
contactCaption := contactCaptionPrefix || idx;
if not exists (select 1 from hs_office.partner p where p.partnerNumber = t) then
call hs_office.partner_create_test_data(mandantTradeName, t, partnerPersonName, contactCaption);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.bankaccount_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5)
)
language plpgsql as $$
declare
t integer;
idx varchar;
v_holder varchar;
v_iban varchar;
v_bic varchar;
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass bankaccount test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
v_holder := case when t % 5 = 0 then 'MassFamily' || idx else 'Mass Partner ' || idx || ' GmbH' end;
v_iban := 'DE' || lpad(t::text, 20, '0');
v_bic := 'MASSDEFF' || lpad((t % 1000)::text, 3, '0');
if not exists (
select 1 from hs_office.bankaccount b where b.holder = v_holder and b.iban = v_iban
) then
call hs_office.bankaccount_create_test_data(v_holder, v_iban, v_bic);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.debitor_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5),
contactCaptionPrefix varchar default 'mass contact '
)
language plpgsql as $$
declare
t integer;
idx varchar;
partnerPersonName varchar;
suffixNum integer;
suffixText char(2);
defaultPrefix char(3);
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass debitor test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
partnerPersonName := case when t % 5 = 0 then 'MassFamily' || idx else 'Mass Partner ' || idx || ' GmbH' end;
suffixNum := 10 + (t % 90);
suffixText := lpad(suffixNum::text, 2, '0');
defaultPrefix := lower(
chr(97 + ((t / 676) % 26)) ||
chr(97 + ((t / 26) % 26)) ||
chr(97 + (t % 26))
);
if not exists (
select 1
from hs_office.debitor d
join hs_office.relation debitorRel on debitorRel.uuid = d.debitorRelUuid and debitorRel.type = 'DEBITOR'
join hs_office.relation partnerRel on partnerRel.holderUuid = debitorRel.anchorUuid and partnerRel.type = 'PARTNER'
join hs_office.partner p on p.partnerRelUuid = partnerRel.uuid
where p.partnerNumber = t and d.debitorNumberSuffix = suffixText
) then
call hs_office.debitor_create_test_data(suffixNum, partnerPersonName, contactCaptionPrefix || idx, defaultPrefix);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.sepamandate_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5)
)
language plpgsql as $$
declare
t integer;
suffixText char(2);
iban varchar;
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass sepa-mandate test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
suffixText := lpad((10 + (t % 90))::text, 2, '0');
iban := 'DE' || lpad(t::text, 20, '0');
if not exists (
select 1
from hs_office.sepamandate sm
join hs_office.debitor d on d.uuid = sm.debitorUuid
join hs_office.relation debitorRel on debitorRel.uuid = d.debitorRelUuid
join hs_office.relation partnerRel on partnerRel.holderUuid = debitorRel.anchorUuid
join hs_office.partner p on p.partnerRelUuid = partnerRel.uuid
where p.partnerNumber = t and d.debitorNumberSuffix = suffixText
) then
call hs_office.sepamandate_create_test_data(t, suffixText, iban, 'mass-ref-' || t::text || '-' || suffixText);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.membership_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5),
withMembershipPercentage integer default 80
)
language plpgsql as $$
declare
t integer;
memberSuffix char(2);
begin
if withMembershipPercentage < 0 or withMembershipPercentage > 100 then
raise exception 'withMembershipPercentage must be between 0 and 100';
end if;
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass membership test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
if (t % 100) < withMembershipPercentage then
memberSuffix := lpad((10 + (t % 90))::text, 2, '0');
call hs_office.membership_create_test_data(t, memberSuffix, daterange('20221001', null, '[]'), 'ACTIVE');
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.coopsharetx_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5),
withMembershipPercentage integer default 80
)
language plpgsql as $$
declare
t integer;
memberSuffix char(2);
v_membershipUuid uuid;
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass coop-sharetx test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
if (t % 100) < withMembershipPercentage then
memberSuffix := lpad((10 + (t % 90))::text, 2, '0');
select m.uuid into v_membershipUuid
from hs_office.membership m
join hs_office.partner p on p.uuid = m.partnerUuid
where p.partnerNumber = t and m.memberNumberSuffix = memberSuffix;
if v_membershipUuid is not null and not exists (
select 1 from hs_office.coopsharetx tx where tx.membershipUuid = v_membershipUuid
) then
call hs_office.coopsharetx_create_test_data(t, memberSuffix);
end if;
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.coopassettx_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5),
withMembershipPercentage integer default 80
)
language plpgsql as $$
declare
t integer;
memberSuffix char(2);
v_membershipUuid uuid;
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass coop-assettx test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
if (t % 100) < withMembershipPercentage then
memberSuffix := lpad((10 + (t % 90))::text, 2, '0');
select m.uuid into v_membershipUuid
from hs_office.membership m
join hs_office.partner p on p.uuid = m.partnerUuid
where p.partnerNumber = t and m.memberNumberSuffix = memberSuffix;
if v_membershipUuid is not null and not exists (
select 1 from hs_office.coopassettx tx where tx.membershipUuid = v_membershipUuid
) then
call hs_office.coopassettx_create_test_data(t, memberSuffix);
end if;
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_booking.project_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5)
)
language plpgsql as $$
declare
t integer;
suffixText char(2);
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass booking-project test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
suffixText := lpad((10 + (t % 90))::text, 2, '0');
if not exists (
select 1 from hs_booking.project p where p.caption = 'D-' || t::text || suffixText || ' default project'
) then
call hs_booking.project_create_test_data(t, suffixText);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_booking.item_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5)
)
language plpgsql as $$
declare
t integer;
suffixText char(2);
v_projectUuid uuid;
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass booking-item test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
suffixText := lpad((10 + (t % 90))::text, 2, '0');
select p.uuid into v_projectUuid from hs_booking.project p where p.caption = 'D-' || t::text || suffixText || ' default project';
if v_projectUuid is not null and not exists (
select 1 from hs_booking.item i where i.projectUuid = v_projectUuid
) then
call hs_booking.item_create_test_data(t, suffixText);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_hosting.asset_create_mass_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5)
)
language plpgsql as $$
declare
t integer;
suffixText char(2);
projectCaption varchar;
v_debitorNumberSuffix char(2);
v_defaultPrefix char(3);
begin
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass hosting-asset test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
suffixText := lpad((10 + (t % 90))::text, 2, '0');
projectCaption := 'D-' || t::text || suffixText || ' default project';
select d.debitorNumberSuffix, d.defaultPrefix
into v_debitorNumberSuffix, v_defaultPrefix
from hs_booking.project p
join hs_office.debitor d on d.uuid = p.debitorUuid
where p.caption = projectCaption;
if v_debitorNumberSuffix is not null
and not exists (
select 1
from hs_hosting.asset a
join hs_booking.item i on i.uuid = a.bookingItemUuid
join hs_booking.project p on p.uuid = i.projectUuid
where p.caption = projectCaption
)
and not exists (
select 1 from hs_hosting.asset a
where (a.type = 'MANAGED_SERVER' and a.identifier = 'vm10' || v_debitorNumberSuffix)
or (a.type = 'CLOUD_SERVER' and a.identifier = 'vm20' || v_debitorNumberSuffix)
or (a.type = 'MANAGED_WEBSPACE' and a.identifier = v_defaultPrefix || '01')
or (a.type = 'MARIADB_INSTANCE' and a.identifier = 'vm10' || v_debitorNumberSuffix || '.MariaDB.default')
or (a.type = 'MARIADB_USER' and a.identifier = v_defaultPrefix || '01_web')
or (a.type = 'MARIADB_DATABASE' and a.identifier = v_defaultPrefix || '01_web')
or (a.type = 'PGSQL_INSTANCE' and a.identifier = 'vm10' || v_debitorNumberSuffix || '.Postgresql.default')
or (a.type = 'PGSQL_USER' and a.identifier = v_defaultPrefix || '01_web')
or (a.type = 'PGSQL_DATABASE' and a.identifier = v_defaultPrefix || '01_web')
or (a.type = 'EMAIL_ALIAS' and a.identifier = v_defaultPrefix || '01-web')
or (a.type = 'UNIX_USER' and a.identifier = v_defaultPrefix || '01-web')
or (a.type = 'UNIX_USER' and a.identifier = v_defaultPrefix || '01-mbox')
or (a.type = 'DOMAIN_SETUP' and a.identifier = v_defaultPrefix || '.example.org')
or (a.type = 'DOMAIN_DNS_SETUP' and a.identifier = v_defaultPrefix || '.example.org|DNS')
or (a.type = 'DOMAIN_HTTP_SETUP' and a.identifier = v_defaultPrefix || '.example.org|HTTP')
or (a.type = 'DOMAIN_SMTP_SETUP' and a.identifier = v_defaultPrefix || '.example.org|SMTP')
or (a.type = 'DOMAIN_MBOX_SETUP' and a.identifier = v_defaultPrefix || '.example.org|MBOX')
or (a.type = 'EMAIL_ADDRESS' and a.identifier = 'test@' || v_defaultPrefix || '.example.org')
) then
call hs_hosting.asset_create_test_data(projectCaption);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.person_create_mass_test_data_for_accounts(
startCount integer,
endCount integer
)
language plpgsql as $$
declare
t integer;
idx varchar;
begin
for t in startCount..endCount
loop
call base.defineContext('mass account-person test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
call hs_office.person_create_test_data('NP', null, 'MassAccountFamily' || idx, 'MassAccountGiven' || idx, true);
commit;
end loop;
end; $$;
--//
create or replace procedure hs_accounts.account_create_mass_test_data(
startCount integer,
endCount integer,
emailPrefix varchar default 'mass-account-',
uidOffset integer default 200000
)
language plpgsql as $$
declare
t integer;
idx varchar;
accountEmail varchar;
subjectUuid uuid;
personUuid uuid;
begin
for t in startCount..endCount
loop
call base.defineContext('mass profile test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
idx := base.intToVarChar(t, 4);
accountEmail := emailPrefix || idx || '@example.com';
select p.uuid into personUuid
from hs_office.person p
where p.familyName = 'MassAccountFamily' || idx and p.givenName = 'MassAccountGiven' || idx;
if personUuid is not null and not exists (
select 1 from hs_accounts.account pr where pr.person_uuid = personUuid
) then
perform rbac.create_subject(accountEmail);
select s.uuid into subjectUuid from rbac.subject s where s.name = accountEmail;
insert into hs_accounts.account (
uuid, version, person_uuid,
global_uid, global_gid
) values (
subjectUuid, 0, personUuid,
uidOffset + t, uidOffset + t
);
end if;
commit;
end loop;
end; $$;
--//
create or replace procedure hs_office.partner_create_mass_bundle_test_data(
startPartnerNumber numeric(5),
endPartnerNumber numeric(5),
withMembershipPercentage integer default 80
)
language plpgsql as $$
declare
t integer;
idx varchar;
personUuid uuid;
accountEmail varchar;
subjectUuid uuid;
begin
call base.defineContext('creating mass partner bundle test-data', null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
set constraints all deferred;
call hs_office.contact_create_mass_test_data(startPartnerNumber::integer, endPartnerNumber::integer);
call hs_office.person_create_mass_test_data(startPartnerNumber::integer, endPartnerNumber::integer);
call hs_office.relation_create_mass_test_data(startPartnerNumber::integer, endPartnerNumber::integer);
call hs_office.partner_create_mass_test_data(startPartnerNumber, endPartnerNumber);
call hs_office.bankaccount_create_mass_test_data(startPartnerNumber, endPartnerNumber);
call hs_office.debitor_create_mass_test_data(startPartnerNumber, endPartnerNumber);
call hs_office.sepamandate_create_mass_test_data(startPartnerNumber, endPartnerNumber);
call hs_office.membership_create_mass_test_data(startPartnerNumber, endPartnerNumber, withMembershipPercentage);
call hs_office.coopsharetx_create_mass_test_data(startPartnerNumber, endPartnerNumber, withMembershipPercentage);
call hs_office.coopassettx_create_mass_test_data(startPartnerNumber, endPartnerNumber, withMembershipPercentage);
call hs_booking.project_create_mass_test_data(startPartnerNumber, endPartnerNumber);
call hs_booking.item_create_mass_test_data(startPartnerNumber, endPartnerNumber);
call hs_hosting.asset_create_mass_test_data(startPartnerNumber, endPartnerNumber);
for t in startPartnerNumber::integer..endPartnerNumber::integer
loop
call base.defineContext('mass partner bundle account test-data #' || t, null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
if t % 5 = 0 then
idx := base.intToVarChar(t, 4);
select p.uuid into personUuid
from hs_office.person p
where p.familyName = 'MassFamily' || idx and p.givenName = 'MassGiven' || idx;
if personUuid is not null and not exists (
select 1 from hs_accounts.account pr where pr.person_uuid = personUuid
) then
accountEmail := 'mass-person-' || idx || '@example.com';
perform rbac.create_subject(accountEmail);
select s.uuid into subjectUuid from rbac.subject s where s.name = accountEmail;
insert into hs_accounts.account (
uuid, version, person_uuid,
global_uid, global_gid
) values (
subjectUuid, 0, personUuid,
300000 + t, 300000 + t
);
end if;
end if;
idx := base.intToVarChar(t, 4);
select p.uuid into personUuid
from hs_office.person p
where p.familyName = 'MassRep' || idx and p.givenName = 'User' || idx;
if personUuid is not null and not exists (
select 1 from hs_accounts.account pr where pr.person_uuid = personUuid
) then
accountEmail := 'mass-rep-' || idx || '@example.com';
perform rbac.create_subject(accountEmail);
select s.uuid into subjectUuid from rbac.subject s where s.name = accountEmail;
insert into hs_accounts.account (
uuid, version, person_uuid,
global_uid, global_gid
) values (
subjectUuid, 0, personUuid,
400000 + t, 400000 + t
);
end if;
commit;
end loop;
end; $$;
--//
@@ -0,0 +1,74 @@
--liquibase formatted sql
-- ============================================================================
--changeset michael.hoennig:hs-mass-test-data-PERFORMANCE context:!without-test-data endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure hs_office.bench_debitor_sepamandates(iterations int default 10)
language plpgsql
as $$
declare
i int;
t0 timestamptz;
ms numeric;
total_limit numeric := 0;
total_count numeric := 0;
min_limit numeric := null;
max_limit numeric := null;
min_count numeric := null;
max_count numeric := null;
rows_read bigint;
begin
for i in 1..iterations loop
call base.defineContext(
'query debitor',
null,
'superuser-alex@hostsharing.net');
t0 := clock_timestamp();
select count(*) into rows_read
from (
select d.defaultprefix, b.iban, s.validity
from hs_office.debitor d
join hs_office.sepamandate_rv s on s.debitoruuid = d.uuid
join hs_office.bankaccount b on b.uuid = s.bankaccountuuid
where d.defaultprefix like 'dq%'
limit 10
) x;
ms := extract(epoch from (clock_timestamp() - t0)) * 1000;
total_limit := total_limit + ms;
if min_limit is null or ms < min_limit then min_limit := ms; end if;
if max_limit is null or ms > max_limit then max_limit := ms; end if;
commit;
end loop;
for i in 1..iterations loop
call base.defineContext('query debitor',
null,
'superuser-alex@hostsharing.net');
t0 := clock_timestamp();
select count(*) into rows_read
from hs_office.debitor d
join hs_office.sepamandate_rv s on s.debitoruuid = d.uuid
join hs_office.bankaccount b on b.uuid = s.bankaccountuuid;
ms := extract(epoch from (clock_timestamp() - t0)) * 1000;
total_count := total_count + ms;
if min_count is null or ms < min_count then min_count := ms; end if;
if max_count is null or ms > max_count then max_count := ms; end if;
commit;
end loop;
raise notice 'limit10 min/avg/max: % ms / % ms / % ms',
round(min_limit, 3), round(total_limit / iterations, 3), round(max_limit, 3);
raise notice 'count all min/avg/max: % ms / % ms / % ms',
round(min_count, 3), round(total_count / iterations, 3), round(max_count, 3);
end $$;
--//
@@ -229,6 +229,12 @@ databaseChangeLog:
- include: - include:
file: db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql file: db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql
context: "!only-prod-schema and !without-test-data" context: "!only-prod-schema and !without-test-data"
- include:
file: db/changelog/9-hs-global/9520-hs-mass-test-data-generators.sql
context: "!only-prod-schema and !without-test-data"
- include:
file: db/changelog/9-hs-global/9521-has-mass-test-data-performance.sql
context: "!only-prod-schema and !without-test-data"
- include: - include:
file: db/changelog/9-hs-global/960-integrations/9600-hs-integration-schema.sql file: db/changelog/9-hs-global/960-integrations/9600-hs-integration-schema.sql
@@ -189,7 +189,7 @@ class ContextIntegrationTests {
@Test @Test
public void hasGlobalAdminRoleIsTrueForGlobalAdminWithAssumedRole() { public void hasGlobalAdminRoleIsTrueForGlobalAdminWithAssumedRole() {
final var hsGlobalAdminRole = jpaAttempt.transacted(() -> { final var hasGlobalAdminRole = jpaAttempt.transacted(() -> {
// given // given
context.define("superuser-alex@hostsharing.net", "rbactest.package#yyy00:ADMIN"); context.define("superuser-alex@hostsharing.net", "rbactest.package#yyy00:ADMIN");
@@ -199,6 +199,20 @@ class ContextIntegrationTests {
// when // when
// then
assertThat(hasGlobalAdminRole.returnedValue()).isFalse();
}
@Test
public void hasGlobalAdminRoleIsFalseForGlobalAdminWithAssumedGlobalAdminRole() {
final var hsGlobalAdminRole = jpaAttempt.transacted(() -> {
// given
context.define("superuser-alex@hostsharing.net", "rbac.global#global:ADMIN");
// when
return (boolean) em.createNativeQuery("select rbac.hasGlobalAdminRole()").getSingleResult();
});
// then // then
assertThat(hsGlobalAdminRole.returnedValue()).isFalse(); assertThat(hsGlobalAdminRole.returnedValue()).isFalse();
} }