Add contains condition support to cps-path 45/134045/12
authorRudrangi Anupriya <ra00745022@techmahindra.com>
Mon, 17 Apr 2023 08:49:46 +0000 (14:19 +0530)
committerRudrangi Anupriya <ra00745022@techmahindra.com>
Fri, 21 Apr 2023 08:14:18 +0000 (13:44 +0530)
Issue-ID: CPS-1272
Change-Id: Ic81d1322cacc64a8752916324b801d02be47d34f
Signed-off-by: Rudrangi Anupriya <ra00745022@techmahindra.com>
cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy

index d471811..86c1705 100644 (file)
@@ -21,7 +21,7 @@
 
 grammar CpsPath ;
 
-cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? ancestorAxis? invalidPostFix?;
+cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? invalidPostFix?;
 
 ancestorAxis : SLASH KW_ANCESTOR COLONCOLON ancestorPath ;
 
@@ -29,6 +29,8 @@ ancestorPath : yangElement ( SLASH yangElement)* ;
 
 textFunctionCondition : SLASH leafName OB KW_TEXT_FUNCTION EQ StringLiteral CB ;
 
+containsFunctionCondition : OB KW_CONTAINS_FUNCTION OP AT leafName COMMA StringLiteral CP CB ;
+
 parent : ( SLASH yangElement)* ;
 
 prefix : parent SLASH containerName ;
@@ -65,6 +67,9 @@ COLONCOLON : '::' ;
 EQ : '=' ;
 OB : '[' ;
 SLASH : '/' ;
+COMMA : ',' ;
+OP : '(' ;
+CP : ')' ;
 
 // KEYWORDS
 
@@ -72,6 +77,7 @@ KW_ANCESTOR : 'ancestor' ;
 KW_AND : 'and' ;
 KW_TEXT_FUNCTION: 'text()' ;
 KW_OR : 'or' ;
+KW_CONTAINS_FUNCTION: 'contains' ;
 
 IntegerLiteral : FragDigits ;
 // Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440
index 4299d13..854450c 100644 (file)
@@ -149,6 +149,12 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         cpsPathQuery.setTextFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
     }
 
