diff options
author | danielhanrahan <daniel.hanrahan@est.tech> | 2023-06-08 14:37:17 +0100 |
---|---|---|
committer | danielhanrahan <daniel.hanrahan@est.tech> | 2023-06-14 14:29:06 +0100 |
commit | 9474e8f257f9a03c80c01cbf3729cd128a43847b (patch) | |
tree | 30c9f019e3e7df44d37ae8e4ffb695426b01ac2b /cps-ri/src/main | |
parent | 1368fd006373dd209a34274723fbda6ecb9d317f (diff) |
Lower memory usage in FragmentRepository
Avoid using Spring Data "interface projection" in FragmentRepository.
The use of FragmentExtract in FragmentRepository is causing an
overhead of around 5 kilobytes per fragment, which is leading to
abnormally high memory usage when queries return a large number of
nodes. For example, around 250MB of additional memory is needlessly
used when fetching 50,000 datanodes.
- Remove FragmentExtract interface and FragmentEntityArranger class.
- Add FragmentPrefetchRepository, using JdbcTemplate and RowMapper
to fetch FragmentEntity descendants in a single SQL query.
- Many CpsDataService operations have memory reductions:
- queryDataNodes
- getDataNodesForMultipleXpaths
- updateDataNodesAndDescendants
- updateNodeLeaves
- and any NCMP methods using the above.
Issue-ID: CPS-1716
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: Ic47a2c9eb34150ed76bd5ce452fe1c9aaf9b4c5c
Diffstat (limited to 'cps-ri/src/main')
-rw-r--r-- | cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java | 97 | ||||
-rw-r--r-- | cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java | 54 | ||||
-rw-r--r-- | cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepository.java (renamed from cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentExtract.java) | 21 | ||||
-rw-r--r-- | cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java | 127 | ||||
-rwxr-xr-x | cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java | 57 |
5 files changed, 151 insertions, 205 deletions
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java deleted file mode 100644 index 697eb8de00..0000000000 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.entities; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class FragmentEntityArranger { - - /** - * Convert a collection of (related) FragmentExtracts into FragmentEntities (trees) with descendants. - * - * @param anchorEntity the anchor(entity) all the fragments belong to - * @param fragmentExtracts FragmentExtracts to convert - * @return a collection of FragmentEntities (trees) with descendants. - */ - public static Collection<FragmentEntity> toFragmentEntityTrees(final AnchorEntity anchorEntity, - final Collection<FragmentExtract> fragmentExtracts) { - final Map<Long, FragmentEntity> fragmentEntityPerId = new HashMap<>(); - if (fragmentExtracts != null) { - for (final FragmentExtract fragmentExtract : fragmentExtracts) { - final FragmentEntity fragmentEntity = toFragmentEntity(anchorEntity, fragmentExtract); - fragmentEntityPerId.put(fragmentEntity.getId(), fragmentEntity); - } - } - return reuniteChildrenWithTheirParents(fragmentEntityPerId); - } - - /** - * Convert a collection of (related) FragmentExtracts into FragmentEntities (trees) with descendants. - * - * @param anchorEntityPerId the anchor(entities) the fragments belong to - * @param fragmentExtracts FragmentExtracts to convert - * @return a collection of FragmentEntities (trees) with descendants. - */ - public static Collection<FragmentEntity> toFragmentEntityTreesAcrossAnchors( - final Map<Long, AnchorEntity> anchorEntityPerId, final Collection<FragmentExtract> fragmentExtracts) { - final Map<Long, FragmentEntity> fragmentEntityPerId = new HashMap<>(); - for (final FragmentExtract fragmentExtract : fragmentExtracts) { - final AnchorEntity anchorEntity = anchorEntityPerId.get(fragmentExtract.getAnchorId()); - final FragmentEntity fragmentEntity = toFragmentEntity(anchorEntity, fragmentExtract); - fragmentEntityPerId.put(fragmentEntity.getId(), fragmentEntity); - } - return reuniteChildrenWithTheirParents(fragmentEntityPerId); - } - - private static FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, - final FragmentExtract fragmentExtract) { - final FragmentEntity fragmentEntity = new FragmentEntity(); - fragmentEntity.setAnchor(anchorEntity); - fragmentEntity.setId(fragmentExtract.getId()); - fragmentEntity.setXpath(fragmentExtract.getXpath()); - fragmentEntity.setAttributes(fragmentExtract.getAttributes()); - fragmentEntity.setParentId(fragmentExtract.getParentId()); - fragmentEntity.setChildFragments(new HashSet<>()); - return fragmentEntity; - } - - private static Collection<FragmentEntity> reuniteChildrenWithTheirParents( - final Map<Long, FragmentEntity> fragmentEntityPerId) { - final Collection<FragmentEntity> fragmentEntitiesWithoutParentInResultSet = new HashSet<>(); - for (final FragmentEntity fragmentEntity : fragmentEntityPerId.values()) { - final FragmentEntity parentFragmentEntity = fragmentEntityPerId.get(fragmentEntity.getParentId()); - if (parentFragmentEntity == null) { - fragmentEntitiesWithoutParentInResultSet.add(fragmentEntity); - } else { - parentFragmentEntity.getChildFragments().add(fragmentEntity); - } - } - return fragmentEntitiesWithoutParentInResultSet; - } - -} diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 02f723029d..e6e250f082 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -36,7 +36,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -52,8 +51,6 @@ import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.entities.AnchorEntity; import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.FragmentEntity; -import org.onap.cps.spi.entities.FragmentEntityArranger; -import org.onap.cps.spi.entities.FragmentExtract; import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.ConcurrencyException; @@ -248,7 +245,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpaths, fetchDescendantsOption); - return toDataNodes(fragmentEntities, fetchDescendantsOption); + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); } private Collection<FragmentEntity> getFragmentEntities(final AnchorEntity anchorEntity, @@ -269,19 +266,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService normalizedXpaths.addAll(fragmentRepository.findAllXpathByAnchorAndParentIdIsNull(anchorEntity)); } - final List<FragmentExtract> fragmentExtracts = - fragmentRepository.findExtractsWithDescendants(anchorEntity.getId(), normalizedXpaths, - fetchDescendantsOption.getDepth()); + final List<FragmentEntity> fragmentEntities = fragmentRepository.findByAnchorAndXpathIn(anchorEntity, + normalizedXpaths); - return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + return fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption, fragmentEntities); } private FragmentEntity getFragmentEntity(final AnchorEntity anchorEntity, final String xpath) { final FragmentEntity fragmentEntity; if (isRootXpath(xpath)) { - final List<FragmentExtract> fragmentExtracts = fragmentRepository.findAllExtractsByAnchor(anchorEntity); - fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts) - .stream().findFirst().orElse(null); + fragmentEntity = fragmentRepository.findOneByAnchorId(anchorEntity.getId()).orElse(null); } else { fragmentEntity = fragmentRepository.getByAnchorAndXpath(anchorEntity, getNormalizedXpath(xpath)); } @@ -320,8 +314,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService fragmentEntities = fragmentRepository.findByAnchorAndXpathIn(anchorEntity, ancestorXpaths); } } - fragmentEntities = prefetchDescendantsForFragmentEntities(fetchDescendantsOption, anchorEntity, - fragmentEntities); + fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption, + fragmentEntities); return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); } @@ -331,31 +325,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return queryDataNodes(dataspaceName, QUERY_ACROSS_ANCHORS, cpsPath, fetchDescendantsOption); } - private Collection<FragmentEntity> prefetchDescendantsForFragmentEntities( - final FetchDescendantsOption fetchDescendantsOption, - final AnchorEntity anchorEntity, - final Collection<FragmentEntity> proxiedFragmentEntities) { - if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { - return proxiedFragmentEntities; - } - - final List<Long> fragmentEntityIds = proxiedFragmentEntities.stream() - .map(FragmentEntity::getId).collect(Collectors.toList()); - - final List<FragmentExtract> fragmentExtracts = - fragmentRepository.findExtractsWithDescendantsByIds(fragmentEntityIds, fetchDescendantsOption.getDepth()); - - if (anchorEntity == ALL_ANCHORS) { - final Collection<Long> anchorIds = fragmentExtracts.stream() - .map(FragmentExtract::getAnchorId).collect(Collectors.toSet()); - final List<AnchorEntity> anchorEntities = anchorRepository.findAllById(anchorIds); - final Map<Long, AnchorEntity> anchorEntityPerId = anchorEntities.stream() - .collect(Collectors.toMap(AnchorEntity::getId, Function.identity())); - return FragmentEntityArranger.toFragmentEntityTreesAcrossAnchors(anchorEntityPerId, fragmentExtracts); - } - return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); - } - private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, final Collection<FragmentEntity> fragmentEntities) { final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size()); @@ -422,15 +391,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService .withChildDataNodes(childDataNodes).build(); } - private Collection<DataNode> toDataNodes(final Collection<FragmentEntity> fragmentEntities, - final FetchDescendantsOption fetchDescendantsOption) { - final Collection<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size()); - for (final FragmentEntity fragmentEntity : fragmentEntities) { - dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption)); - } - return dataNodes; - } - private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { if (fetchDescendantsOption.hasNext()) { diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentExtract.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepository.java index 50be3c7b7a..2460db869a 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentExtract.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepository.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation. + * Copyright (C) 2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,14 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.spi.entities; +package org.onap.cps.spi.repository; -public interface FragmentExtract { +import java.util.Collection; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.entities.FragmentEntity; - Long getId(); - - Long getAnchorId(); - - String getXpath(); - - Long getParentId(); - - String getAttributes(); +public interface FragmentPrefetchRepository { + Collection<FragmentEntity> prefetchDescendantsOfFragmentEntities( + final FetchDescendantsOption fetchDescendantsOption, + final Collection<FragmentEntity> proxiedFragmentEntities); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java new file mode 100644 index 0000000000..4f056c8f6e --- /dev/null +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentPrefetchRepositoryImpl.java @@ -0,0 +1,127 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.spi.repository; + +import java.sql.Connection; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.RequiredArgsConstructor; +import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.entities.AnchorEntity; +import org.onap.cps.spi.entities.FragmentEntity; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class FragmentPrefetchRepositoryImpl implements FragmentPrefetchRepository { + + private final JdbcTemplate jdbcTemplate; + + @Override + public Collection<FragmentEntity> prefetchDescendantsOfFragmentEntities( + final FetchDescendantsOption fetchDescendantsOption, + final Collection<FragmentEntity> proxiedFragmentEntities) { + + if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { + return proxiedFragmentEntities; + } + + final List<Long> fragmentEntityIds = proxiedFragmentEntities.stream() + .map(FragmentEntity::getId).collect(Collectors.toList()); + + final Map<Long, AnchorEntity> anchorEntityPerId = proxiedFragmentEntities.stream() + .map(FragmentEntity::getAnchor) + .collect(Collectors.toMap(AnchorEntity::getId, anchor -> anchor, (anchor1, anchor2) -> anchor1)); + + final int maxDepth = fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + ? Integer.MAX_VALUE + : fetchDescendantsOption.getDepth(); + return findFragmentEntitiesWithDescendantsByIds(fragmentEntityIds, anchorEntityPerId, maxDepth); + } + + private Collection<FragmentEntity> findFragmentEntitiesWithDescendantsByIds( + final Collection<Long> fragmentEntityIds, + final Map<Long, AnchorEntity> anchorEntityPerId, + final int maxDepth) { + final String sql + = "WITH RECURSIVE parent_search AS (" + + " SELECT id, 0 AS depth " + + " FROM fragment " + + " WHERE id = ANY (?) " + + " UNION " + + " SELECT child.id, depth + 1 " + + " FROM fragment child INNER JOIN parent_search parent ON child.parent_id = parent.id" + + " WHERE depth < ?" + + ") " + + "SELECT fragment.id, anchor_id AS anchorId, xpath, parent_id AS parentId, " + + " CAST(attributes AS TEXT) AS attributes " + + "FROM fragment INNER JOIN parent_search ON fragment.id = parent_search.id"; + + final PreparedStatementSetter preparedStatementSetter = preparedStatement -> { + final Connection connection = preparedStatement.getConnection(); + final java.sql.Array idArray = connection.createArrayOf("bigint", fragmentEntityIds.toArray()); + preparedStatement.setArray(1, idArray); + preparedStatement.setInt(2, maxDepth); + }; + + final RowMapper<FragmentEntity> fragmentEntityRowMapper = (resultSet, rowNum) -> { + final FragmentEntity fragmentEntity = new FragmentEntity(); + fragmentEntity.setId(resultSet.getLong("id")); + fragmentEntity.setXpath(resultSet.getString("xpath")); + fragmentEntity.setParentId(resultSet.getLong("parentId")); + fragmentEntity.setAttributes(resultSet.getString("attributes")); + fragmentEntity.setAnchor(anchorEntityPerId.get(resultSet.getLong("anchorId"))); + fragmentEntity.setChildFragments(new HashSet<>()); + return fragmentEntity; + }; + + final Map<Long, FragmentEntity> fragmentEntityPerId; + try (final Stream<FragmentEntity> fragmentEntityStream = jdbcTemplate.queryForStream(sql, + preparedStatementSetter, fragmentEntityRowMapper)) { + fragmentEntityPerId = fragmentEntityStream.collect( + Collectors.toMap(FragmentEntity::getId, Function.identity())); + } + return reuniteChildrenWithTheirParents(fragmentEntityPerId); + } + + private static Collection<FragmentEntity> reuniteChildrenWithTheirParents( + final Map<Long, FragmentEntity> fragmentEntityPerId) { + final Collection<FragmentEntity> fragmentEntitiesWithoutParent = new HashSet<>(); + for (final FragmentEntity fragmentEntity : fragmentEntityPerId.values()) { + final FragmentEntity parentFragmentEntity = fragmentEntityPerId.get(fragmentEntity.getParentId()); + if (parentFragmentEntity == null) { + fragmentEntitiesWithoutParent.add(fragmentEntity); + } else { + parentFragmentEntity.getChildFragments().add(fragmentEntity); + } + } + return fragmentEntitiesWithoutParent; + } + +} diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java index 82c422f6fd..03de95eb8d 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java @@ -29,7 +29,6 @@ import java.util.Optional; import org.onap.cps.spi.entities.AnchorEntity;
import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.FragmentEntity;
-import org.onap.cps.spi.entities.FragmentExtract;
import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
@@ -38,7 +37,8 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository;
@Repository
-public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery {
+public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,
+ FragmentPrefetchRepository {
Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);
@@ -47,7 +47,10 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath));
}
- List<FragmentEntity> findByAnchorIdAndXpathIn(long anchorId, String[] xpaths);
+ @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",
+ nativeQuery = true)
+ List<FragmentEntity> findByAnchorIdAndXpathIn(@Param("anchorId") long anchorId,
+ @Param("xpaths") String[] xpaths);
default List<FragmentEntity> findByAnchorAndXpathIn(final AnchorEntity anchorEntity,
final Collection<String> xpaths) {
@@ -66,8 +69,8 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, boolean existsByAnchorId(long anchorId);
- @Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor")
- List<FragmentExtract> findAllExtractsByAnchor(@Param("anchor") AnchorEntity anchorEntity);
+ @Query(value = "SELECT * FROM fragment WHERE anchor_id = :anchorId LIMIT 1", nativeQuery = true)
+ Optional<FragmentEntity> findOneByAnchorId(@Param("anchorId") long anchorId);
@Modifying
@Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)
@@ -111,48 +114,4 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND parentId IS NULL")
List<String> findAllXpathByAnchorAndParentIdIsNull(@Param("anchor") AnchorEntity anchorEntity);
- @Query(value
- = "WITH RECURSIVE parent_search AS ("
- + " SELECT id, 0 AS depth "
- + " FROM fragment "
- + " WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths) "
- + " UNION "
- + " SELECT c.id, depth + 1 "
- + " FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"
- + " WHERE depth < (SELECT CASE WHEN :maxDepth = -1 THEN " + Integer.MAX_VALUE + " ELSE :maxDepth END) "
- + ") "
- + "SELECT f.id, anchor_id AS anchorId, xpath, f.parent_id AS parentId, CAST(attributes AS TEXT) AS attributes "
- + "FROM fragment f INNER JOIN parent_search p ON f.id = p.id",
- nativeQuery = true
- )
- List<FragmentExtract> findExtractsWithDescendants(@Param("anchorId") long anchorId,
- @Param("xpaths") String[] xpaths,
- @Param("maxDepth") int maxDepth);
-
- default List<FragmentExtract> findExtractsWithDescendants(final long anchorId, final Collection<String> xpaths,
- final int maxDepth) {
- return findExtractsWithDescendants(anchorId, xpaths.toArray(new String[0]), maxDepth);
- }
-
- @Query(value
- = "WITH RECURSIVE parent_search AS ("
- + " SELECT id, 0 AS depth "
- + " FROM fragment "
- + " WHERE id = ANY (:ids) "
- + " UNION "
- + " SELECT c.id, depth + 1 "
- + " FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"
- + " WHERE depth < (SELECT CASE WHEN :maxDepth = -1 THEN " + Integer.MAX_VALUE + " ELSE :maxDepth END) "
- + ") "
- + "SELECT f.id, anchor_id AS anchorId, xpath, f.parent_id AS parentId, CAST(attributes AS TEXT) AS attributes "
- + "FROM fragment f INNER JOIN parent_search p ON f.id = p.id",
- nativeQuery = true
- )
- List<FragmentExtract> findExtractsWithDescendantsByIds(@Param("ids") long[] ids,
- @Param("maxDepth") int maxDepth);
-
- default List<FragmentExtract> findExtractsWithDescendantsByIds(final Collection<Long> ids, final int maxDepth) {
- return findExtractsWithDescendantsByIds(ids.stream().mapToLong(id -> id).toArray(), maxDepth);
- }
-
}
|