From dbf10db6f468075293d61e7bbeb9006fd15cfce6 Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Wed, 21 Dec 2022 09:29:54 +0000 Subject: [PATCH] CpsPath Query Optimization - Optimized CpsPathqueries with descendants that only care about the xpath (no attribuets checks) - Use native query with regular expression for target xpath and descendants - Refactored so existing sql-geneartion code can be re-used in different repository implementations - Adjusted related performance test expectations Issue-ID: CPS-1421 Signed-off-by: ToineSiebelink Change-Id: I3a807a14478c4b3272a5335d31c9aa3615eb2bee --- .../onap/cps/cpspath/parser/CpsPathUtilSpec.groovy | 1 + .../cps/spi/entities/FragmentEntityArranger.java | 12 +- .../spi/impl/CpsDataPersistenceServiceImpl.java | 107 +++++++++++----- .../cps/spi/repository/FragmentQueryBuilder.java | 139 +++++++++++++++++++++ .../cps/spi/repository/FragmentRepository.java | 8 ++ .../FragmentRepositoryCpsPathQueryImpl.java | 77 +----------- .../spi/performance/CpsToDataNodePerfTest.groovy | 26 ++-- 7 files changed, 244 insertions(+), 126 deletions(-) create mode 100644 cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy index 662e42b6b..df2e9d72c 100644 --- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy +++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy @@ -85,4 +85,5 @@ class CpsPathUtilSpec extends Specification { // In CI this actually takes about 3-5 sec which is approx. 50+ parser executions per millisecond! assert setupStopWatch.getTotalTimeMillis() < 10000 } + } 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 index 27891c525..6b1162d11 100644 --- 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 @@ -31,14 +31,13 @@ import lombok.NoArgsConstructor; public class FragmentEntityArranger { /** - * Convert a collection of (related) FragmentExtracts into a FragmentEntity (tree) with descendants. - * Multiple top level nodes not yet support. If found only the first top level element is returned + * 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 FragmentEntity (tree) with descendants, null if none found. + * @return a collection of FragmentEntities (trees) with descendants. */ - public static FragmentEntity toFragmentEntityTree(final AnchorEntity anchorEntity, + public static Collection toFragmentEntityTrees(final AnchorEntity anchorEntity, final Collection fragmentExtracts) { final Map fragmentEntityPerId = new HashMap<>(); for (final FragmentExtract fragmentExtract : fragmentExtracts) { @@ -61,7 +60,8 @@ public class FragmentEntityArranger { return fragmentEntity; } - private static FragmentEntity reuniteChildrenWithTheirParents(final Map fragmentEntityPerId) { + private static Collection reuniteChildrenWithTheirParents( + final Map fragmentEntityPerId) { final Collection fragmentEntitiesWithoutParentInResultSet = new HashSet<>(); for (final FragmentEntity fragmentEntity : fragmentEntityPerId.values()) { final FragmentEntity parentFragmentEntity = fragmentEntityPerId.get(fragmentEntity.getParentId()); @@ -71,7 +71,7 @@ public class FragmentEntityArranger { parentFragmentEntity.getChildFragments().add(fragmentEntity); } } - return fragmentEntitiesWithoutParentInResultSet.stream().findFirst().orElse(null); + 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 82bcea2f1..3bd299430 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 @@ -61,6 +61,7 @@ import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.repository.AnchorRepository; import org.onap.cps.spi.repository.DataspaceRepository; +import org.onap.cps.spi.repository.FragmentQueryBuilder; import org.onap.cps.spi.repository.FragmentRepository; import org.onap.cps.spi.utils.SessionManager; import org.onap.cps.utils.JsonObjectMapper; @@ -265,36 +266,37 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final String xpath, final FetchDescendantsOption fetchDescendantsOption) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final FragmentEntity fragmentEntity; if (isRootXpath(xpath)) { final List fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity, anchorEntity); - return FragmentEntityArranger.toFragmentEntityTree(anchorEntity, - fragmentExtracts); + fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts) + .stream().findFirst().orElse(null); } else { final String normalizedXpath = getNormalizedXpath(xpath); - final FragmentEntity fragmentEntity; if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { fragmentEntity = fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath); } else { - fragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity, normalizedXpath); - } - if (fragmentEntity == null) { - throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath); + fragmentEntity = buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath) + .stream().findFirst().orElse(null); } - return fragmentEntity; } + if (fragmentEntity == null) { + throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath); + } + return fragmentEntity; + } - private FragmentEntity buildFragmentEntityFromFragmentExtracts(final AnchorEntity anchorEntity, - final String normalizedXpath) { - final FragmentEntity fragmentEntity; + private Collection buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity, + final String normalizedXpath) { final List fragmentExtracts = fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath); log.debug("Fetched {} fragment entities by anchor {} and cps path {}.", fragmentExtracts.size(), anchorEntity.getName(), normalizedXpath); - fragmentEntity = FragmentEntityArranger.toFragmentEntityTree(anchorEntity, fragmentExtracts); - return fragmentEntity; + return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + } @Override @@ -308,32 +310,73 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } catch (final PathParsingException e) { throw new CpsPathException(e.getMessage()); } - List fragmentEntities = - fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); + + Collection fragmentEntities; + if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) { + return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery); + } + fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { - final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() - : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + fragmentEntities = getAncestorFragmentEntities(anchorEntity, cpsPathQuery, fragmentEntities); } - return createDataNodesFromFragmentEntities(fetchDescendantsOption, anchorEntity, - fragmentEntities); + return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities); } - private List createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, - final AnchorEntity anchorEntity, - final List fragmentEntities) { - final List dataNodes = new ArrayList<>(fragmentEntities.size()); - for (final FragmentEntity proxiedFragmentEntity : fragmentEntities) { - final DataNode dataNode; + private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final CpsPathQuery cpsPathQuery) { + return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + && !cpsPathQuery.hasLeafConditions() + && !cpsPathQuery.hasTextFunctionCondition(); + } + + private List getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final CpsPathQuery cpsPathQuery) { + Collection fragmentEntities; + final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true); + final List fragmentExtracts = + fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex); + fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + if (cpsPathQuery.hasAncestorAxis()) { + fragmentEntities = getAncestorFragmentEntities(anchorEntity, cpsPathQuery, fragmentEntities); + } + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); + } + + private Collection getAncestorFragmentEntities(final AnchorEntity anchorEntity, + final CpsPathQuery cpsPathQuery, + Collection fragmentEntities) { + final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); + fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() + : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + return fragmentEntities; + } + + private List createDataNodesFromProxiedFragmentEntities( + final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final Collection proxiedFragmentEntities) { + final List dataNodes = new ArrayList<>(proxiedFragmentEntities.size()); + for (final FragmentEntity proxiedFragmentEntity : proxiedFragmentEntities) { if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { - dataNode = toDataNode(proxiedFragmentEntity, fetchDescendantsOption); + dataNodes.add(toDataNode(proxiedFragmentEntity, fetchDescendantsOption)); } else { final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath()); - final FragmentEntity unproxiedFragmentEntity = buildFragmentEntityFromFragmentExtracts(anchorEntity, - normalizedXpath); - dataNode = toDataNode(unproxiedFragmentEntity, fetchDescendantsOption); + final Collection unproxiedFragmentEntities = + buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath); + for (final FragmentEntity unproxiedFragmentEntity : unproxiedFragmentEntities) { + dataNodes.add(toDataNode(unproxiedFragmentEntity, fetchDescendantsOption)); + } } - dataNodes.add(dataNode); + } + return Collections.unmodifiableList(dataNodes); + } + + private List createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, + final Collection fragmentEntities) { + final List dataNodes = new ArrayList<>(fragmentEntities.size()); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption)); } return Collections.unmodifiableList(dataNodes); } @@ -364,7 +407,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds); } - private static Set processAncestorXpath(final List fragmentEntities, + private static Set processAncestorXpath(final Collection fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set ancestorXpath = new HashSet<>(); final Pattern pattern = diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java new file mode 100644 index 000000000..f107928ca --- /dev/null +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java @@ -0,0 +1,139 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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.util.HashMap; +import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.cpspath.parser.CpsPathPrefixType; +import org.onap.cps.cpspath.parser.CpsPathQuery; +import org.onap.cps.spi.entities.FragmentEntity; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Slf4j +@Component +public class FragmentQueryBuilder { + private static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/"; + private static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?"; + private static final String REGEX_DESCENDANT_PATH_POSTFIX = "(\\/.*)?"; + private static final String REGEX_END_OF_INPUT = "$"; + + @PersistenceContext + private EntityManager entityManager; + + private final JsonObjectMapper jsonObjectMapper; + + /** + * Create a sql query to retrieve by anchor(id) and cps path. + * + * @param anchorId the id of the anchor + * @param cpsPathQuery the cps path query to be transformed into a sql query + * @return a executable query object + */ + public Query getQueryForAnchorAndCpsPath(final int anchorId, final CpsPathQuery cpsPathQuery) { + final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId"); + final Map queryParameters = new HashMap<>(); + queryParameters.put("anchorId", anchorId); + sqlStringBuilder.append(" AND xpath ~ :xpathRegex"); + final String xpathRegex = getXpathSqlRegex(cpsPathQuery, false); + queryParameters.put("xpathRegex", xpathRegex); + if (cpsPathQuery.hasLeafConditions()) { + sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb"); + queryParameters.put("leafDataAsJson", jsonObjectMapper.asJsonString( + cpsPathQuery.getLeavesData())); + } + + addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); + final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class); + setQueryParameters(query, queryParameters); + return query; + } + + /** + * Create a regular expression (string) for xpath based on the given cps path query. + * + * @param cpsPathQuery the cps path query to determine the required regular expression + * @param includeDescendants include descendants yes or no + * @return a string representing the required regular expression + */ + public static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery, final boolean includeDescendants) { + final StringBuilder xpathRegexBuilder = new StringBuilder(); + if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) { + xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix())); + } else { + xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX); + xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName())); + } + xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX); + if (includeDescendants) { + xpathRegexBuilder.append(REGEX_DESCENDANT_PATH_POSTFIX); + } + xpathRegexBuilder.append(REGEX_END_OF_INPUT); + return xpathRegexBuilder.toString(); + } + + private static String escapeXpath(final String xpath) { + // See https://jira.onap.org/browse/CPS-500 for limitations of this basic escape mechanism + return xpath.replace("[@", "\\[@"); + } + + private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) { + try { + return Integer.parseInt(cpsPathQuery.getTextFunctionConditionValue()); + } catch (final NumberFormatException e) { + return null; + } + } + + private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery, + final StringBuilder sqlStringBuilder, + final Map queryParameters) { + if (cpsPathQuery.hasTextFunctionCondition()) { + sqlStringBuilder.append(" AND ("); + sqlStringBuilder.append("attributes @> jsonb_build_object(:textLeafName, :textValue)"); + sqlStringBuilder + .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValue))"); + queryParameters.put("textLeafName", cpsPathQuery.getTextFunctionConditionLeafName()); + queryParameters.put("textValue", cpsPathQuery.getTextFunctionConditionValue()); + final Integer textValueAsInt = getTextValueAsInt(cpsPathQuery); + if (textValueAsInt != null) { + sqlStringBuilder.append(" OR attributes @> jsonb_build_object(:textLeafName, :textValueAsInt)"); + sqlStringBuilder + .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValueAsInt))"); + queryParameters.put("textValueAsInt", textValueAsInt); + } + sqlStringBuilder.append(")"); + } + } + + private static void setQueryParameters(final Query query, final Map queryParameters) { + for (final Map.Entry queryParameter : queryParameters.entrySet()) { + query.setParameter(queryParameter.getKey(), queryParameter.getValue()); + } + } + +} 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 2c25a61a7..c9461bf06 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 @@ -94,4 +94,12 @@ public interface FragmentRepository extends JpaRepository, nativeQuery = true) List findByAnchorIdAndParentXpath(@Param("anchorId") int anchorId, @Param("parentXpath") String parentXpath); + + @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId," + + " CAST(attributes AS TEXT) AS attributes" + + " FROM FRAGMENT WHERE anchor_id = :anchorId" + + " AND xpath ~ :xpathRegex", + nativeQuery = true) + List quickFindWithDescendants(@Param("anchorId") int anchorId, + @Param("xpathRegex") String xpathRegex); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java index 1d61416cf..6e8f05f01 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java @@ -20,103 +20,32 @@ package org.onap.cps.spi.repository; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.cpspath.parser.CpsPathPrefixType; import org.onap.cps.cpspath.parser.CpsPathQuery; import org.onap.cps.spi.entities.FragmentEntity; -import org.onap.cps.utils.JsonObjectMapper; @RequiredArgsConstructor @Slf4j public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery { - public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/"; - public static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$"; - @PersistenceContext private EntityManager entityManager; - private final JsonObjectMapper jsonObjectMapper; + + private final FragmentQueryBuilder fragmentQueryBuilder; @Override @Transactional public List findByAnchorAndCpsPath(final int anchorId, final CpsPathQuery cpsPathQuery) { - final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId"); - final Map queryParameters = new HashMap<>(); - queryParameters.put("anchorId", anchorId); - sqlStringBuilder.append(" AND xpath ~ :xpathRegex"); - final String xpathRegex = getXpathSqlRegex(cpsPathQuery); - queryParameters.put("xpathRegex", xpathRegex); - if (cpsPathQuery.hasLeafConditions()) { - sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb"); - queryParameters.put("leafDataAsJson", jsonObjectMapper.asJsonString( - cpsPathQuery.getLeavesData())); - } - - addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters); - final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class); - setQueryParameters(query, queryParameters); + final Query query = fragmentQueryBuilder.getQueryForAnchorAndCpsPath(anchorId, cpsPathQuery); final List fragmentEntities = query.getResultList(); log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size()); return fragmentEntities; } - private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) { - final StringBuilder xpathRegexBuilder = new StringBuilder(); - if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) { - xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix())); - } else { - xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX); - xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName())); - } - xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX); - return xpathRegexBuilder.toString(); - } - - private static String escapeXpath(final String xpath) { - // See https://jira.onap.org/browse/CPS-500 for limitations of this basic escape mechanism - return xpath.replace("[@", "\\[@"); - } - - private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) { - try { - return Integer.parseInt(cpsPathQuery.getTextFunctionConditionValue()); - } catch (final NumberFormatException e) { - return null; - } - } - - private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder, - final Map queryParameters) { - if (cpsPathQuery.hasTextFunctionCondition()) { - sqlStringBuilder.append(" AND ("); - sqlStringBuilder.append("attributes @> jsonb_build_object(:textLeafName, :textValue)"); - sqlStringBuilder - .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValue))"); - queryParameters.put("textLeafName", cpsPathQuery.getTextFunctionConditionLeafName()); - queryParameters.put("textValue", cpsPathQuery.getTextFunctionConditionValue()); - final Integer textValueAsInt = getTextValueAsInt(cpsPathQuery); - if (textValueAsInt != null) { - sqlStringBuilder.append(" OR attributes @> jsonb_build_object(:textLeafName, :textValueAsInt)"); - sqlStringBuilder - .append(" OR attributes @> jsonb_build_object(:textLeafName, json_build_array(:textValueAsInt))"); - queryParameters.put("textValueAsInt", textValueAsInt); - } - sqlStringBuilder.append(")"); - } - } - - private static void setQueryParameters(final Query query, final Map queryParameters) { - for (final Map.Entry queryParameter : queryParameters.entrySet()) { - query.setParameter(queryParameter.getKey(), queryParameter.getValue()); - } - } - } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy index b26cef4de..33e83f101 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy @@ -20,7 +20,7 @@ package org.onap.cps.spi.performance -import org.apache.commons.lang3.time.StopWatch +import org.springframework.util.StopWatch import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.impl.CpsPersistenceSpecBase import org.onap.cps.spi.model.DataNode @@ -56,7 +56,7 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { setupStopWatch.start() createLineage() setupStopWatch.stop() - def setupDurationInMillis = setupStopWatch.getTime() + def setupDurationInMillis = setupStopWatch.getTotalTimeMillis() and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds' assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS } @@ -66,7 +66,7 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { readStopWatch.start() def result = objectUnderTest.getDataNode('PERF-DATASPACE', 'PERF-ANCHOR', xpath, INCLUDE_ALL_DESCENDANTS) readStopWatch.stop() - def readDurationInMillis = readStopWatch.getTime() + def readDurationInMillis = readStopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS and: 'data node is returned with all the descendants populated' @@ -79,11 +79,10 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { def 'Query parent data node with many descendants by cps-path'() { when: 'query is executed with all descendants' - readStopWatch.reset() readStopWatch.start() def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-parent-1' , INCLUDE_ALL_DESCENDANTS) readStopWatch.stop() - def readDurationInMillis = readStopWatch.getTime() + def readDurationInMillis = readStopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS and: 'data node is returned with all the descendants populated' @@ -92,11 +91,10 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { def 'Query many descendants by cps-path with #scenario'() { when: 'query is executed with all descendants' - readStopWatch.reset() readStopWatch.start() def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-test-grand-child-1', descendantsOption) readStopWatch.stop() - def readDurationInMillis = readStopWatch.getTime() + def readDurationInMillis = readStopWatch.getTotalTimeMillis() then: 'read duration is under 500 milliseconds' assert readDurationInMillis < alowedDuration and: 'data node is returned with all the descendants populated' @@ -104,24 +102,24 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase { where: 'the following options are used' scenario | descendantsOption || alowedDuration 'omit descendants ' | OMIT_DESCENDANTS || 150 - 'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS || 1500 + 'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS || 150 } def createLineage() { (1..NUMBER_OF_CHILDREN).each { def childName = "perf-test-child-${it}".toString() - def newChild = goForthAndMultiply(PERF_TEST_PARENT, childName) - objectUnderTest.addChildDataNode('PERF-DATASPACE', 'PERF-ANCHOR', PERF_TEST_PARENT, newChild) + def child = goForthAndMultiply(PERF_TEST_PARENT, childName) + objectUnderTest.addChildDataNode('PERF-DATASPACE', 'PERF-ANCHOR', PERF_TEST_PARENT, child) } } def goForthAndMultiply(parentXpath, childName) { - def children = [] + def grandChildren = [] (1..NUMBER_OF_GRAND_CHILDREN).each { - def child = new DataNodeBuilder().withXpath("${parentXpath}/${childName}/perf-test-grand-child-${it}").build() - children.add(child) + def grandChild = new DataNodeBuilder().withXpath("${parentXpath}/${childName}/perf-test-grand-child-${it}").build() + grandChildren.add(grandChild) } - return new DataNodeBuilder().withXpath("${parentXpath}/${childName}").withChildDataNodes(children).build() + return new DataNodeBuilder().withXpath("${parentXpath}/${childName}").withChildDataNodes(grandChildren).build() } def countDataNodes(dataNodes) { -- 2.16.6