diff --git a/repo/repo-sqale/sql/pgnew-repo.sql b/repo/repo-sqale/sql/pgnew-repo.sql index 2b3564e3a28..5c0309da3ed 100644 --- a/repo/repo-sqale/sql/pgnew-repo.sql +++ b/repo/repo-sqale/sql/pgnew-repo.sql @@ -597,22 +597,30 @@ CREATE INDEX m_ref_object_parent_org_targetOid_relation_id_idx ON m_ref_object_parent_org (targetOid, relation_id); -- region org-closure --- Trigger on m_ref_object_parent_org refreshes this view. --- This is not most performant, but it is *correct* and it's still WIP. - +/* +Trigger on m_ref_object_parent_org refreshes this view. +This is not most performant, but it is *correct* and it's still WIP. +Closure contains also identity (org = org) entries because: +* It's easier to do optimized matrix-multiplication based refresh with them later. +* It actually makes some query easier and requires AND instead of OR conditions. +* While the table shows that o => o (=> means "is parent of"), this is not the semantics +of isParent/ChildOf searches and they never return parameter OID as a result. +*/ CREATE MATERIALIZED VIEW m_org_closure AS WITH RECURSIVE org_h ( ancestor_oid, -- ref.targetoid descendant_oid --ref.owner_oid -- paths -- number of different paths, not used for materialized view version - -- TODO depth? if so, cycles must be checked in recursive term + -- depth -- possible later, but cycle detected must be added to the recursive term ) AS ( - -- gather all organizations with parents - SELECT r.targetoid, r.owner_oid - FROM m_ref_object_parent_org r - WHERE r.owner_type = 'ORG' + -- non-recursive term: + -- Gather all organization oids and initialize identity lines (o => o). + SELECT o.oid, o.oid FROM m_org o + -- It's possible to exclude orgs not in parent-org-refs (either owner or target!), + -- but it's not a big deal and makes things simple for JOINs (no outer needed). UNION - -- generate their parents + -- recursive (iterative) term: + -- Generate their parents (anc => desc, that is target => owner), => means "is parent of". SELECT par.targetoid, chi.descendant_oid -- leaving original child there generates closure FROM m_ref_object_parent_org as par, org_h as chi WHERE par.owner_oid = chi.ancestor_oid diff --git a/repo/repo-sqale/src/main/java/com/evolveum/midpoint/repo/sqale/filtering/OrgFilterProcessor.java b/repo/repo-sqale/src/main/java/com/evolveum/midpoint/repo/sqale/filtering/OrgFilterProcessor.java index d28438c5308..d2ddf13949c 100644 --- a/repo/repo-sqale/src/main/java/com/evolveum/midpoint/repo/sqale/filtering/OrgFilterProcessor.java +++ b/repo/repo-sqale/src/main/java/com/evolveum/midpoint/repo/sqale/filtering/OrgFilterProcessor.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.repo.sqale.SqaleQueryContext; import com.evolveum.midpoint.repo.sqale.qmodel.object.MObject; import com.evolveum.midpoint.repo.sqale.qmodel.object.QObject; +import com.evolveum.midpoint.repo.sqale.qmodel.org.QOrgClosure; import com.evolveum.midpoint.repo.sqale.qmodel.ref.QObjectReference; import com.evolveum.midpoint.repo.sqale.qmodel.ref.QObjectReferenceMapping; import com.evolveum.midpoint.repo.sqlbase.QueryException; @@ -45,8 +46,7 @@ public Predicate process(OrgFilter filter) throws QueryException { QObject objectPath = (QObject) path; if (filter.isRoot()) { QObjectReference ref = getNewRefAlias(); - return new SQLQuery<>().select(Expressions.constant(1)) - .from(ref) + return subQuery(ref) .where(ref.ownerOid.eq(objectPath.oid)) .notExists(); } @@ -68,8 +68,7 @@ public Predicate process(OrgFilter filter) throws QueryException { if (filter.getScope() == OrgFilter.Scope.ONE_LEVEL) { QObjectReference ref = getNewRefAlias(); - SQLQuery subQuery = new SQLQuery<>().select(Expressions.constant(1)) - .from(ref) + SQLQuery subQuery = subQuery(ref) .where(ref.ownerOid.eq(objectPath.oid) .and(ref.targetOid.eq(UUID.fromString(oidParam)))); if (relationId != null) { @@ -77,8 +76,16 @@ public Predicate process(OrgFilter filter) throws QueryException { } return subQuery.exists(); } else if (filter.getScope() == OrgFilter.Scope.SUBTREE) { - throw new UnsupportedOperationException(); - // TODO + QObjectReference ref = getNewRefAlias(); + QOrgClosure oc = getNewClosureAlias(); + SQLQuery subQuery = subQuery(ref) + .join(oc).on(oc.descendantOid.eq(ref.targetOid)) + .where(ref.ownerOid.eq(objectPath.oid) + .and(oc.ancestorOid.eq(UUID.fromString(oidParam)))); + if (relationId != null) { + subQuery.where(ref.relationId.eq(relationId)); + } + return subQuery.exists(); } else if (filter.getScope() == OrgFilter.Scope.ANCESTORS) { throw new UnsupportedOperationException(); // TODO @@ -87,10 +94,21 @@ public Predicate process(OrgFilter filter) throws QueryException { } } + private SQLQuery subQuery(FlexibleRelationalPathBase entityPath) { + return new SQLQuery<>().select(Expressions.constant(1)) + .from(entityPath); + } + private QObjectReference getNewRefAlias() { - var refMapping = QObjectReferenceMapping.getForParentOrg(); + QObjectReferenceMapping, MObject> refMapping = + QObjectReferenceMapping.getForParentOrg(); QObjectReference ref = refMapping.newAlias( context.uniqueAliasName(refMapping.defaultAliasName())); return ref; } + + private QOrgClosure getNewClosureAlias() { + return new QOrgClosure( + context.uniqueAliasName(QOrgClosure.DEFAULT_ALIAS_NAME)); + } } diff --git a/repo/repo-sqale/src/test/java/com/evolveum/midpoint/repo/sqale/func/SqaleRepoSearchObjectTest.java b/repo/repo-sqale/src/test/java/com/evolveum/midpoint/repo/sqale/func/SqaleRepoSearchObjectTest.java index 93bed45acc4..ea632acd784 100644 --- a/repo/repo-sqale/src/test/java/com/evolveum/midpoint/repo/sqale/func/SqaleRepoSearchObjectTest.java +++ b/repo/repo-sqale/src/test/java/com/evolveum/midpoint/repo/sqale/func/SqaleRepoSearchObjectTest.java @@ -44,8 +44,10 @@ public class SqaleRepoSearchObjectTest extends SqaleRepoBaseTest { private String org21Oid; private String orgXOid; // under two orgs - private String user1Oid; // typical object + private String user1Oid; // user without org private String user2Oid; // different user, this one is in org + private String user3Oid; // another user in org + private String user4Oid; // another user in org private String task1Oid; // task has more attribute type variability private String shadow1Oid; // ditto private String service1Oid; // object with integer attribute @@ -89,6 +91,7 @@ public void initObjects() throws Exception { null, result); org21Oid = repositoryService.addObject( new OrgType(prismContext).name("org-2-1") + .costCenter("5") .parentOrgRef(org2Oid, OrgType.COMPLEX_TYPE) .asPrismObject(), null, result); @@ -117,6 +120,19 @@ public void initObjects() throws Exception { .parentOrgRef(org11Oid, OrgType.COMPLEX_TYPE, relation1) .asPrismObject(), null, result); + user3Oid = repositoryService.addObject( + new UserType(prismContext).name("user-3") + .costCenter("50") + .parentOrgRef(orgXOid, OrgType.COMPLEX_TYPE) + .parentOrgRef(org21Oid, OrgType.COMPLEX_TYPE, relation1) + .asPrismObject(), + null, result); + user4Oid = repositoryService.addObject( + new UserType(prismContext).name("user-4") + .costCenter("51") + .parentOrgRef(org111Oid, OrgType.COMPLEX_TYPE) + .asPrismObject(), + null, result); task1Oid = repositoryService.addObject( new TaskType(prismContext).name("task-1").asPrismObject(), null, result); @@ -258,6 +274,60 @@ public void test212QueryForDirectChildrenOfAnyTypeWithRelation() throws SchemaEx .containsExactlyInAnyOrder(org112Oid, user2Oid); } + @Test + public void test215QueryForChildrenOfAnyType() throws SchemaException { + when("searching objects anywhere under an org"); + OperationResult operationResult = createOperationResult(); + SearchResultList result = searchObjects(ObjectType.class, + prismContext.queryFor(ObjectType.class) + .isChildOf(org2Oid) + .build(), + operationResult); + + then("all objects under the specified organization are returned"); + assertThatOperationResult(operationResult).isSuccess(); + assertThat(result).hasSize(4) + .extracting(o -> o.getOid()) + .containsExactlyInAnyOrder(org21Oid, orgXOid, user2Oid, user3Oid); + } + + @Test + public void test216QueryForChildrenOfAnyTypeWithRelation() throws SchemaException { + when("searching objects anywhere under an org with specific relation"); + OperationResult operationResult = createOperationResult(); + SearchResultList result = searchObjects(ObjectType.class, + prismContext.queryFor(ObjectType.class) + .isChildOf(prismContext.itemFactory() + .createReferenceValue(org2Oid).relation(relation1)) + .build(), + operationResult); + + then("all objects under the specified organization with specified relation are returned"); + assertThatOperationResult(operationResult).isSuccess(); + assertThat(result).hasSize(1) + .extracting(o -> o.getOid()) + .containsExactlyInAnyOrder(user3Oid); + // user-2 has another parent link with relation1, but not under org-2 + } + + @Test + public void test230QueryForChildrenOfAnyTypeWithAnotherCondition() throws SchemaException { + when("searching objects anywhere under an org"); + OperationResult operationResult = createOperationResult(); + SearchResultList result = searchObjects(FocusType.class, + prismContext.queryFor(FocusType.class) + .isChildOf(org2Oid) + .and().item(FocusType.F_COST_CENTER).startsWith("5") + .build(), + operationResult); + + then("all objects under the specified organization matching other conditions are returned"); + assertThatOperationResult(operationResult).isSuccess(); + assertThat(result).hasSize(2) + .extracting(o -> o.getOid()) + .containsExactlyInAnyOrder(org21Oid, user3Oid); + } + // TODO child/parent tests // endregion