Query data nodes with limit 80/140180/5
authorleventecsanyi <levente.csanyi@est.tech>
Thu, 6 Feb 2025 14:26:11 +0000 (15:26 +0100)
committerleventecsanyi <levente.csanyi@est.tech>
Tue, 11 Feb 2025 16:58:40 +0000 (17:58 +0100)
  - added new methods to java interfaces
  - added integration test
  - removed unused methods

Issue-ID: CPS-2394
Change-Id: Iac4094a5daedbf593d17f55928136a80391c6d23
Signed-off-by: leventecsanyi <levente.csanyi@est.tech>
cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQueryImpl.java
cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/QueryPerfTest.groovy

index ac6fe38..e102765 100644 (file)
@@ -23,6 +23,7 @@
 
 package org.onap.cps.ri;
 
+import static org.onap.cps.api.CpsQueryService.NO_LIMIT;
 import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION;
 
 import com.google.common.collect.ImmutableSet;
@@ -223,10 +224,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             description = "Time taken to query data nodes")
     public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
                                          final FetchDescendantsOption fetchDescendantsOption) {
+        return queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption, NO_LIMIT);
+    }
+
+    @Override
+    public List<DataNode> queryDataNodes(final String dataspaceName,
+                                         final String anchorName,
+                                         final String cpsPath,
+                                         final FetchDescendantsOption fetchDescendantsOption,
+                                         final int queryResultLimit) {
         final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
         final CpsPathQuery cpsPathQuery = getCpsPathQuery(cpsPath);
         final Collection<FragmentEntity> fragmentEntities =
-                fragmentRepository.findByAnchorAndCpsPath(anchorEntity, cpsPathQuery);
+                fragmentRepository.findByAnchorAndCpsPath(anchorEntity, cpsPathQuery, queryResultLimit);
         return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
     }
 
index 3f3ca79..bf354be 100644 (file)
@@ -51,13 +51,18 @@ public class FragmentQueryBuilder {
     private EntityManager entityManager;
 
     /**
-     * Create a sql query to retrieve by anchor(id) and cps path.
+     * Create a sql query to retrieve by anchor(id) and cps path with an optional queryResultLimit on results.
      *
      * @param anchorEntity the anchor
      * @param cpsPathQuery the cps path query to be transformed into a sql query
+     * @param queryResultLimit queryResultLimit number of returned entities
+     *              (if the queryResultLimit is less than 1 the method returns all related entities)
+     *
      * @return a executable query object
      */
-    public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity, final CpsPathQuery cpsPathQuery) {
+    public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity,
+                                                      final CpsPathQuery cpsPathQuery,
+                                                      final int queryResultLimit) {
         final StringBuilder sqlStringBuilder = new StringBuilder();
         final Map<String, Object> queryParameters = new HashMap<>();
 
@@ -65,6 +70,7 @@ public class FragmentQueryBuilder {
         addWhereClauseForAnchor(anchorEntity, sqlStringBuilder, queryParameters);
         addNodeSearchConditions(cpsPathQuery, sqlStringBuilder, queryParameters, false);
         addSearchSuffix(cpsPathQuery, sqlStringBuilder, queryParameters);
+        addLimitClause(sqlStringBuilder, queryParameters, queryResultLimit);
 
         return getQuery(sqlStringBuilder.toString(), queryParameters, FragmentEntity.class);
     }
@@ -219,6 +225,15 @@ public class FragmentQueryBuilder {
         }
     }
 
