diff --git a/app/genres/fiori-service.cds b/app/genres/fiori-service.cds index d195d935..77b132a5 100644 --- a/app/genres/fiori-service.cds +++ b/app/genres/fiori-service.cds @@ -7,8 +7,8 @@ using AdminService from '../../srv/admin-service'; annotate AdminService.GenreHierarchy with @Aggregation.RecursiveHierarchy#GenreHierarchy: { $Type: 'Aggregation.RecursiveHierarchyType', - NodeProperty: node_id, // identifies a node - ParentNavigationProperty: parent // navigates to a node's parent + NodeProperty: ID, // identifies a node + ParentNavigationProperty: parnt // navigates to a node's parent }; annotate AdminService.GenreHierarchy with @Hierarchy.RecursiveHierarchy#GenreHierarchy: { diff --git a/db/books.cds b/db/books.cds index a4d8ba44..67f9fd88 100644 --- a/db/books.cds +++ b/db/books.cds @@ -47,9 +47,9 @@ annotate Authors with */ entity Genres { key ID : Integer; - node : Integer not null; - parent_node : Integer default 0; name : localized String(255); descr : localized String(1000); - parent : Association to one Genres on parent.node = parent_node; + parnt : Association to Genres; + children : Composition of many Genres + on children.parnt = $self; } diff --git a/db/data/my.bookshop-Genres.csv b/db/data/my.bookshop-Genres.csv index 044b2b47..f498be2d 100644 --- a/db/data/my.bookshop-Genres.csv +++ b/db/data/my.bookshop-Genres.csv @@ -1,16 +1,16 @@ -ID;parent_node;name;node -10;;Fiction;10 -11;10;Drama;11 -12;10;Poetry;12 -13;10;Fantasy;13 -14;10;Science Fiction;14 -15;10;Romance;15 -16;10;Mystery;16 -17;10;Thriller;17 -18;10;Dystopia;18 -19;10;Fairy Tale;19 -20;;Non-Fiction;20 -21;20;Biography;21 -22;21;Autobiography;22 -23;20;Essay;23 -24;20;Speech;24 \ No newline at end of file +ID;parnt_ID;name +10;;Fiction +11;10;Drama +12;10;Poetry +13;10;Fantasy +14;10;Science Fiction +15;10;Romance +16;10;Mystery +17;10;Thriller +18;10;Dystopia +19;10;Fairy Tale +20;;Non-Fiction +21;20;Biography +22;21;Autobiography +23;20;Essay +24;20;Speech \ No newline at end of file diff --git a/pom.xml b/pom.xml index 98a241b6..9dac44cf 100644 --- a/pom.xml +++ b/pom.xml @@ -23,11 +23,11 @@ 21 - 3.4.0 + 3.5.0 5.13.0 3.5.3 3.8.4 - 8.4.1 + 8.5.0 diff --git a/srv/admin-service.cds b/srv/admin-service.cds index 7709ebd0..32fb80e8 100644 --- a/srv/admin-service.cds +++ b/srv/admin-service.cds @@ -16,11 +16,7 @@ service AdminService @(requires : 'admin') { entity Orders as select from my.Orders; extend my.Genres with Hierarchy; - entity GenreHierarchy as projection on my.Genres { - node as node_id, - parent_node as parent_id, - * - } excluding { node, parent_node } + entity GenreHierarchy as projection on my.Genres; @cds.persistence.skip entity Upload @odata.singleton { diff --git a/srv/src/main/java/my/bookshop/handlers/HierarchyHandler.java b/srv/src/main/java/my/bookshop/handlers/HierarchyHandler.java index 6780d8d3..d9ea0fa4 100644 --- a/srv/src/main/java/my/bookshop/handlers/HierarchyHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/HierarchyHandler.java @@ -39,6 +39,8 @@ import cds.gen.adminservice.GenreHierarchy; import cds.gen.adminservice.GenreHierarchy_; +import static cds.gen.adminservice.AdminService_.GENRE_HIERARCHY; + @Component @Profile("default") @ServiceName(AdminService_.CDS_NAME) @@ -47,8 +49,8 @@ public class HierarchyHandler implements EventHandler { private final PersistenceService db; HierarchyHandler(PersistenceService db) { - this.db = db; - } + this.db = db; + } @Before(event = CqnService.EVENT_READ, entity = GenreHierarchy_.CDS_NAME) public void readGenreHierarchy(CdsReadEventContext event) { @@ -58,12 +60,12 @@ public void readGenreHierarchy(CdsReadEventContext event) { if (trafos.size() < 1) { return; } - + if (getTopLevels(trafos) instanceof CqnTopLevelsTransformation topLevels) { result = topLevels(topLevels, CQL.TRUE); } else if (trafos.get(0) instanceof CqnDescendantsTransformation descendants) { result = handleDescendants(descendants); - } else if (trafos.get(0) instanceof CqnAncestorsTransformation ancestors) { + } else if (trafos.get(0) instanceof CqnAncestorsTransformation ancestors) { if (trafos.size() == 2 && trafos.get(1) instanceof CqnTopLevelsTransformation topLevels) { result = handleAncestors(ancestors, topLevels); } else if (trafos.size() == 3 && trafos.get(2) instanceof CqnTopLevelsTransformation topLevels) { @@ -77,7 +79,8 @@ public void readGenreHierarchy(CdsReadEventContext event) { private CqnTopLevelsTransformation getTopLevels(List trafos) { if (trafos.get(0) instanceof CqnTopLevelsTransformation topLevels) { return topLevels; - } else if (trafos.size() == 2 && trafos.get(0) instanceof CqnOrderByTransformation && trafos.get(1) instanceof CqnTopLevelsTransformation topLevels) { + } else if (trafos.size() == 2 && trafos.get(0) instanceof CqnOrderByTransformation + && trafos.get(1) instanceof CqnTopLevelsTransformation topLevels) { return topLevels; } return null; @@ -92,16 +95,17 @@ private void setResult(CdsReadEventContext event, List result) { } private void addDrillState(List ghs) { - List ids = ghs.stream().map(gh -> gh.getNodeId()).toList(); - Set parents = ghs.stream().map(gh -> gh.getParentId()).filter(p -> p != 0).collect(Collectors.toSet()); - CqnSelect q = Select.from(AdminService_.GENRE_HIERARCHY, gh -> gh.parent()).columns(gh -> gh.node_id()) - .where(gh -> gh.node_id().in(ids)); + List ids = ghs.stream().map(gh -> gh.getId()).toList(); + Set parents = ghs.stream().map(gh -> gh.getParntId()).filter(p -> p != null) + .collect(Collectors.toSet()); + CqnSelect q = Select.from(GENRE_HIERARCHY).columns(gh -> gh.parnt_ID().as("id")) + .where(gh -> gh.parnt_ID().in(ids)); Set nonLeafs = db .run(q) - .stream().map(r -> r.get(GenreHierarchy.NODE_ID)).collect(Collectors.toSet()); + .stream().map(r -> r.get("id")).collect(Collectors.toSet()); for (GenreHierarchy gh : ghs) { - Integer id = gh.getNodeId(); + Integer id = gh.getId(); if (nonLeafs.contains(id)) { if (parents.contains(id)) { gh.setDrillState("expanded"); @@ -111,42 +115,43 @@ private void addDrillState(List ghs) { } else { gh.setDrillState("leaf"); } - } + } } - - private List handleDescendants(CqnDescendantsTransformation descendants) { - Map lookup = new HashMap<>(); - CqnFilterTransformation filter = (CqnFilterTransformation) descendants.transformations().get(0); - CqnSelect getRoot = Select.from(AdminService_.GENRE_HIERARCHY).where(filter.filter()); - GenreHierarchy root = db.run(getRoot).single(GenreHierarchy.class); - lookup.put(root.getNodeId(), root); - CqnPredicate parentFilter = CQL.copy(filter.filter(), new Modifier() { + private CqnPredicate descendantsFilter(CqnDescendantsTransformation descendants) { + CqnTransformation trafo = descendants.transformations().get(0); + CqnPredicate start = ((CqnFilterTransformation) trafo).filter(); + CqnPredicate result = CQL.FALSE; + if (descendants.keepStart()) { + result = CQL.or(result, start); + } + CqnPredicate children = CQL.copy(start, new Modifier() { @Override public CqnValue ref(CqnElementRef ref) { - return CQL.get(GenreHierarchy.PARENT_ID); + return CQL.get(GenreHierarchy.PARNT_ID); } }); + result = CQL.or(result, children); - CqnSelect childrenCQN = Select.from(AdminService_.GENRE_HIERARCHY).where(parentFilter); - List children = db.run(childrenCQN).listOf(GenreHierarchy.class); - children.forEach(gh -> lookup.put(gh.getNodeId(), gh)); - children.forEach(gh -> gh.setParent(lookup.get(gh.getParentId()))); - - return children.stream().sorted(new Sorter()).toList(); + return result; } - private List handleAncestors(CqnAncestorsTransformation ancestors, CqnTopLevelsTransformation topLevels) { + private CqnPredicate ancestorsFilter(CqnAncestorsTransformation ancestors) { CqnTransformation trafo = ancestors.transformations().get(0); - Select inner = Select.from(AdminService_.GENRE_HIERARCHY).columns(gh -> gh.node_id()); + Select inner = Select.from(GENRE_HIERARCHY).columns(gh -> gh.ID()); if (trafo instanceof CqnFilterTransformation filter) { inner.where(filter.filter()); } else if (trafo instanceof CqnSearchTransformation search) { inner.search(search.search()); } - Select outer = Select.from(AdminService_.GENRE_HIERARCHY).columns(gh -> gh.node_id().as("i0"), gh -> gh.parent().node_id().as("i1"), - gh -> gh.parent().parent().node_id().as("i2"), gh -> gh.parent().parent().parent().node_id().as("i3"), - gh -> gh.parent().parent().parent().parent().node_id().as("i4")).where(gh -> gh.node_id().in(inner)); + + Select outer = Select.from(GENRE_HIERARCHY) + .columns(gh -> gh.ID().as("i0"), + gh -> gh.parnt().ID().as("i1"), + gh -> gh.parnt().parnt().ID().as("i2"), + gh -> gh.parnt().parnt().parnt().ID().as("i3"), + gh -> gh.parnt().parnt().parnt().parnt().ID().as("i4")) + .where(gh -> gh.ID().in(inner)); Set ancestorIds = new HashSet<>(); db.run(outer).stream().forEach(r -> { @@ -157,7 +162,30 @@ private List handleAncestors(CqnAncestorsTransformation ancestor addIfNotNull(ancestorIds, r, "i4"); }); - CqnPredicate filter = CQL.get("node_id").in(ancestorIds.stream().toList()); + return CQL.get(GenreHierarchy_.ID).in(ancestorIds.stream().toList()); + } + + private List handleDescendants(CqnDescendantsTransformation descendants) { + CqnPredicate filter = descendantsFilter(descendants); + CqnSelect childrenCQN = Select.from(GENRE_HIERARCHY).where(filter); + List nodes = db.run(childrenCQN).listOf(GenreHierarchy.class); + + connect(nodes); + + return nodes.stream().sorted(new Sorter()).toList(); + } + + private static void connect(List nodes) { + Map lookup = new HashMap<>(); + nodes.forEach(gh -> lookup.put(gh.getId(), gh)); + nodes.forEach(gh -> gh.setParnt(lookup.get(gh.getParntId()))); + nodes.forEach(gh -> gh.setDistanceFromRoot(distanceFromRoot(gh))); + } + + private List handleAncestors(CqnAncestorsTransformation ancestors, + CqnTopLevelsTransformation topLevels) { + CqnPredicate filter = ancestorsFilter(ancestors); + return topLevels(topLevels, filter); } @@ -166,7 +194,7 @@ private void addIfNotNull(Set ancestorIds, Row r, String key) { if (id != null) { ancestorIds.add(id); } - } + } private List topLevels(CqnTopLevelsTransformation topLevels, CqnPredicate filter) { return topLevels.levels() < 0 ? topLevelsAll(filter) : topLevelsLimit(topLevels, filter); @@ -177,25 +205,26 @@ private List topLevelsLimit(CqnTopLevelsTransformation topLevels Map lookup = new HashMap<>(); Map expandLevels = topLevels.expandLevels(); - CqnSelect getRoots = Select.from(AdminService_.GENRE_HIERARCHY).where(gh -> gh.parent_id().eq(0).and(filter)); + CqnSelect getRoots = Select.from(GENRE_HIERARCHY).where(gh -> gh.parnt_ID().isNull().and(filter)); List roots = db.run(getRoots).listOf(GenreHierarchy.class); roots.forEach(root -> { root.setDistanceFromRoot(0l); - lookup.put(root.getNodeId(), root); - List parents = List.of(root.getNodeId()); + lookup.put(root.getId(), root); + List parents = List.of(root.getId()); for (long i = 1; i < limit; i++) { List ps = parents; - CqnSelect getChildren = Select.from(AdminService_.GENRE_HIERARCHY).where(gh -> gh.parent_id().in(ps).and(filter)); + CqnSelect getChildren = Select.from(GENRE_HIERARCHY) + .where(gh -> gh.parnt_ID().in(ps).and(filter)); List children = db.run(getChildren).listOf(GenreHierarchy.class); if (children.isEmpty()) { break; } long dfr = i; parents = children.stream().peek(gh -> { - gh.setParent(lookup.get(gh.getParentId())); + gh.setParnt(lookup.get(gh.getParntId())); gh.setDistanceFromRoot(dfr); - lookup.put(gh.getNodeId(), gh); - }).map(GenreHierarchy::getNodeId).toList(); + lookup.put(gh.getId(), gh); + }).map(GenreHierarchy::getId).toList(); } }); @@ -203,14 +232,14 @@ private List topLevelsLimit(CqnTopLevelsTransformation topLevels List expandedIds = expandLevels.keySet().stream().map(key -> (Integer) key).toList(); CqnSelect expandedCQN = Select.from(AdminService_.GENRE_HIERARCHY).where(gh -> CQL.and(filter, - CQL.or(gh.node_id().in(expandedIds), gh.parent_id().in(expandedIds)))); + CQL.or(gh.ID().in(expandedIds), gh.parnt_ID().in(expandedIds)))); List expanded = db.run(expandedCQN).listOf(GenreHierarchy.class); expanded.forEach(gh -> { - if (!lookup.keySet().contains(gh.getNodeId())) { - gh.setParent(lookup.get(gh.getParentId())); + if (!lookup.keySet().contains(gh.getId())) { + gh.setParnt(lookup.get(gh.getParntId())); gh.setDistanceFromRoot(distanceFromRoot(gh)); - lookup.put(gh.getNodeId(), gh); + lookup.put(gh.getId(), gh); } }); @@ -220,22 +249,19 @@ private List topLevelsLimit(CqnTopLevelsTransformation topLevels } private List topLevelsAll(CqnPredicate filter) { - Map lookup = new HashMap<>(); - - CqnSelect allCqn = Select.from(AdminService_.GENRE_HIERARCHY).where(filter); + CqnSelect allCqn = Select.from(GENRE_HIERARCHY).where(filter); var all = db.run(allCqn).listOf(GenreHierarchy.class); - all.forEach(gh -> lookup.put(gh.getNodeId(), gh)); - all.forEach(gh -> gh.setParent(lookup.get(gh.getParentId()))); - all.forEach(gh -> gh.setDistanceFromRoot(distanceFromRoot(gh))); + + connect(all); return all.stream().sorted(new Sorter()).toList(); } private static long distanceFromRoot(GenreHierarchy gh) { long dfr = 0; - while (gh.getParent() != null) { + while (gh.getParnt() != null) { dfr++; - gh = gh.getParent(); + gh = gh.getParnt(); } return dfr; @@ -260,14 +286,14 @@ public int compare(GenreHierarchy gh1, GenreHierarchy gh2) { return res; } - Deque getPath(GenreHierarchy gh){ + Deque getPath(GenreHierarchy gh) { Deque path = new ArrayDeque<>(); do { path.push(gh.getName()); - gh = gh.getParent(); - } while (gh != null); + gh = gh.getParnt(); + } while (gh != null); return path; - } + } } } diff --git a/srv/src/main/resources/application.yaml b/srv/src/main/resources/application.yaml index dad875b3..f28a0738 100644 --- a/srv/src/main/resources/application.yaml +++ b/srv/src/main/resources/application.yaml @@ -1,7 +1,8 @@ --- logging: level: - '[com.sap.cds.auditlog]': DEBUG + com.sap.cds.auditlog: DEBUG + com.sap.cds.persistence.sql: DEBUG spring: jmx: enabled: true