diff options
Diffstat (limited to 'cps-ri')
7 files changed, 155 insertions, 106 deletions
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 48f1de710d..ab135fd3ae 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 @@ -28,9 +28,11 @@ import com.google.common.collect.ImmutableSet.Builder; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; @@ -62,6 +64,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private FragmentRepository fragmentRepository; private static final Gson GSON = new GsonBuilder().create(); + private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@\\S+?]){0,1})"; @Override public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath, @@ -82,7 +85,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService try { fragmentRepository.save(fragmentEntity); } catch (final DataIntegrityViolationException exception) { - throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception); + throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception); } } @@ -144,7 +147,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); final var cpsPathQuery = CpsPathQuery.createFrom(cpsPath); - final List<FragmentEntity> fragmentEntities; + List<FragmentEntity> fragmentEntities; if (CpsPathQueryType.XPATH_LEAF_VALUE.equals(cpsPathQuery.getCpsPathQueryType())) { fragmentEntities = fragmentRepository .getByAnchorAndXpathAndLeafAttributes(anchorEntity.getId(), cpsPathQuery.getXpathPrefix(), @@ -158,11 +161,31 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService fragmentEntities = fragmentRepository .getByAnchorAndXpathEndsInDescendantName(anchorEntity.getId(), cpsPathQuery.getDescendantName()); } + if (cpsPathQuery.hasAncestorAxis()) { + final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); + fragmentEntities = ancestorXpaths.isEmpty() + ? Collections.emptyList() : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + } return fragmentEntities.stream() .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption)) .collect(Collectors.toUnmodifiableList()); } + private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities, + final CpsPathQuery cpsPathQuery) { + final Set<String> ancestorXpath = new HashSet<>(); + final var pattern = + Pattern.compile("(\\S*\\/" + cpsPathQuery.getAncestorSchemaNodeIdentifier() + REG_EX_FOR_OPTIONAL_LIST_INDEX + + "\\/\\S*"); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + final var matcher = pattern.matcher(fragmentEntity.getXpath()); + if (matcher.matches()) { + ancestorXpath.add(matcher.group(1)); + } + } + return ancestorXpath; + } + private static DataNode toDataNode(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { final Map<String, Object> leaves = GSON.fromJson(fragmentEntity.getAttributes(), Map.class); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java index 6f53e0013d..f48d16590e 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java @@ -20,6 +20,8 @@ package org.onap.cps.spi.query; +import static org.springframework.util.StringUtils.isEmpty; + import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -39,6 +41,7 @@ public class CpsPathQuery { private Object leafValue; private String descendantName; private Map<String, Object> leavesData; + private String ancestorSchemaNodeIdentifier; private static final String NON_CAPTURING_GROUP_1_TO_99_YANG_CONTAINERS = "((?:\\/[^\\/]+){1,99})"; @@ -48,14 +51,14 @@ public class CpsPathQuery { private static final Pattern QUERY_CPS_PATH_WITH_SINGLE_LEAF_PATTERN = Pattern.compile(NON_CAPTURING_GROUP_1_TO_99_YANG_CONTAINERS + YANG_LEAF_VALUE_EQUALS_CONDITION); - private static final Pattern DESCENDANT_ANYWHERE_PATTERN = Pattern.compile("\\/\\/([^\\/].+)"); + private static final Pattern DESCENDANT_ANYWHERE_PATTERN = Pattern.compile("\\/\\/([^\\/][^:]+)"); private static final Pattern LEAF_INTEGER_VALUE_PATTERN = Pattern.compile("[-+]?\\d+"); private static final Pattern LEAF_STRING_VALUE_IN_SINGLE_QUOTES_PATTERN = Pattern.compile("'(.*)'"); private static final Pattern LEAF_STRING_VALUE_IN_DOUBLE_QUOTES_PATTERN = Pattern.compile("\"(.*)\""); - private static final String YANG_MULTIPLE_LEAF_VALUE_EQUALS_CONDITION = "\\[(.*?)\\s{0,9}]"; + private static final String YANG_MULTIPLE_LEAF_VALUE_EQUALS_CONDITION = "\\[(.*?)\\s{0,9}]"; private static final Pattern DESCENDANT_ANYWHERE_PATTERN_WITH_MULTIPLE_LEAF_PATTERN = Pattern.compile(DESCENDANT_ANYWHERE_PATTERN + YANG_MULTIPLE_LEAF_VALUE_EQUALS_CONDITION); @@ -64,45 +67,60 @@ public class CpsPathQuery { private static final Pattern LEAF_VALUE_PATTERN = Pattern.compile("@(\\S+?)=(.*)"); + private static final Pattern ANCESTOR_AXIS_PATTERN = Pattern.compile("(\\S+)\\/ancestor::\\/?(\\S+)"); + /** * Returns a cps path query. * - * @param cpsPath cps path + * @param cpsPathSource cps path * @return a CpsPath object. */ - public static CpsPathQuery createFrom(final String cpsPath) { - var matcher = QUERY_CPS_PATH_WITH_SINGLE_LEAF_PATTERN.matcher(cpsPath); - final var cpsPathQuery = new CpsPathQuery(); + public static CpsPathQuery createFrom(final String cpsPathSource) { + var cpsPath = cpsPathSource; + final CpsPathQuery cpsPathQuery = new CpsPathQuery(); + var matcher = ANCESTOR_AXIS_PATTERN.matcher(cpsPath); if (matcher.matches()) { - return buildCpsPathQueryWithSingleLeafPattern(cpsPath, matcher, cpsPathQuery); + cpsPath = matcher.group(1); + cpsPathQuery.setAncestorSchemaNodeIdentifier(matcher.group(2)); + } + matcher = QUERY_CPS_PATH_WITH_SINGLE_LEAF_PATTERN.matcher(cpsPath); + if (matcher.matches()) { + cpsPathQuery.setParametersForSingleLeafValue(cpsPath, matcher); + return cpsPathQuery; } matcher = DESCENDANT_ANYWHERE_PATTERN_WITH_MULTIPLE_LEAF_PATTERN.matcher(cpsPath); if (matcher.matches()) { - return buildCpsQueryForDescendentWithLeafPattern(cpsPath, matcher, cpsPathQuery); + cpsPathQuery.setParametersForDescendantWithLeafValues(cpsPath, matcher); + return cpsPathQuery; } matcher = DESCENDANT_ANYWHERE_PATTERN.matcher(cpsPath); if (matcher.matches()) { - cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE); - cpsPathQuery.setDescendantName(matcher.group(1)); + cpsPathQuery.setParametersForDescendantAnywhere(matcher); return cpsPathQuery; } throw new CpsPathException("Invalid cps path.", String.format("Cannot interpret or parse cps path '%s'.", cpsPath)); } - private static CpsPathQuery buildCpsPathQueryWithSingleLeafPattern(final String cpsPath, final Matcher matcher, - final CpsPathQuery cpsPathQuery) { - cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_LEAF_VALUE); - cpsPathQuery.setXpathPrefix(matcher.group(1)); - cpsPathQuery.setLeafName(matcher.group(2)); - cpsPathQuery.setLeafValue(convertLeafValueToCorrectType(matcher.group(3), cpsPath)); - return cpsPathQuery; + /** + * Has ancestor axis been populated. + * + * @return boolean value. + */ + public boolean hasAncestorAxis() { + return !(isEmpty(ancestorSchemaNodeIdentifier)); + } + + private void setParametersForSingleLeafValue(final String cpsPath, final Matcher matcher) { + setCpsPathQueryType(CpsPathQueryType.XPATH_LEAF_VALUE); + setXpathPrefix(matcher.group(1)); + setLeafName(matcher.group(2)); + setLeafValue(convertLeafValueToCorrectType(matcher.group(3), cpsPath)); } - private static CpsPathQuery buildCpsQueryForDescendentWithLeafPattern(final String cpsPath, final Matcher matcher, - final CpsPathQuery cpsPathQuery) { - cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES); - cpsPathQuery.setDescendantName(matcher.group(1)); + private void setParametersForDescendantWithLeafValues(final String cpsPath, final Matcher matcher) { + setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES); + setDescendantName(matcher.group(1)); final Map<String, Object> leafData = new HashMap<>(); for (final String leafValuePair : matcher.group(2).split(INDIVIDUAL_LEAF_DETAIL_PATTERN)) { final var descendentMatcher = LEAF_VALUE_PATTERN.matcher(leafValuePair); @@ -114,8 +132,12 @@ public class CpsPathQuery { String.format("Cannot interpret or parse attributes in cps path '%s'.", cpsPath)); } } - cpsPathQuery.setLeavesData(leafData); - return cpsPathQuery; + setLeavesData(leafData); + } + + private void setParametersForDescendantAnywhere(final Matcher matcher) { + setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE); + setDescendantName(matcher.group(1)); } private static Object convertLeafValueToCorrectType(final String leafValueString, final String cpsPath) { 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 b9874484c0..c484ae9e87 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 @@ -57,6 +57,9 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long> .orElseThrow(() -> new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName()));
}
+ List<FragmentEntity> findAllByAnchorAndXpathIn(@NonNull AnchorEntity anchorEntity,
+ @NonNull Collection<String> xpath);
+
@Modifying
@Query("DELETE FROM FragmentEntity fe WHERE fe.anchor IN (:anchors)")
void deleteByAnchorIn(@NotNull @Param("anchors") Collection<AnchorEntity> anchorEntities);
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy index 8acfe783da..4bebff9f84 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy @@ -20,14 +20,10 @@ */ package org.onap.cps.spi.impl -import com.google.common.collect.ImmutableSet -import com.google.gson.Gson -import com.google.gson.GsonBuilder import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.CpsPathException import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql @@ -39,42 +35,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { @Autowired CpsDataPersistenceService objectUnderTest - static final Gson GSON = new GsonBuilder().create() - static final String SET_DATA = '/data/fragment.sql' - static final String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1' - - static DataNode existingDataNode - static DataNode existingChildDataNode - - def expectedLeavesByXpathMap = [ - '/parent-100' : ['parent-leaf': 'parent-leaf value'], - '/parent-100/child-001' : ['first-child-leaf': 'first-child-leaf value'], - '/parent-100/child-002' : ['second-child-leaf': 'second-child-leaf value'], - '/parent-100/child-002/grand-child': ['grand-child-leaf': 'grand-child-leaf value'] - ] - - static { - existingDataNode = createDataNodeTree(XPATH_DATA_NODE_WITH_DESCENDANTS) - existingChildDataNode = createDataNodeTree('/parent-1/child-1') - } - - static def createDataNodeTree(String... xpaths) { - def dataNodeBuilder = new DataNodeBuilder().withXpath(xpaths[0]) - if (xpaths.length > 1) { - def xPathsDescendant = Arrays.copyOfRange(xpaths, 1, xpaths.length) - def childDataNode = createDataNodeTree(xPathsDescendant) - dataNodeBuilder.withChildDataNodes(ImmutableSet.of(childDataNode)) - } - dataNodeBuilder.build() - } - - def static treeToFlatMapByXpath(Map<String, DataNode> flatMap, DataNode dataNodeTree) { - flatMap.put(dataNodeTree.getXpath(), dataNodeTree) - dataNodeTree.getChildDataNodes() - .forEach(childDataNode -> treeToFlatMapByXpath(flatMap, childDataNode)) - return flatMap - } @Sql([CLEAR_DATA, SET_DATA]) def 'Cps Path query for single leaf value with type: #type.'() { @@ -98,10 +59,10 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { then: 'no data is returned' result.isEmpty() where: 'following cps queries are performed' - scenario | cpsPath - 'cps path is incomplete' | '/parent-200[@common-leaf-name-int=5]' - 'leaf value does not exist' | '/parent-200/child-202[@common-leaf-name=\'does not exist\']' - 'incomplete end of xpath prefix' | '/parent-200/child-20[@common-leaf-name-int=5]' + scenario | cpsPath + 'cps path is incomplete' | '/parent-200[@common-leaf-name-int=5]' + 'leaf value does not exist' | '/parent-200/child-202[@common-leaf-name=\'does not exist\']' + 'incomplete end of xpath prefix' | '/parent-200/child-20[@common-leaf-name-int=5]' } @Sql([CLEAR_DATA, SET_DATA]) @@ -125,13 +86,13 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { then: 'the correct number of data nodes are retrieved' result.size() == expectedXPaths.size() and: 'xpaths of the retrieved data nodes are as expected' - for(int i = 0; i<result.size(); i++) { + for (int i = 0; i < result.size(); i++) { assert result[i].getXpath() == expectedXPaths[i] } where: 'the following data is used' scenario | cpsPath || expectedXPaths 'fully unique descendant name' | '//grand-child-202' || ['/parent-200/child-202/grand-child-202'] - 'descendant name match end of other node' | '//child-202' || ['/parent-200/child-202','/parent-201/child-202'] + 'descendant name match end of other node' | '//child-202' || ['/parent-200/child-202', '/parent-201/child-202'] } @Sql([CLEAR_DATA, SET_DATA]) @@ -141,7 +102,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { then: 'the correct number of data nodes are retrieved' result.size() == expectedXPaths.size() and: 'xpaths of the retrieved data nodes are as expected' - for(int i = 0; i<result.size(); i++) { + for (int i = 0; i < result.size(); i++) { assert result[i].getXpath() == expectedXPaths[i] } where: 'the following data is used' @@ -180,4 +141,24 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { 'one of the leaf without value' | '//child-202[@common-leaf-name-int=5 and @another-attribute"]' 'more than one leaf separated by or' | '//child-202[@common-leaf-name-int=5 or @common-leaf-name="common-leaf value"]' } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Query for attribute by cps path of type ancestor with #scenario.'() { + when: 'the given cps path is parsed' + def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_NAME1, cpsPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'the xpaths of the retrieved data nodes are as expected' + result.size() == expectedXPaths.size() + for (int i = 0; i < result.size(); i++) { + assert result[i].getXpath() == expectedXPaths[i] + } + where: 'the following data is used' + scenario | cpsPath || expectedXPaths + 'multiple list-ancestors' | '//books/ancestor::categories' || ['/bookstore/books/categories[@name="SciFi"]', '/bookstore/magazines/categories[@name="kids"]'] + 'one ancestor value' | '//books/ancestor::books' || ['/bookstore/books'] + 'top ancestor' | '//books/ancestor::bookstore' || ['/bookstore'] + 'list with index value in the xpath prefix' | '//categories[@name="kids"]/books/ancestor::bookstore' || ['/bookstore'] + 'ancestor with parent' | '//books/ancestor::/bookstore/magazines' || ['/bookstore/magazines'] + 'ancestor with parent that does not exist' | '//books/ancestor::/parentDoesNoExist/magazines' || [] + 'ancestor does not exist' | '//books/ancestor::ancestorDoesNotExist' || [] + } } 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 afd2cd1423..f632e022f0 100755 --- 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 @@ -202,27 +202,6 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { 'empty xpath' |'' } - def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) { - expectedLeavesMap.forEach((key, value) -> { - def actualValue = actualLeavesMap[key] - if (value instanceof Collection<?> && actualValue instanceof Collection<?>) { - assert value.size() == actualValue.size() - assert value.containsAll(actualValue) - } else { - assert value == actualValue - } - } - ) - return true - } - - def static treeToFlatMapByXpath(Map<String, DataNode> flatMap, DataNode dataNodeTree) { - flatMap.put(dataNodeTree.getXpath(), dataNodeTree) - dataNodeTree.getChildDataNodes() - .forEach(childDataNode -> treeToFlatMapByXpath(flatMap, childDataNode)) - return flatMap - } - @Sql([CLEAR_DATA, SET_DATA]) def 'Get data node error scenario: #scenario.'() { when: 'attempt to get data node with #scenario' @@ -327,4 +306,25 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) { return GSON.fromJson(fragmentEntity.getAttributes(), Map<String, Object>.class) } + + def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) { + expectedLeavesMap.forEach((key, value) -> { + def actualValue = actualLeavesMap[key] + if (value instanceof Collection<?> && actualValue instanceof Collection<?>) { + assert value.size() == actualValue.size() + assert value.containsAll(actualValue) + } else { + assert value == actualValue + } + }) + return true + } + + def static treeToFlatMapByXpath(Map<String, DataNode> flatMap, DataNode dataNodeTree) { + flatMap.put(dataNodeTree.getXpath(), dataNodeTree) + dataNodeTree.getChildDataNodes() + .forEach(childDataNode -> treeToFlatMapByXpath(flatMap, childDataNode)) + return flatMap + } + } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/query/CpsPathQuerySpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/query/CpsPathQuerySpec.groovy index bd0fb44feb..ee641d1029 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/query/CpsPathQuerySpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/query/CpsPathQuerySpec.groovy @@ -38,10 +38,10 @@ class CpsPathQuerySpec extends Specification { result.leafValue == expectedLeafValue where: 'the following data is used' scenario | cpsPath || expectedXpathPrefix | expectedLeafName | expectedLeafValue - 'leaf of type String' | '/parent/child[@common-leaf-name=\'common-leaf-value\']' || '/parent/child' |'common-leaf-name' | 'common-leaf-value' - 'leaf of type Integer' | '/parent/child[@common-leaf-name-int=5]' || '/parent/child' |'common-leaf-name-int' | 5 - 'spaces around =' | '/parent/child[@common-leaf-name-int = 5]' || '/parent/child' |'common-leaf-name-int' | 5 - 'key in top container' | '/parent[@common-leaf-name-int=5]' || '/parent' |'common-leaf-name-int' | 5 + 'leaf of type String' | '/parent/child[@common-leaf-name=\'common-leaf-value\']' || '/parent/child' | 'common-leaf-name' | 'common-leaf-value' + 'leaf of type Integer' | '/parent/child[@common-leaf-name-int=5]' || '/parent/child' | 'common-leaf-name-int' | 5 + 'spaces around =' | '/parent/child[@common-leaf-name-int = 5]' || '/parent/child' | 'common-leaf-name-int' | 5 + 'key in top container' | '/parent[@common-leaf-name-int=5]' || '/parent' | 'common-leaf-name-int' | 5 } def 'Parse cps path of type ends with a #scenario.'() { @@ -52,9 +52,9 @@ class CpsPathQuerySpec extends Specification { and: 'the right ends with parameters are set' result.descendantName == expectedEndsWithValue where: 'the following data is used' - scenario | cpsPath || expectedEndsWithValue - 'yang container' | '//cps-path' || 'cps-path' - 'parent & child' | '//parent/child' || 'parent/child' + scenario | cpsPath || expectedEndsWithValue + 'yang container' | '//cps-path' || 'cps-path' + 'parent & child' | '//parent/child' || 'parent/child' } def 'Parse cps path that ends with a yang list containing #scenario.'() { @@ -67,8 +67,8 @@ class CpsPathQuerySpec extends Specification { result.leavesData.size() == expectedNumberOfLeaves where: 'the following data is used' scenario | cpsPath || expectedNumberOfLeaves - 'one attribute' | '//child[@common-leaf-name-int=5]' || 1 - 'more than one attribute' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2 + 'one attribute' | '//child[@common-leaf-name-int=5]' || 1 + 'more than one attribute' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2 } def 'Parse cps path with #scenario.'() { @@ -86,6 +86,7 @@ class CpsPathQuerySpec extends Specification { 'too many containers' | '/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/82/83/84/85/86/87/88/89/90/91/92/93/94/95/96/97/98/99/100[@a=1]' 'end with descendant and more than one attribute separated by "or"' | '//child[@int-leaf=5 or @leaf-name="leaf value"]' 'missing attribute value' | '//child[@int-leaf=5 and @name]' + 'incomplete ancestor value' | '//books/ancestor::' } def 'Convert cps leaf value to valid type with leaf of type #scenario.'() { @@ -94,12 +95,22 @@ class CpsPathQuerySpec extends Specification { then: 'the leaf value returned is of the right type' result == expectedLeafOutputValue where: "the following data is used" - scenario | leafValueInputString || expectedLeafOutputValue - 'Integer' | "5" || 5 + scenario | leafValueInputString || expectedLeafOutputValue + 'Integer' | "5" || 5 'String with single quotes' | '\'value in single quotes\'' || 'value in single quotes' 'String with double quotes' | '"value in double quotes"' || 'value in double quotes' 'String containing single quote' | '"value with \'"' || 'value with \'' 'String containing double quote' | '\'value with "\'' || 'value with "' } - -}
\ No newline at end of file + + def 'Parse cps path using ancestor by schema node identifier.'() { + when: 'the given cps path is parsed' + def result = objectUnderTest.createFrom('//someXpath/ancestor::someAncestor') + then: 'the query has the right type' + result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE + and: 'the correct ancestor schema node identifier is set' + result.ancestorSchemaNodeIdentifier == 'someAncestor' + and: 'the result has ancestor axis' + result.hasAncestorAxis() + } +} diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index 3e2ae8157e..95cb3c79ad 100755 --- a/cps-ri/src/test/resources/data/fragment.sql +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -31,4 +31,13 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) (4206, 1001, 3003, null, '/parent-201', '{"leaf-value": "original"}'), (4207, 1001, 3003, 4206, '/parent-201/child-202', '{"common-leaf-name": "common-leaf other value", "common-leaf-name-int" : 5}'), (4208, 1001, 3003, 4206, '/parent-201/child-203[@key1="A" and @key2=1]', '{"key1": "A", "key2" : 1, "other-leaf" : "leaf value"}'), - (4209, 1001, 3003, 4206, '/parent-201/child-203[@key1="A" and @key2=2]', '{"key1": "A", "key2" : 2, "other-leaf" : "other value"}');
\ No newline at end of file + (4209, 1001, 3003, 4206, '/parent-201/child-203[@key1="A" and @key2=2]', '{"key1": "A", "key2" : 2, "other-leaf" : "other value"}'); + +INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES + (1, 1001, 3001, null, '/bookstore'), + (2, 1001, 3001, 1, '/bookstore/books'), + (3, 1001, 3001, 1, '/bookstore/magazines'), + (4, 1001, 3001, 2, '/bookstore/books/categories[@name="SciFi"]'), + (5, 1001, 3001, 3, '/bookstore/magazines/categories[@name="kids"]'), + (6, 1001, 3001, 4, '/bookstore/books/categories[@name="SciFi"]/books'), + (7, 1001, 3001, 6, '/bookstore/magazines/categories[@name="kids"]/books'); |