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
@@ -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,45 +191,62 @@ begin
Creates a restricted view based on the 'SELECT' permission of the current subject.
*/
sql := format($sql$
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
(select distinct rbac.grant.descendantuuid,
rbac.grant.ascendantuuid,
1 as level,
true
from rbac.grant
where rbac.grant.assumed
and (rbac.grant.ascendantuuid = any (rbac.currentSubjectOrAssumedRolesUuids()))
union all
select distinct g.descendantuuid,
g.ascendantuuid,
grants.level + 1 as level,
base.assertTrue(grants.level < 22, 'too many grant-levels: ' || grants.level)
from rbac.grant g
join recursive_grants grants on grants.descendantuuid = g.ascendantuuid
where g.assumed),
grant_count AS (
SELECT COUNT(*) AS grant_count FROM recursive_grants
),
count_check as (select base.assertTrue((select count(*) as grant_count from recursive_grants) < 400000,
'too many grants for current subjects: ' || (select count(*) as grant_count from recursive_grants))
as valid)
select distinct perm.objectuuid
from recursive_grants
join rbac.permission perm on recursive_grants.descendantuuid = perm.uuid
join rbac.object obj on obj.uuid = perm.objectuuid
join count_check cc on cc.valid
where obj.objectTable = '%1$s' -- 'SELECT' permission is included in all other permissions
)
select target.*
from %1$s as target
where target.uuid in (select * from accessible_uuids)
order by %2$s;
end if;
end;
$f$;
create or replace view %1$s_rv as
with accessible_uuids as (
with recursive
recursive_grants as
(select distinct rbac.grant.descendantuuid,
rbac.grant.ascendantuuid,
1 as level,
true
from rbac.grant
where rbac.grant.assumed
and (rbac.grant.ascendantuuid = any (rbac.currentSubjectOrAssumedRolesUuids()))
union all
select distinct g.descendantuuid,
g.ascendantuuid,
grants.level + 1 as level,
base.assertTrue(grants.level < 22, 'too many grant-levels: ' || grants.level)
from rbac.grant g
join recursive_grants grants on grants.descendantuuid = g.ascendantuuid
where g.assumed),
grant_count AS (
SELECT COUNT(*) AS grant_count FROM recursive_grants
),
count_check as (select base.assertTrue((select count(*) as grant_count from recursive_grants) < 400000,
'too many grants for current subjects: ' || (select count(*) as grant_count from recursive_grants))
as valid)
select distinct perm.objectuuid
from recursive_grants
join rbac.permission perm on recursive_grants.descendantuuid = perm.uuid
join rbac.object obj on obj.uuid = perm.objectuuid
join count_check cc on cc.valid
where obj.objectTable = '%1$s' -- 'SELECT' permission is included in all other permissions
)
select target.*
from %1$s as target
where rbac.hasGlobalAdminRole() or target.uuid in (select * from accessible_uuids)
order by %2$s;
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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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