diff options
author | 2025-03-27 10:56:13 +0000 | |
---|---|---|
committer | 2025-03-27 10:56:13 +0000 | |
commit | a0993069311561c2b48e79df9273d421eb252723 (patch) | |
tree | 8aa80249bf07e79482af81892f4abe3e1ff029a9 | |
parent | c79c4234c525ae29590096d1f3f107813be1cfd0 (diff) | |
parent | 66afb872fd75c36e05b4f94a76c00ca08f4511dd (diff) |
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 } } |