/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2025 Nordix Foundation
+ * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
package org.onap.cps.ri;
import static org.onap.cps.api.CpsQueryService.NO_LIMIT;
-import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS;
import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
throw new IllegalArgumentException(
"Only Cps Path Queries with attribute-axis are supported by queryDataLeaf");
}
-
- final String attributeName = cpsPathQuery.getAttributeAxisAttributeName();
- final Collection<DataNode> dataNodes = queryDataNodes(dataspaceName, anchorName, cpsPath,
- OMIT_DESCENDANTS, queryResultLimit);
- return dataNodes.stream()
- .map(dataNode -> {
- final Object attributeValue = dataNode.getLeaves().get(attributeName);
- return targetClass.isInstance(attributeValue) ? targetClass.cast(attributeValue) : null;
- })
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
+ final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
+ return fragmentRepository.findAttributeValuesByAnchorAndCpsPath(anchorEntity, cpsPathQuery,
+ cpsPathQuery.getAttributeAxisAttributeName(), queryResultLimit, targetClass);
}
@Override
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2025 Nordix Foundation
+ * Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* @return a executable query object
*/
public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity,
- final CpsPathQuery cpsPathQuery,
- final int queryResultLimit) {
+ final CpsPathQuery cpsPathQuery,
+ final int queryResultLimit) {
final StringBuilder sqlStringBuilder = new StringBuilder();
final Map<String, Object> queryParameters = new HashMap<>();
addSearchPrefix(cpsPathQuery, sqlStringBuilder);
addWhereClauseForAnchor(anchorEntity, sqlStringBuilder, queryParameters);
+ if (cpsPathQuery.hasAttributeAxis() && !cpsPathQuery.hasAncestorAxis()) {
+ sqlStringBuilder.append(" AND jsonb_exists(fragment.attributes, :attributeName)");
+ }
addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, false);
addSearchSuffix(cpsPathQuery, sqlStringBuilder, queryParameters);
addLimitClause(sqlStringBuilder, queryParameters, queryResultLimit);
-
+ if (cpsPathQuery.hasAttributeAxis()) {
+ queryParameters.put("attributeName", cpsPathQuery.getAttributeAxisAttributeName());
+ return getQuery(sqlStringBuilder.toString(), queryParameters, String.class);
+ }
return getQuery(sqlStringBuilder.toString(), queryParameters, FragmentEntity.class);
}
WHERE parentFragment.id IN (
SELECT parent_id FROM fragment""");
} else {
- sqlStringBuilder.append("SELECT fragment.* FROM fragment");
+ final String fieldsToSelect = cpsPathQuery.hasAttributeAxis()
+ ? "DISTINCT (attributes -> :attributeName)"
+ : "fragment.*";
+ sqlStringBuilder.append("SELECT ").append(fieldsToSelect).append(" FROM fragment");
}
}
FROM fragment
JOIN ancestors ON ancestors.parent_id = fragment.id
)
- SELECT * FROM ancestors
- WHERE""");
-
+ """);
+ if (cpsPathQuery.hasAttributeAxis()) {
+ sqlStringBuilder.append("""
+ SELECT DISTINCT (attributes -> :attributeName) FROM ancestors WHERE
+ jsonb_exists(ancestors.attributes, :attributeName) AND""");
+ } else {
+ sqlStringBuilder.append("SELECT * FROM ancestors WHERE");
+ }
final String ancestorPath = DESCENDANT_PATH + cpsPathQuery.getAncestorSchemaNodeIdentifier();
final CpsPathQuery ancestorCpsPathQuery = CpsPathUtil.getCpsPathQuery(ancestorPath);
addAncestorNodeSearchCondition(ancestorCpsPathQuery, sqlStringBuilder, queryParameters);
/*-
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2025 Nordix Foundation.
+ * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
package org.onap.cps.ri.repository;
import java.util.List;
+import java.util.Set;
import org.onap.cps.api.parameters.PaginationOption;
import org.onap.cps.cpspath.parser.CpsPathQuery;
import org.onap.cps.ri.models.AnchorEntity;
List<FragmentEntity> findByAnchorAndCpsPath(AnchorEntity anchorEntity, CpsPathQuery cpsPathQuery,
int queryResultLimit);
+ <T> Set<T> findAttributeValuesByAnchorAndCpsPath(AnchorEntity anchorEntity, CpsPathQuery cpsPathQuery,
+ String attributeName, int queryResultLimit, Class<T> targetClass);
+
List<FragmentEntity> findByDataspaceAndCpsPath(DataspaceEntity dataspaceEntity,
CpsPathQuery cpsPathQuery, List<Long> anchorIds);
/*-
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation.
+ * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
import jakarta.persistence.Query;
import jakarta.transaction.Transactional;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.parameters.PaginationOption;
import org.onap.cps.ri.models.AnchorEntity;
import org.onap.cps.ri.models.DataspaceEntity;
import org.onap.cps.ri.models.FragmentEntity;
+import org.onap.cps.utils.JsonObjectMapper;
@RequiredArgsConstructor
@Slf4j
public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery {
private final FragmentQueryBuilder fragmentQueryBuilder;
+ private final JsonObjectMapper jsonObjectMapper;
@Override
@Transactional
public List<FragmentEntity> findByAnchorAndCpsPath(final AnchorEntity anchorEntity,
final CpsPathQuery cpsPathQuery,
final int queryResultLimit) {
- final Query query = fragmentQueryBuilder
- .getQueryForAnchorAndCpsPath(anchorEntity, cpsPathQuery, queryResultLimit);
+ final Query query = fragmentQueryBuilder.getQueryForAnchorAndCpsPath(anchorEntity, cpsPathQuery,
+ queryResultLimit);
final List<FragmentEntity> fragmentEntities = query.getResultList();
log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size());
if (queryResultLimit > 0) {
return fragmentEntities;
}
+ @Override
+ @Transactional
+ public <T> Set<T> findAttributeValuesByAnchorAndCpsPath(final AnchorEntity anchorEntity,
+ final CpsPathQuery cpsPathQuery,
+ final String attributeName,
+ final int queryResultLimit,
+ final Class<T> targetClass) {
+ final Query query = fragmentQueryBuilder.getQueryForAnchorAndCpsPath(anchorEntity, cpsPathQuery,
+ queryResultLimit);
+ final List<String> jsonResultList = query.getResultList();
+ return jsonResultList.stream()
+ .map(jsonValue -> jsonObjectMapper.convertJsonString(jsonValue, targetClass))
+ .collect(Collectors.toSet());
+ }
+
@Override
@Transactional
public List<FragmentEntity> findByDataspaceAndCpsPath(final DataspaceEntity dataspaceEntity,
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2025 Nordix Foundation.
+ * Copyright (C) 2020-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 Bell Canada
* Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2023-2025 Nordix Foundation
+ * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
* Modifications Copyright (C) 2023-2025 TechMahindra Ltd
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the 'License');
'all books' | '//books/@title' || 19
'all books in a category' | '/bookstore/categories[@code=5]/books/@title' || 10
'non-existing path' | '/non-existing/@title' || 0
+ 'non-existing attribute' | '//books/@non-existing' || 0
}
def 'Query data leaf with type #leafType using CPS path.'() {
where:
leafName | leafType || expectedResults
'lang' | String.class || ['English']
- 'price' | Number.class || [13, 20]
+ 'price' | Integer.class || [13, 20]
'editions' | List.class || [[1988, 2000], [2006]]
}
assert result == ['Children', 'Comedy'] as Set
}
+ def 'Attempt to query data leaf without specifying leaf name gives an error.'() {
+ given: 'a cps path without an attribute axis'
+ def cpsPathWithoutAttributeAxis = '//books'
+ when: 'query data leaf is called without attribute axis in cps path'
+ objectUnderTest.queryDataLeaf(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPathWithoutAttributeAxis, String.class)
+ then: 'illegal argument exception is thrown'
+ thrown(IllegalArgumentException)
+ }
+
def 'Cps Path query using comparative and boolean operators.'() {
given: 'a cps path query in the discount category'
def cpsPath = "/bookstore/categories[@code='5']/books" + leafCondition
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2023-2025 Nordix Foundation
+ * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
recordAndAssertResourceUsage("Query data leaf ${scenario}", durationLimit, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB())
where: 'the following parameters are used'
scenario | cpsPath || durationLimit | memoryLimit | expectedNumberOfValues
- 'unique leaf value' | '/openroadm-devices/openroadm-device/@device-id' || 0.10 | 8 | OPENROADM_DEVICES_PER_ANCHOR
- 'common leaf value' | '/openroadm-devices/openroadm-device/@ne-state' || 0.05 | 1 | 1
- 'non-existing data leaf' | '/openroadm-devices/openroadm-device/@non-existing' || 0.05 | 1 | 0
+ 'unique leaf value' | '/openroadm-devices/openroadm-device/@device-id' || 0.05 | 0.1 | OPENROADM_DEVICES_PER_ANCHOR
+ 'common leaf value' | '/openroadm-devices/openroadm-device/@ne-state' || 0.02 | 0.1 | 1
+ 'non-existing data leaf' | '/openroadm-devices/openroadm-device/@non-existing' || 0.01 | 0.1 | 0
}
}