+    @Override
+    public void exitContainsFunctionCondition(final CpsPathParser.ContainsFunctionConditionContext ctx) {
+        cpsPathQuery.setContainsFunctionConditionLeafName(ctx.leafName().getText());
+        cpsPathQuery.setContainsFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
+    }
+
     @Override
     public void enterListElementRef(final CpsPathParser.ListElementRefContext ctx) {
         normalizedXpathBuilder.append(OPEN_BRACKET);
index 2c96d91..418b5ec 100644 (file)
@@ -44,6 +44,8 @@ public class CpsPathQuery {
     private String textFunctionConditionLeafName;
     private String textFunctionConditionValue;
     private List<String> booleanOperatorsType;
+    private String containsFunctionConditionLeafName;
+    private String containsFunctionConditionValue;
 
     /**
      * Returns a cps path query.
@@ -82,6 +84,15 @@ public class CpsPathQuery {
         return textFunctionConditionLeafName != null;
     }
 
+    /**
+     * Has contains function condition been included in cpsPath.
+     *
+     * @return boolean value.
+     */
+    public boolean hasContainsFunctionCondition() {
+        return containsFunctionConditionLeafName != null;
+    }
+
     /**
      * Returns boolean indicating xpath is an absolute path to a list element.
      *
index 153dfbe..96fdf88 100644 (file)
@@ -136,6 +136,17 @@ class CpsPathQuerySpec extends Specification {
             'descendant with leaf value and ancestor' | '//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent' || true                 | true
     }
 
+    def 'Parse #scenario cps path with contains function condition'() {
+        when: 'the given cps path is parsed'
+            def result = CpsPathQuery.createFrom('//someContainer[contains(@lang,"en")]')
+        then: 'the query has the right xpath type'
+            result.cpsPathPrefixType == DESCENDANT
+        and: 'the right contains function condition is set'
+            result.hasContainsFunctionCondition()
+            result.containsFunctionConditionLeafName == 'lang'
+            result.containsFunctionConditionValue == 'en'
+    }
+
     def 'Parse cps path with error: #scenario.'() {
         when: 'the given cps path is parsed'
             CpsPathQuery.createFrom(cpsPath)
index ca6e18b..ab9c02e 100644 (file)
@@ -98,6 +98,7 @@ public class FragmentQueryBuilder {
             sqlStringBuilder.append(")");
         }
         addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
+        addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
         final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
         setQueryParameters(query, queryParameters);
         return query;
@@ -160,6 +161,16 @@ public class FragmentQueryBuilder {
         }
     }
 
+    private static void addContainsFunctionCondition(final CpsPathQuery cpsPathQuery,
+                                                     final StringBuilder sqlStringBuilder,
+                                                     final Map<String, Object> queryParameters) {
+        if (cpsPathQuery.hasContainsFunctionCondition()) {
+            sqlStringBuilder.append(" AND attributes ->> :containsLeafName LIKE CONCAT('%',:containsValue,'%') ");
+            queryParameters.put("containsLeafName", cpsPathQuery.getContainsFunctionConditionLeafName());
+            queryParameters.put("containsValue", cpsPathQuery.getContainsFunctionConditionValue());
+        }
+    }
+
     private static void setQueryParameters(final Query query, final Map<String, Object> queryParameters) {
         for (final Map.Entry<String, Object> queryParameter : queryParameters.entrySet()) {
             query.setParameter(queryParameter.getKey(), queryParameter.getValue());
index bd39605..38bb4de 100644 (file)
@@ -147,6 +147,22 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             'text condition on key containing /'     | '//books/title[text()="Debian GNU/Linux"]'  || ["Debian GNU/Linux"]
     }
 
+    def 'Query for attribute by cps path using contains condition #scenario.'() {
+        when: 'a query is executed to get response by the given cps path'
+            def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
+        then: 'the cps-path of queryDataNodes has expected number of nodes'
+            assert result.size() == expectedResultsize
+        and: 'xpaths of the retrieved data nodes are as expected'
+            def bookTitles = result.collect { it.getLeaves().get('title') }
+            assert bookTitles.sort() == expectedBookTitles.sort()
+        where: 'the following data is used'
+            scenario                                 | cpsPath                           | expectedResultsize || expectedBookTitles
+            'contains condition with leaf'           | '//books[contains(@title,"Mat")]' | 1                  || ["Matilda"]
+            'contains condition with case-sensitive' | '//books[contains(@title,"Ti")]'  | 0                  || []
+            'contains condition with Integer Value'  | '//books[contains(@price,"15")]'  | 2                  || ["Annihilation", "The Gruffalo"]
+            'contains condition with No-value'       | '//books[contains(@title,"")]'    | 9                  || ["A Book with No Language", "Annihilation", "Debian GNU/Linux", "Good Omens", "Logarithm tables", "Matilda", "The Colour of Magic", "The Gruffalo", "The Light Fantastic"]
+    }
+
     def 'Cps Path query using descendant anywhere with #scenario condition for a container element.'() {
         when: 'a query is executed to get a data node by the given cps path'
             def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
@@ -164,6 +180,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             'more than one leaf has multiple OR'                       | '//books[ @title="Matilda" or @price=15 or @edition=2006]' || ['Annihilation', 'Matilda', 'The Gruffalo']
             'leaves reversed in order'                                 | '//books[@lang="English" and @price=12]'                   || ['The Colour of Magic']
             'leaf and text'                                            | '//books[@price=14]/authors[text()="Terry Pratchett"]'     || ['The Light Fantastic']
+            'leaf and contains'                                        | '//books[contains(@price,"13")]'                           || ['Good Omens']
     }
 
     def 'Cps Path query using descendant anywhere with #scenario condition(s) for a list element.'() {
@@ -195,6 +212,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             'ancestor combined with text condition'     | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"]
             'ancestor with parent that does not exist'  | '//books/ancestor::parentDoesNoExist/categories'      || []
             'ancestor does not exist'                   | '//books/ancestor::ancestorDoesNotExist'              || []
+            'ancestor combined with contains condition' | '//books[contains(@title,"Mat")]/ancestor::bookstore' || ["/bookstore"]
     }
 
     def 'Query for attribute by cps path of type ancestor with #scenario descendants.'() {