+    private static void addLimitClause(final StringBuilder sqlStringBuilder,
+                                       final Map<String, Object> queryParameters,
+                                       final int queryResultLimit) {
+        if (queryResultLimit > 0) {
+            sqlStringBuilder.append(" LIMIT :queryResultLimit");
+            queryParameters.put("queryResultLimit", queryResultLimit);
+        }
+    }
+
     private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) {
         try {
             return Integer.parseInt(cpsPathQuery.getTextFunctionConditionValue());
index 9c1929e..4ee6555 100644 (file)
@@ -29,7 +29,9 @@ import org.onap.cps.ri.models.DataspaceEntity;
 import org.onap.cps.ri.models.FragmentEntity;
 
 public interface FragmentRepositoryCpsPathQuery {
-    List<FragmentEntity> findByAnchorAndCpsPath(AnchorEntity anchorEntity, CpsPathQuery cpsPathQuery);
+
+    List<FragmentEntity> findByAnchorAndCpsPath(AnchorEntity anchorEntity, CpsPathQuery cpsPathQuery,
+                                                int queryResultLimit);
 
     List<FragmentEntity> findByDataspaceAndCpsPath(DataspaceEntity dataspaceEntity,
                                                    CpsPathQuery cpsPathQuery, List<Long> anchorIds);
index e8c2725..80fbe9b 100644 (file)
@@ -41,10 +41,15 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
     @Override
     @Transactional
     public List<FragmentEntity> findByAnchorAndCpsPath(final AnchorEntity anchorEntity,
-                                                       final CpsPathQuery cpsPathQuery) {
-        final Query query = fragmentQueryBuilder.getQueryForAnchorAndCpsPath(anchorEntity, cpsPathQuery);
+                                                       final CpsPathQuery cpsPathQuery,
+                                                       final int 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) {
+            log.debug("Result limited to {} entries", queryResultLimit);
+        }
         return fragmentEntities;
     }
 
index d783b9e..3044fe0 100644 (file)
@@ -32,6 +32,8 @@ import org.onap.cps.api.parameters.PaginationOption;
  */
 public interface CpsQueryService {
 
+    public static int NO_LIMIT = 0;
+
     /**
      * Get data nodes for the given dataspace and anchor by cps path.
      *
@@ -45,6 +47,20 @@ public interface CpsQueryService {
     Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName,
                                         String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
+    /**
+     * Retrieves a collection of data nodes based on the specified CPS path query.
+     *
+     * @param dataspaceName the name of the dataspace (must not be null or empty)
+     * @param anchorName the name of the anchor (must not be null or empty)
+     * @param cpsPath the CPS path used for querying (must not be null or empty)
+     * @param fetchDescendantsOption specifies whether to include descendant nodes in the output
+     * @param queryResultLimit the maximum number of data nodes to return; if less than 1, returns all matching nodes
+     *
+     * @return a collection of matching {@link DataNode} instances (can be empty if no nodes are found)
+     */
+    Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName,
+                                        String cpsPath, FetchDescendantsOption fetchDescendantsOption,
+                                        int queryResultLimit);
 
     /**
      * Get data leaf for the given dataspace and anchor by cps path.
index 508a1b2..f27445f 100644 (file)
@@ -46,8 +46,22 @@ public class CpsQueryServiceImpl implements CpsQueryService {
     public Collection<DataNode> queryDataNodes(final String dataspaceName, final String anchorName,
                                                final String cpsPath,
                                                final FetchDescendantsOption fetchDescendantsOption) {
-        cpsValidator.validateNameCharacters(dataspaceName, anchorName);
-        return cpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
+        return queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption, NO_LIMIT);
+    }
+
+    @Override
+    @Timed(value = "cps.data.service.datanode.query",
+            description = "Time taken to query data nodes with a limit on results")
+    public Collection<DataNode> queryDataNodes(final String dataSpaceName, final String anchorName,
+                                               final String cpsPath,
+                                               final FetchDescendantsOption fetchDescendantsOption,
+                                               final int queryResultLimit) {
+        cpsValidator.validateNameCharacters(dataSpaceName, anchorName);
+        return cpsDataPersistenceService.queryDataNodes(dataSpaceName,
+                                                        anchorName,
+                                                        cpsPath,
+                                                        fetchDescendantsOption,
+                                                        queryResultLimit);
     }
 
     @Override
index a4f05cd..5be5fb0 100644 (file)
@@ -185,6 +185,23 @@ public interface CpsDataPersistenceService {
     List<DataNode> queryDataNodes(String dataspaceName, String anchorName,
                                   String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
+    /**
+     * Get a datanode by cps path.
+     *
+     * @param dataspaceName          dataspace name
+     * @param anchorName             anchor name
+     * @param cpsPath                cps path
+     * @param fetchDescendantsOption defines whether the descendants of the node(s) found by the query should be
+     *                               included in the output
+     * @param queryResultLimit       limits the number of returned entities (if less than 1 returns all)
+     *
+     * @return the data nodes found i.e. 0 or more data nodes
+     */
+    List<DataNode> queryDataNodes(String dataspaceName,
+                                  String anchorName,
+                                  String cpsPath, FetchDescendantsOption fetchDescendantsOption,
+                                  int queryResultLimit);
+
     /**
      * Get data leaf for the given dataspace and anchor by cps path.
      *
index b15ee72..9db4aa4 100644 (file)
@@ -21,7 +21,7 @@
 
 package org.onap.cps.impl
 
-
+import org.onap.cps.api.CpsQueryService
 import org.onap.cps.utils.CpsValidator
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.api.parameters.FetchDescendantsOption
@@ -42,7 +42,7 @@ class CpsQueryServiceImplSpec extends Specification {
         when: 'queryDataNodes is invoked'
             objectUnderTest.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption)
         then: 'the persistence service is called once with the correct parameters'
-            1 * mockCpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption)
+            1 * mockCpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption, CpsQueryService.NO_LIMIT)
         and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName'
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
         where: 'all fetch descendants options are supported'
@@ -50,6 +50,21 @@ class CpsQueryServiceImplSpec extends Specification {
                                        FetchDescendantsOption.DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)]
     }
 
+    def 'Query data nodes by cps path with limit.'() {
+        given: 'a dataspace name, an anchor name and a cps path'
+            def dataspaceName = 'some-dataspace'
+            def anchorName = 'some-anchor'
+            def cpsPath = '/cps-path'
+            def fetchDescendantsOption = FetchDescendantsOption.OMIT_DESCENDANTS
+            def myLimit = 123
+        when: 'queryDataNodes (with limit) is invoked'
+            objectUnderTest.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption, myLimit)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockCpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption, myLimit)
+        and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName'
+            1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+    }
+
     def 'Query data nodes across all anchors by cps path with #fetchDescendantsOption.'() {
         given: 'a dataspace name, an anchor name and a cps path'
             def dataspaceName = 'some-dataspace'
index d1b445f..42fb964 100644 (file)
@@ -457,4 +457,17 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
         and: 'the queried nodes have expected bookstore names'
             assert result.anchorName.toSet() == [BOOKSTORE_ANCHOR_1, BOOKSTORE_ANCHOR_2].toSet()
     }
+
+    def 'Query with a limit of #limit.' () {
+        when:
+            def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories', OMIT_DESCENDANTS, limit)
+        then: 'the expected number of nodes is returned'
+            assert countDataNodesInTree(result) == expectedNumberOfResults
+        where: 'the following parameters are used'
+            limit || expectedNumberOfResults
+            1     || 1
+            2     || 2
+            0     || 5
+            -1    || 5
+    }
 }
index 364127f..acc95ca 100644 (file)
@@ -103,5 +103,4 @@ class QueryPerfTest extends CpsPerfTestBase {
             'direct descendants' | DIRECT_CHILDREN_ONLY    || 0.11           | 8           | 1 + OPENROADM_DEVICES_PER_ANCHOR
             'all descendants'    | INCLUDE_ALL_DESCENDANTS || 1.34           | 400         | 1 + OPENROADM_DEVICES_PER_ANCHOR * OPENROADM_DATANODES_PER_DEVICE
     }
-
 }