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:
@@ -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; $$;
|
||||
|
||||
-- ============================================================================
|
||||
--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.
|
||||
@@ -98,6 +98,7 @@ create or replace procedure base.contextDefined(
|
||||
language plpgsql as $$
|
||||
declare
|
||||
currentSubjectUuid uuid;
|
||||
currentSubjectHasGlobalAdminRole boolean;
|
||||
begin
|
||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||
|
||||
@@ -111,6 +112,13 @@ begin
|
||||
execute format('set local hsadminng.currentSubjectOrAssumedRolesUuids to %L',
|
||||
(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;
|
||||
end; $$;
|
||||
|
||||
@@ -181,4 +189,3 @@ begin
|
||||
return string_to_array(currentSubjectOrAssumedRolesUuids, ';');
|
||||
end; $$;
|
||||
--//
|
||||
|
||||
|
||||
@@ -179,8 +179,10 @@ create or replace procedure rbac.generateRbacRestrictedView(targetTable text, or
|
||||
declare
|
||||
sql text;
|
||||
newColumns text;
|
||||
functionName text;
|
||||
begin
|
||||
targetTable := lower(targetTable);
|
||||
functionName := replace(targetTable, '.', '_');
|
||||
if columnNames = '*' then
|
||||
columnNames := base.tableColumnNames(targetTable);
|
||||
end if;
|
||||
@@ -189,7 +191,18 @@ begin
|
||||
Creates a restricted view based on the 'SELECT' permission of the current subject.
|
||||
*/
|
||||
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 recursive
|
||||
recursive_grants as
|
||||
@@ -223,11 +236,17 @@ begin
|
||||
)
|
||||
select 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;
|
||||
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};
|
||||
$sql$, targetTable, orderBy);
|
||||
$sql$, targetTable, orderBy, functionName);
|
||||
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()
|
||||
returns boolean
|
||||
language plpgsql as $$
|
||||
declare
|
||||
isGlobalAdmin text;
|
||||
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; $$;
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--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.
|
||||
|
||||
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()
|
||||
returns boolean
|
||||
|
||||
+1
@@ -167,6 +167,7 @@ call rbac.generateRbacIdentityViewFromProjection('rbactest.customer',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
reference
|
||||
|
||||
+1
@@ -232,6 +232,7 @@ call rbac.generateRbacIdentityViewFromProjection('rbactest.package',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
name
|
||||
|
||||
+1
@@ -231,6 +231,7 @@ call rbac.generateRbacIdentityViewFromProjection('rbactest.domain',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
name
|
||||
|
||||
+1
@@ -90,6 +90,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.contact',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
caption
|
||||
|
||||
@@ -90,6 +90,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.person',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
concat(tradeName, familyName, givenName)
|
||||
|
||||
+1
@@ -245,6 +245,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.relation',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
(select idName from hs_office.person_iv p where p.uuid = target.holderUuid)
|
||||
|
||||
+1
@@ -244,6 +244,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.partner',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
'P-' || partnerNumber
|
||||
|
||||
+1
@@ -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:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- trigger change of change in generateRbacRestrictedView regarding #453 optimization for global:ADMIN
|
||||
call rbac.generateRbacRestrictedView('hs_office.partner_details',
|
||||
$orderBy$
|
||||
uuid
|
||||
|
||||
+1
@@ -90,6 +90,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.bankaccount',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
iban
|
||||
|
||||
+1
@@ -226,6 +226,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.debitor',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
defaultPrefix
|
||||
|
||||
+1
@@ -200,6 +200,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.sepamandate',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
validity
|
||||
|
||||
+1
@@ -182,6 +182,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_office.membership',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
validity
|
||||
|
||||
+1
@@ -155,6 +155,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.coopsharetx',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
reference
|
||||
|
||||
+1
@@ -155,6 +155,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_office.coopassettx',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
reference
|
||||
|
||||
+1
@@ -194,6 +194,7 @@ call rbac.generateRbacIdentityViewFromQuery('hs_booking.project',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
caption
|
||||
|
||||
+1
@@ -263,6 +263,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_booking.item',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
validity
|
||||
|
||||
+1
@@ -168,6 +168,7 @@ call rbac.generateRbacIdentityViewFromProjection('hs_hosting.asset',
|
||||
-- ============================================================================
|
||||
--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',
|
||||
$orderBy$
|
||||
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:
|
||||
file: db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql
|
||||
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:
|
||||
file: db/changelog/9-hs-global/960-integrations/9600-hs-integration-schema.sql
|
||||
|
||||
@@ -189,7 +189,7 @@ class ContextIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void hasGlobalAdminRoleIsTrueForGlobalAdminWithAssumedRole() {
|
||||
final var hsGlobalAdminRole = jpaAttempt.transacted(() -> {
|
||||
final var hasGlobalAdminRole = jpaAttempt.transacted(() -> {
|
||||
// given
|
||||
context.define("superuser-alex@hostsharing.net", "rbactest.package#yyy00:ADMIN");
|
||||
|
||||
@@ -199,6 +199,20 @@ class ContextIntegrationTests {
|
||||
|
||||
// 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
|
||||
assertThat(hsGlobalAdminRole.returnedValue()).isFalse();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user