Read Performance Improvement - Using Native Query
- Native query for FragmentExtracts
- Convert FragmentExtracts to tree of FragmentEntity
- Native Query now used for all Gets with descendants (orignal hibernate option only used when descendanst ommited)
- Added error handling for not-found on native query
- Ommit descendants by default on many udpate use-cases (this might have a signifcant perf. improvemnt impact too)
- Improved legacy tests for delete use-cases
- Corrected performace test expectation
- Fix TTL test realizing TTL resolution is whole seconds!
Issue-ID: CPS-1301
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Change-Id: I658ac1b7b7036f01050f30bdf9e5bd175725ef1d
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
new file mode 100644
index 0000000..50187a4
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java
@@ -0,0 +1,79 @@
+/*
+ * ============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.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 a FragmentEntity (tree) with descendants.
+ * Multiple top level nodes not yet support. If found only the first top level element is returned
+ *
+ * @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.
+ */
+ public static FragmentEntity toFragmentEntityTree(final AnchorEntity anchorEntity,
+ final Collection<FragmentExtract> fragmentExtracts) {
+ final Map<Long, FragmentEntity> fragmentEntityPerId = new HashMap<>();
+ for (final FragmentExtract fragmentExtract : fragmentExtracts) {
+ 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 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);
+ }
+ }
+ if (fragmentEntitiesWithoutParentInResultSet.iterator().hasNext()) {
+ return fragmentEntitiesWithoutParentInResultSet.iterator().next();
+ }
+ return null;
+ }
+
+}
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/entities/FragmentExtract.java
new file mode 100644
index 0000000..52c1a60
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentExtract.java
@@ -0,0 +1,34 @@
+/*
+ * ============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.entities;
+
+public interface FragmentExtract {
+
+ Long getId();
+
+ Long getAnchorId();
+
+ String getXpath();
+
+ Long getParentId();
+
+ String getAttributes();
+}
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 06c12a8..2a4a192 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
@@ -51,6 +51,8 @@
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.entities.SchemaSetEntity;
import org.onap.cps.spi.entities.YangResourceEntity;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
@@ -120,7 +122,8 @@
private void addNewChildDataNode(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final DataNode newChild) {
- final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final FragmentEntity parentFragmentEntity =
+ getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
final FragmentEntity newChildAsFragmentEntity =
convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
parentFragmentEntity.getAnchor(), newChild);
@@ -135,7 +138,8 @@
private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> newChildren) {
- final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final FragmentEntity parentFragmentEntity =
+ getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
try {
newChildren.forEach(newChildAsDataNode -> {
@@ -219,19 +223,25 @@
@Override
public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
final FetchDescendantsOption fetchDescendantsOption) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
+ final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption);
final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
dataNode.setModuleNamePrefix(getRootModuleNamePrefix(fragmentEntity.getAnchor()));
return dataNode;
}
+ private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName,
+ final String anchorName,
+ final String xpath) {
+ return getFragmentByXpath(dataspaceName, anchorName, xpath, FetchDescendantsOption.OMIT_DESCENDANTS);
+ }
+
private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
- final String xpath) {
+ final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
if (isRootXpath(xpath)) {
- return fragmentRepository.findFirstRootByDataspaceAndAnchor(
- dataspaceEntity, anchorEntity);
+ return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
} else {
final String normalizedXpath;
try {
@@ -239,9 +249,21 @@
} catch (final PathParsingException e) {
throw new CpsPathException(e.getMessage());
}
-
- return fragmentRepository.getByDataspaceAndAnchorAndXpath(
- dataspaceEntity, anchorEntity, normalizedXpath);
+ final FragmentEntity fragmentEntity;
+ if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
+ fragmentEntity =
+ fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
+ } else {
+ final List<FragmentExtract> fragmentExtracts =
+ fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath);
+ log.debug("Fetched {} fragment entities by anchor {} and cps path {}.",
+ fragmentExtracts.size(), anchorName, xpath);
+ fragmentEntity = FragmentEntityArranger.toFragmentEntityTree(anchorEntity, fragmentExtracts);
+ }
+ if (fragmentEntity == null) {
+ throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath);
+ }
+ return fragmentEntity;
}
}
@@ -316,7 +338,7 @@
}
private String getRootModuleNamePrefix(final AnchorEntity anchorEntity) {
- final String cachedModuleNamePrefix = getModuleNamePrefixFromCache(anchorEntity);
+ final String cachedModuleNamePrefix = getModuleNamePrefixFromCache(anchorEntity.getName());
if (cachedModuleNamePrefix != null) {
return cachedModuleNamePrefix;
}
@@ -344,9 +366,9 @@
}
}
- private String getModuleNamePrefixFromCache(final AnchorEntity anchorEntity) {
- if (anchorDataCache.containsKey(anchorEntity.getName())) {
- final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchorEntity.getName());
+ private String getModuleNamePrefixFromCache(final String anchorName) {
+ if (anchorDataCache.containsKey(anchorName)) {
+ final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchorName);
return anchorDataCacheEntry.hasProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME)
? anchorDataCacheEntry.getProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME).toString() : null;
}
@@ -366,7 +388,7 @@
@Override
public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
final Map<String, Object> leaves) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
+ final FragmentEntity fragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, xpath);
fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves));
fragmentRepository.save(fragmentEntity);
}
@@ -374,7 +396,8 @@
@Override
public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
final DataNode dataNode) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath());
+ final FragmentEntity fragmentEntity =
+ getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath());
updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
try {
fragmentRepository.save(fragmentEntity);
@@ -393,7 +416,8 @@
final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
.collect(Collectors.toMap(
dataNode -> dataNode,
- dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
+ dataNode ->
+ getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath())));
dataNodeFragmentEntityMap.forEach(
(dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
try {
@@ -453,7 +477,8 @@
@Transactional
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> newListElements) {
- final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final FragmentEntity parentEntity =
+ getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
@@ -507,7 +532,7 @@
} else {
parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/'));
}
- parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ parentFragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/'));
final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE
.matcher(lastXpathElement).find();
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 c48c79e..112ebfd 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
@@ -30,6 +30,7 @@
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;
@@ -69,4 +70,12 @@
@Modifying
@Query("DELETE FROM FragmentEntity fe WHERE fe.anchor IN (:anchors)")
void deleteByAnchorIn(@NotNull @Param("anchors") Collection<AnchorEntity> anchorEntities);
+
+ @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 = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%') )",
+ nativeQuery = true)
+ List<FragmentExtract> findByAnchorIdAndParentXpath(@Param("anchorId") int anchorId,
+ @Param("parentXpath") String parentXpath);
}
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 654c1c0..1d61416 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
@@ -57,7 +57,7 @@
if (cpsPathQuery.hasLeafConditions()) {
sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb");
queryParameters.put("leafDataAsJson", jsonObjectMapper.asJsonString(
- cpsPathQuery.getLeavesData()));
+ cpsPathQuery.getLeavesData()));
}
addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index 5e15ca7..412c5aa 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -531,28 +531,25 @@
def 'Confirm deletion of #scenario.'() {
given: 'a valid data node'
def dataNode
- def dataNodeXpath
- when: 'data nodes are deleted'
+ and: 'data nodes are deleted'
objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion)
- then: 'verify data nodes are removed'
- try {
- dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, getDataNodesXpaths, INCLUDE_ALL_DESCENDANTS)
- dataNodeXpath = dataNode.xpath
- assert dataNodeXpath == expectedXpaths
- } catch (DataNodeNotFoundException) {
- assert dataNodeXpath == expectedXpaths
+ when: 'verify data nodes are removed'
+ objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion, INCLUDE_ALL_DESCENDANTS)
+ then:
+ thrown(DataNodeNotFoundException)
+ and: 'some related object is not deleted'
+ if (xpathSurvivor!=null) {
+ dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathSurvivor, INCLUDE_ALL_DESCENDANTS)
+ assert dataNode.xpath == xpathSurvivor
}
where: 'following parameters were used'
- scenario | xpathForDeletion | getDataNodesXpaths || expectedXpaths
- 'child of target' | '/parent-206/child-206' | '/parent-206/child-206' || null
- 'child data node, parent still exists' | '/parent-206/child-206' | '/parent-206' || '/parent-206'
- 'list element' | '/parent-206/child-206/grand-child-206[@key="A"]' | '/parent-206/child-206/grand-child-206[@key="A"]' || null
- 'list element, sibling still exists' | '/parent-206/child-206/grand-child-206[@key="A"]' | '/parent-206/child-206/grand-child-206[@key="X"]' || "/parent-206/child-206/grand-child-206[@key='X']"
- 'container node' | '/parent-206' | '/parent-206' || null
- 'container list node' | '/parent-206[@key="A"]' | '/parent-206[@key="B"]' || "/parent-206[@key='B']"
- 'root node with xpath /' | '/' | '/' || null
- 'root node with xpath passed as blank' | '' | '' || null
-
+ scenario | xpathForDeletion || xpathSurvivor
+ 'child data node, parent still exists' | '/parent-206/child-206' || '/parent-206'
+ 'list element, sibling still exists' | '/parent-206/child-206/grand-child-206[@key="A"]' || "/parent-206/child-206/grand-child-206[@key='X']"
+ 'container node' | '/parent-206' || null
+ 'container list node' | '/parent-206[@key="A"]' || "/parent-206[@key='B']"
+ 'root node with xpath /' | '/' || null
+ 'root node with xpath passed as blank' | '' || null
}
@Sql([CLEAR_DATA, SET_DATA])
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
index 3b15b76..b124925 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
@@ -25,8 +25,7 @@
import org.onap.cps.spi.cache.AnchorDataCacheEntry
import org.onap.cps.spi.entities.AnchorEntity
import org.onap.cps.spi.entities.FragmentEntity
-import org.onap.cps.spi.entities.SchemaSetEntity
-import org.onap.cps.spi.entities.YangResourceEntity
+import org.onap.cps.spi.entities.FragmentExtract
import org.onap.cps.spi.exceptions.ConcurrencyException
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.DataNode
@@ -52,25 +51,6 @@
def objectUnderTest = new CpsDataPersistenceServiceImpl(
mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper, mockSessionManager, mockAnchorDataCache)
- @Shared
- def NEW_RESOURCE_CONTENT = 'module stores {\n' +
- ' yang-version 1.1;\n' +
- ' namespace "org:onap:ccsdk:sample";\n' +
- '\n' +
- ' prefix book-store;\n' +
- '\n' +
- ' revision "2020-09-15" {\n' +
- ' description\n' +
- ' "Sample Model";\n' +
- ' }' +
- '}'
-
- @Shared
- def yangResourceSet = [new YangResourceEntity(moduleName: 'moduleName', content: NEW_RESOURCE_CONTENT,
- fileName: 'sampleYangResource'
- )] as Set
-
-
def 'Handling of StaleStateException (caused by concurrent updates) during update data node and descendants.'() {
given: 'the fragment repository returns a fragment entity'
mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> {
@@ -107,16 +87,12 @@
and: 'it contains the failed datanodes'
assert thrown.details.contains('/node2')
assert thrown.details.contains('/node3')
-
}
+
def 'Retrieving a data node with a property JSON value of #scenario'() {
- given: 'a fragment with a property JSON value of #scenario'
- mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> {
- new FragmentEntity(childFragments: Collections.emptySet(),
- attributes: "{\"some attribute\": ${dataString}}",
- anchor: new AnchorEntity(schemaSet: new SchemaSetEntity(yangResources: yangResourceSet )))
- }
+ given: 'the db has a fragment with an attribute property JSON value of #scenario'
+ mockFragmentWithJson("{\"some attribute\": ${dataString}}")
when: 'getting the data node represented by this fragment'
def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
'/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
@@ -140,9 +116,7 @@
def 'Retrieving a data node with invalid JSON'() {
given: 'a fragment with invalid JSON'
- mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> {
- new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
- }
+ mockFragmentWithJson('{invalid json')
when: 'getting the data node represented by this fragment'
objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
'/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
@@ -214,4 +188,21 @@
}
return dataNode
}
+
+ def mockFragmentWithJson(json) {
+ def anchorName = 'some anchor'
+ def anchorDataCacheEntry = new AnchorDataCacheEntry()
+ anchorDataCacheEntry.setProperty(objectUnderTest.TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME, 'some prefix')
+ mockAnchorDataCache.containsKey(anchorName) >> true
+ mockAnchorDataCache.get(anchorName) >> anchorDataCacheEntry
+ def mockAnchor = Mock(AnchorEntity)
+ mockAnchor.getId() >> 123
+ mockAnchor.getName() >> anchorName
+ mockAnchorRepository.getByDataspaceAndName(*_) >> mockAnchor
+ def mockFragmentExtract = Mock(FragmentExtract)
+ mockFragmentExtract.getId() >> 456
+ mockFragmentExtract.getAttributes() >> json
+ mockFragmentRepository.findByAnchorIdAndParentXpath(*_) >> [mockFragmentExtract]
+ }
+
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsToDataNodePerfSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsToDataNodePerfSpec.groovy
index 5b28028..283be6b 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsToDataNodePerfSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsToDataNodePerfSpec.groovy
@@ -22,20 +22,23 @@
import org.apache.commons.lang3.time.StopWatch
import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.spi.impl.CpsDataPersistenceServiceImpl
import org.onap.cps.spi.impl.CpsPersistenceSpecBase
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.spi.repository.FragmentRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
class CpsToDataNodePerfSpec extends CpsPersistenceSpecBase {
+ static final String SET_DATA = '/data/fragment.sql'
+
@Autowired
CpsDataPersistenceService objectUnderTest
- static final String SET_DATA = '/data/fragment.sql'
- static final String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1'
+ def XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1'
@Sql([CLEAR_DATA, SET_DATA])
def 'Get data node by xpath with all descendants with many children'() {
@@ -48,14 +51,13 @@
when: 'data node is requested with all descendants'
def readStopWatch = new StopWatch()
readStopWatch.start()
- def result = objectUnderTest.getDataNode(
- DATASPACE_NAME, ANCHOR_NAME1, XPATH_DATA_NODE_WITH_DESCENDANTS, INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, XPATH_DATA_NODE_WITH_DESCENDANTS, INCLUDE_ALL_DESCENDANTS)
readStopWatch.stop()
def readDurationInMillis = readStopWatch.getTime()
- then : 'setup duration is under 8 seconds'
+ then: 'setup duration is under 8 seconds'
assert setupDurationInMillis < 8000
- and: 'read duration is under 6 seconds'
- assert readDurationInMillis < 6000
+ and: 'read duration is under 1500 milliseconds'
+ assert readDurationInMillis < 1500
and: 'data node is returned with all the descendants populated'
assert countDataNodes(result) == 1533
}
@@ -86,4 +88,4 @@
}
return nodeCount
}
-}
\ No newline at end of file
+}
diff --git a/cps-ri/src/test/resources/application.yml b/cps-ri/src/test/resources/application.yml
index e835b77..4f40aea 100644
--- a/cps-ri/src/test/resources/application.yml
+++ b/cps-ri/src/test/resources/application.yml
@@ -25,6 +25,7 @@
enable_lazy_load_no_trans: true
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
+ show_sql: false
datasource:
url: ${DB_URL}