aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLee Anjella Macabuhay <lee.anjella.macabuhay@est.tech>2025-03-27 10:56:13 +0000
committerGerrit Code Review <gerrit@onap.org>2025-03-27 10:56:13 +0000
commita0993069311561c2b48e79df9273d421eb252723 (patch)
tree8aa80249bf07e79482af81892f4abe3e1ff029a9
parentc79c4234c525ae29590096d1f3f107813be1cfd0 (diff)
parent66afb872fd75c36e05b4f94a76c00ca08f4511dd (diff)
Merge "Efficient implementation of Attribute Axis in SQL"HEADmaster
-rw-r--r--cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java18
-rw-r--r--cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java30
-rw-r--r--cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java6
-rw-r--r--cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java25
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy14
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy8
7 files changed, 70 insertions, 33 deletions
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
index 575f9d7d3b..472da34833 100644
--- a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
@@ -1,6 +1,6 @@
/*
* ============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.
@@ -24,7 +24,6 @@
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;
@@ -39,7 +38,6 @@ import java.util.HashMap;
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;
@@ -249,17 +247,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
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
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java
index 0f17b6f931..3b88748545 100644
--- a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java
+++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java
@@ -1,6 +1,6 @@
/*
* ============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");
@@ -61,17 +61,23 @@ public class FragmentQueryBuilder {
* @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);
}
@@ -312,7 +318,10 @@ public class FragmentQueryBuilder {
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");
}
}
@@ -327,9 +336,14 @@ public class FragmentQueryBuilder {
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);
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java
index 50c7494b29..a24b280bf8 100644
--- a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java
+++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java
@@ -1,6 +1,6 @@
/*-
* ============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");
@@ -22,6 +22,7 @@
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;
@@ -33,6 +34,9 @@ public interface FragmentRepositoryCpsPathQuery {
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);
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java
index 80fbe9b6cd..cc8055d3c1 100644
--- a/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java
@@ -1,6 +1,6 @@
/*-
* ============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");
@@ -24,6 +24,8 @@ package org.onap.cps.ri.repository;
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;
@@ -31,20 +33,22 @@ import org.onap.cps.cpspath.parser.CpsPathQuery;
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) {
@@ -55,6 +59,21 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
@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,
final CpsPathQuery cpsPathQuery, final List<Long> anchorIds) {
final Query query = fragmentQueryBuilder.getQueryForDataspaceAndCpsPath(
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index b319929e47..138fc34ca1 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -1,6 +1,6 @@
/*
* ============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.
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy
index aa80e7f6e8..212686e917 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============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');
@@ -66,6 +66,7 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
'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.'() {
@@ -78,7 +79,7 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
where:
leafName | leafType || expectedResults
'lang' | String.class || ['English']
- 'price' | Number.class || [13, 20]
+ 'price' | Integer.class || [13, 20]
'editions' | List.class || [[1988, 2000], [2006]]
}
@@ -91,6 +92,15 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
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
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
index 70639c3c70..8c429b3a30 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy
@@ -1,6 +1,6 @@
/*
* ============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.
@@ -116,9 +116,9 @@ class QueryPerfTest extends CpsPerfTestBase {
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
}
}