Add <,> operators support to cps-path 88/133588/30
authorRudrangi Anupriya <ra00745022@techmahindra.com>
Tue, 30 May 2023 11:50:20 +0000 (17:20 +0530)
committerRudrangi Anupriya <ra00745022@techmahindra.com>
Tue, 30 May 2023 15:02:36 +0000 (20:32 +0530)
Issue-ID: CPS-1273
Change-Id: I5d562463b9a49abfe0436047a637857d10596fff
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/CpsPathComparativeOperator.java [new file with mode: 0644]
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/pom.xml
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 86c1705..c88a822 100644 (file)
@@ -47,13 +47,15 @@ listElementRef :  OB leafCondition ( booleanOperators leafCondition)* CB ;
 
 multipleLeafConditions : OB leafCondition ( booleanOperators leafCondition)* CB ;
 
-leafCondition : AT leafName EQ ( IntegerLiteral | StringLiteral) ;
+leafCondition : AT leafName comparativeOperators ( IntegerLiteral | StringLiteral) ;
 
 leafName : QName ;
 
 booleanOperators : ( KW_AND | KW_OR ) ;
 
-invalidPostFix : (AT | CB | COLONCOLON | EQ ).+ ;
+comparativeOperators : ( EQ | GT | LT | GE | LE ) ;
+
+invalidPostFix : (AT | CB | COLONCOLON | comparativeOperators ).+ ;
 
 /*
  * Lexer Rules
@@ -70,6 +72,10 @@ SLASH : '/' ;
 COMMA : ',' ;
 OP : '(' ;
 CP : ')' ;
+GT : '>' ;
+LT : '<' ;
+GE : '>=' ;
+LE : '<=' ;
 
 // KEYWORDS
 
index f44e310..5c47127 100644 (file)
@@ -25,10 +25,8 @@ import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Queue;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
 import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext;
@@ -59,7 +57,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
 
     final List<String> booleanOperators = new ArrayList<>();
 
-    final Queue<String> booleanOperatorsQueue = new LinkedList<>();
+    final List<String> comparativeOperators = new ArrayList<>();
 
     @Override
     public void exitInvalidPostFix(final CpsPathParser.InvalidPostFixContext ctx) {
@@ -83,25 +81,20 @@ public class CpsPathBuilder extends CpsPathBaseListener {
 
     @Override
     public void exitLeafCondition(final LeafConditionContext ctx) {
-        Object comparisonValue = null;
+        Object comparisonValue;
         if (ctx.IntegerLiteral() != null) {
             comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
-        }
-        if (ctx.StringLiteral() != null) {
-            final boolean wasWrappedInDoubleQuote  = ctx.StringLiteral().getText().startsWith("\"");
+        } else if (ctx.StringLiteral() != null) {
+            final boolean wasWrappedInDoubleQuote = ctx.StringLiteral().getText().startsWith("\"");
             comparisonValue = stripFirstAndLastCharacter(ctx.StringLiteral().getText());
             if (wasWrappedInDoubleQuote) {
                 comparisonValue = String.valueOf(comparisonValue).replace("'", "\\'");
             }
-        } else if (comparisonValue == null) {
-            throw new PathParsingException("Unsupported comparison value encountered in expression" + ctx.getText());
-        }
-        leavesData.put(ctx.leafName().getText(), comparisonValue);
-        final String booleanOperator = booleanOperatorsQueue.poll();
-        appendCondition(normalizedXpathBuilder, ctx.leafName().getText(), booleanOperator, comparisonValue);
-        if (processingAncestorAxis) {
-            appendCondition(normalizedAncestorPathBuilder, ctx.leafName().getText(), booleanOperator, comparisonValue);
+        } else {
+            throw new PathParsingException(
+                    "Unsupported comparison value encountered in expression" + ctx.getText());
         }
+        leafContext(ctx.leafName(), comparisonValue);
     }
 
     @Override
@@ -109,8 +102,13 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         final CpsPathBooleanOperatorType cpsPathBooleanOperatorType = CpsPathBooleanOperatorType.fromString(
                 ctx.getText());
         booleanOperators.add(cpsPathBooleanOperatorType.getValues());
-        booleanOperatorsQueue.add(cpsPathBooleanOperatorType.getValues());
-        cpsPathQuery.setBooleanOperatorsType(booleanOperators);
+    }
+
+    @Override
+    public void exitComparativeOperators(final CpsPathParser.ComparativeOperatorsContext ctx) {
+        final CpsPathComparativeOperator cpsPathComparativeOperator = CpsPathComparativeOperator.fromString(
+                ctx.getText());
+        comparativeOperators.add(cpsPathComparativeOperator.getLabel());
     }
 
     @Override
@@ -174,6 +172,8 @@ public class CpsPathBuilder extends CpsPathBaseListener {
     CpsPathQuery build() {
         cpsPathQuery.setNormalizedXpath(normalizedXpathBuilder.toString());
         cpsPathQuery.setContainerNames(containerNames);
+        cpsPathQuery.setBooleanOperators(booleanOperators);
+        cpsPathQuery.setComparativeOperators(comparativeOperators);
         return cpsPathQuery;
     }
 
@@ -192,14 +192,30 @@ public class CpsPathBuilder extends CpsPathBaseListener {
         }
     }
 
+    private void leafContext(final CpsPathParser.LeafNameContext ctx, final Object comparisonValue) {
+        leavesData.put(ctx.getText(), comparisonValue);
+        appendCondition(normalizedXpathBuilder, ctx.getText(), comparisonValue);
+        if (processingAncestorAxis) {
+            appendCondition(normalizedAncestorPathBuilder, ctx.getText(), comparisonValue);
+        }
+    }
+
     private void appendCondition(final StringBuilder currentNormalizedPathBuilder, final String name,
-                                 final String booleanOperator, final Object value) {
+                                 final Object value) {
         final char lastCharacter = currentNormalizedPathBuilder.charAt(currentNormalizedPathBuilder.length() - 1);
-        currentNormalizedPathBuilder.append(lastCharacter == '[' ? "" : " " + booleanOperator + " ")
-                                    .append("@")
+        final boolean isStartOfExpression = lastCharacter == '[';
+        if (!isStartOfExpression) {
+            currentNormalizedPathBuilder.append(" ").append(getLastElement(booleanOperators)).append(" ");
+        }
+        currentNormalizedPathBuilder.append("@")
                                     .append(name)
-                                    .append("='")
+                                    .append(getLastElement(comparativeOperators))
+                                    .append("'")
                                     .append(value)
                                     .append("'");
     }
+
+    private String getLastElement(final List<String> listOfStrings) {
+        return listOfStrings.get(listOfStrings.size() - 1);
+    }
 }
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathComparativeOperator.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathComparativeOperator.java
new file mode 100644 (file)
index 0000000..c7ffd0d
--- /dev/null
@@ -0,0 +1,64 @@
+/*\r
+ *  ============LICENSE_START=======================================================\r
+ *  Copyright (C) 2023 Tech Mahindra Ltd\r
+ *  ================================================================================\r
+ *  Licensed under the Apache License, Version 2.0 (the "License");\r
+ *  you may not use this file except in compliance with the License.\r
+ *  You may obtain a copy of the License at\r
+ *\r
+ *        http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ *  Unless required by applicable law or agreed to in writing, software\r
+ *  distributed under the License is distributed on an "AS IS" BASIS,\r
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ *  See the License for the specific language governing permissions and\r
+ *  limitations under the License.\r
+ *\r
+ *  SPDX-License-Identifier: Apache-2.0\r
+ *  ============LICENSE_END=========================================================\r
+ */\r
+\r
+package org.onap.cps.cpspath.parser;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+public enum CpsPathComparativeOperator {\r
+    EQ("="),\r
+    GT(">"),\r
+    LT("<"),\r
+    GE(">="),\r
+    LE("<=");\r
+\r
+    private final String label;\r
+\r
+    CpsPathComparativeOperator(final String label) {\r
+        this.label = label;\r
+    }\r
+\r
+    public final String getLabel() {\r
+        return this.label;\r
+    }\r
+\r
+    private static final Map<String, CpsPathComparativeOperator> cpsPathComparativeOperatorPerLabel = new HashMap<>();\r
+\r
+    static {\r
+        for (final CpsPathComparativeOperator cpsPathComparativeOperator : CpsPathComparativeOperator.values()) {\r
+            cpsPathComparativeOperatorPerLabel.put(cpsPathComparativeOperator.label, cpsPathComparativeOperator);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Finds the value of the given enumeration.\r
+     *\r
+     * @param label value of the enum\r
+     * @return a comparativeOperatorType\r
+     */\r
+    public static CpsPathComparativeOperator fromString(final String label) {\r
+        if (!cpsPathComparativeOperatorPerLabel.containsKey(label)) {\r
+            throw new PathParsingException("Incomplete leaf condition (no operator)");\r
+        }\r
+        return cpsPathComparativeOperatorPerLabel.get(label);\r
+    }\r
+}\r
+\r
index 418b5ec..3c3cbcc 100644 (file)
@@ -43,7 +43,8 @@ public class CpsPathQuery {
     private String ancestorSchemaNodeIdentifier = "";
     private String textFunctionConditionLeafName;
     private String textFunctionConditionValue;
-    private List<String> booleanOperatorsType;
+    private List<String> booleanOperators;
+    private List<String> comparativeOperators;
     private String containsFunctionConditionLeafName;
     private String containsFunctionConditionValue;
 
index 9552a4d..9ab5491 100644 (file)
@@ -71,6 +71,10 @@ class CpsPathQuerySpec extends Specification {
             'yang container'                                              | '/cps-path'                                                    || '/cps-path'
             'descendant anywhere'                                         | '//cps-path'                                                   || '//cps-path'
             'descendant with leaf condition'                              | '//cps-path[@key=1]'                                           || "//cps-path[@key='1']"
+            'descendant with leaf condition has ">" operator'             | '//cps-path[@key>9]'                                           || "//cps-path[@key>'9']"
+            'descendant with leaf condition has "<" operator'             | '//cps-path[@key<10]'                                          || "//cps-path[@key<'10']"
+            'descendant with leaf condition has ">=" operator'            | '//cps-path[@key>=8]'                                          || "//cps-path[@key>='8']"
+            'descendant with leaf condition has "<=" operator'            | '//cps-path[@key<=12]'                                         || "//cps-path[@key<='12']"
             'descendant with leaf value and ancestor'                     | '//cps-path[@key=1]/ancestor:parent[@key=1]'                   || "//cps-path[@key='1']/ancestor:parent[@key='1']"
             'parent & child'                                              | '/parent/child'                                                || '/parent/child'
             'parent leaf of type Integer & child'                         | '/parent/child[@code=1]/child2'                                || "/parent/child[@code='1']/child2"
@@ -108,16 +112,22 @@ class CpsPathQuerySpec extends Specification {
             result.descendantName == "child"
             result.leavesData.size() == expectedNumberOfLeaves
         and: 'the given operator(s) returns in the correct order'
-            result.booleanOperatorsType == expectedOperators
+            result.booleanOperators == expectedOperators
+        and: 'the given comparativeOperator(s) returns in the correct order'
+            result.comparativeOperators == expectedComparativeOperator
         where: 'the following data is used'
-            scenario                                                      | cpsPath                                                                          || expectedNumberOfLeaves || expectedOperators
-            'one attribute'                                               | '//child[@common-leaf-name-int=5]'                                               || 1                      || null
-            'more than one attribute has AND operator'                    | '//child[@int-leaf=5 and @leaf-name="leaf value"]'                               || 2                      || ['and']
-            'more than one attribute has OR operator'                     | '//child[@int-leaf=5 or @leaf-name="leaf value"]'                                || 2                      || ['or']
-            'more than one attribute has combinations and operators'      | '//child[@int-leaf=5 and @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2                      || ['and', 'and']
-            'more than one attribute has combinations or operators'       | '//child[@int-leaf=5 or @leaf-name="leaf value" or @leaf-name="leaf value1" ]'   || 2                      || ['or', 'or']
-            'more than one attribute has combinations and/or combination' | '//child[@int-leaf=5 and @leaf-name="leaf value" or @leaf-name="leaf value1" ]'  || 2                      || ['and', 'or']
-            'more than one attribute has combinations or/and combination' | '//child[@int-leaf=5 or @leaf-name="leaf value" and @leaf-name="leaf value1" ]'  || 2                      || ['or', 'and']
+            scenario                                                      | cpsPath                                                                                   || expectedNumberOfLeaves || expectedOperators || expectedComparativeOperator
+            'one attribute'                                               | '//child[@common-leaf-name-int=5]'                                                        || 1                      || []                || ['=']
+            'more than one attribute has AND operator'                    | '//child[@int-leaf=5 and @leaf-name="leaf value"]'                                        || 2                      || ['and']           || ['=', '=']
+            'more than one attribute has OR operator'                     | '//child[@int-leaf=5 or @leaf-name="leaf value"]'                                         || 2                      || ['or']            || ['=', '=']
+            'more than one attribute has combinations AND operators'      | '//child[@int-leaf=5 and @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]'   || 3                      || ['and', 'and']    || ['=', '=', '=']
+            'more than one attribute has combinations OR operators'       | '//child[@int-leaf=5 or @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]'     || 3                      || ['or', 'or']      || ['=', '=', '=']
+            'more than one attribute has combinations AND/OR combination' | '//child[@int-leaf=5 and @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]'    || 3                      || ['and', 'or']     || ['=', '=', '=']
+            'more than one attribute has combinations OR/AND combination' | '//child[@int-leaf=5 or @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]'    || 3                      || ['or', 'and']     || ['=', '=', '=']
+            'more than one attribute has AND/> operators'                 | '//child[@int-leaf>15 and @leaf-name="leaf value"]'                                       || 2                      || ['and']           || ['>', '=']
+            'more than one attribute has OR/< operators'                  | '//child[@int-leaf<5 or @leaf-name="leaf value"]'                                         || 2                      || ['or']            || ['<', '=']
+            'more than one attribute has combinations AND/>= operators'   | '//child[@int-leaf>=18 and @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 3                      || ['and', 'and']    || ['>=', '=', '=']
+            'more than one attribute has combinations OR/<= operators'    | '//child[@int-leaf<=25 or @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]'   || 3                      || ['or', 'or']      || ['<=', '=', '=']
     }
 
     def 'Parse #scenario cps path with text function condition'() {
index 25dc91c..aa86a7f 100644 (file)
@@ -33,7 +33,7 @@
     <artifactId>cps-ri</artifactId>\r
 \r
     <properties>\r
-        <minimum-coverage>0.54</minimum-coverage>\r
+        <minimum-coverage>0.53</minimum-coverage>\r
         <!-- Additional coverage is provided by integration-test module -->\r
     </properties>\r
 \r
index 72750dc..ba94d56 100644 (file)
@@ -23,7 +23,6 @@ package org.onap.cps.spi.repository;
 
 import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 import java.util.Queue;
 import javax.persistence.EntityManager;
@@ -36,6 +35,7 @@ import org.onap.cps.cpspath.parser.CpsPathQuery;
 import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.FragmentEntity;
+import org.onap.cps.spi.exceptions.CpsPathException;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Component;
 
@@ -141,25 +141,40 @@ public class FragmentQueryBuilder {
 
     private void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
         if (cpsPathQuery.hasLeafConditions()) {
-            sqlStringBuilder.append(" AND (");
-            final List<String> queryBooleanOperatorsType = cpsPathQuery.getBooleanOperatorsType();
-            final Queue<String> booleanOperatorsQueue = (queryBooleanOperatorsType == null) ? null : new LinkedList<>(
-                queryBooleanOperatorsType);
-            cpsPathQuery.getLeavesData().entrySet().forEach(entry -> {
-                sqlStringBuilder.append(" attributes @> ");
-                sqlStringBuilder.append("'");
-                sqlStringBuilder.append(jsonObjectMapper.asJsonString(entry));
-                sqlStringBuilder.append("'");
-                if (!(booleanOperatorsQueue == null || booleanOperatorsQueue.isEmpty())) {
-                    sqlStringBuilder.append(" ");
-                    sqlStringBuilder.append(booleanOperatorsQueue.poll());
-                    sqlStringBuilder.append(" ");
-                }
-            });
-            sqlStringBuilder.append(")");
+            queryLeafConditions(cpsPathQuery, sqlStringBuilder);
         }
     }
 
+    private void queryLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
+        sqlStringBuilder.append(" AND (");
+        final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators());
+        final Queue<String> comparativeOperatorQueue = new LinkedList<>(cpsPathQuery.getComparativeOperators());
+        cpsPathQuery.getLeavesData().entrySet().forEach(entry -> {
+            final String nextComparativeOperator = comparativeOperatorQueue.poll();
+            if (entry.getValue() instanceof Integer) {
+                sqlStringBuilder.append("(attributes ->> ");
+                sqlStringBuilder.append("'").append(entry.getKey()).append("')\\:\\:int");
+                sqlStringBuilder.append(" ").append(nextComparativeOperator).append(" ");
+                sqlStringBuilder.append("'").append(jsonObjectMapper.asJsonString(entry.getValue())).append("'");
+            } else {
+                if ("=".equals(nextComparativeOperator)) {
+                    sqlStringBuilder.append(" attributes @> ");
+                    sqlStringBuilder.append("'");
+                    sqlStringBuilder.append(jsonObjectMapper.asJsonString(entry));
+                    sqlStringBuilder.append("'");
+                } else {
+                    throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer ");
+                }
+            }
+            if (!booleanOperatorsQueue.isEmpty()) {
+                sqlStringBuilder.append(" ");
+                sqlStringBuilder.append(booleanOperatorsQueue.poll());
+                sqlStringBuilder.append(" ");
+            }
+        });
+        sqlStringBuilder.append(")");
+    }
+
     private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery,
                                                  final StringBuilder sqlStringBuilder,
                                                  final Map<String, Object> queryParameters) {
index d64617c..233c58f 100644 (file)
@@ -55,13 +55,13 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
 
     def 'Cps Path query using combinations of OR operator #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)
+            def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
         then: 'the result contains expected number of nodes'
             assert result.size() == expectedResultSize
         and: 'the cps-path of queryDataNodes has the expectedLeaves'
             assert result.leaves.sort() == expectedLeaves.sort()
         where: 'the following data is used'
-            scenario                                | cpspath                                                          || expectedResultSize | expectedLeaves
+            scenario                                | cpsPath                                                          || expectedResultSize | expectedLeaves
             'the "OR" condition'                    | '//books[@lang="English" or @price=15]'                          || 6                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
                                                                                                                                                 [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]],
                                                                                                                                                 [lang: "English", price: 14, title: "The Light Fantastic", authors: ["Terry Pratchett"], editions: [1986]],
@@ -78,6 +78,29 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             'combination of OR/AND'                 | '//books[@title="Annihilation" or @price=39 and @lang="arabic"]' || 1                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]]]
     }
 
+    def 'cps-path query using combinations of Comparative Operators #scenario.'() {
+        when: 'a query is executed to get response by the given cpsPath'
+            def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
+        then: 'the result contains 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
+            'the ">" condition'                              | '//books[@price>13 ]'                                              || 5                  | ['A Book with No Language', 'Annihilation', 'Debian GNU/Linux', 'The Gruffalo', 'The Light Fantastic']
+            'the "<" condition '                             | '//books[@price<15]'                                               || 5                  | ['Good Omens', 'Logarithm tables', 'Matilda', 'The Colour of Magic', 'The Light Fantastic']
+            'the "<=" condition'                             | '//books[@price<=15]'                                              || 7                  | ['Annihilation', 'Good Omens', 'Logarithm tables', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic']
+            'the ">=" condition'                             | '//books[@price>=20]'                                              || 2                  | ['A Book with No Language', 'Debian GNU/Linux']
+            'the "<" condition  where result does not exist' | '//books[@price<5]'                                                || 0                  | []
+            'the ">" condition  where result does not exist' | '//books[@price>1000]'                                             || 0                  | []
+            'the ">" condition with AND condition'           | '//books[@price>13 and @title="A Book with No Language"]'          || 1                  | ['A Book with No Language']
+            'the "<" condition with OR condition'            | '//books[@price<10 or @lang="German"]'                             || 1                  | ['Debian GNU/Linux']
+            'the "<=" condition with AND/OR condition'       | '//books[@price<=15 and @title="Annihilation" or @lang="Spanish"]' || 1                  | ['Annihilation']
+            'the ">=" condition with OR/AND condition'       | '//books[@price>=13 or @lang="Spanish" and @title="Good Omens"]'   || 6                  | ['A Book with No Language', 'Annihilation', 'Good Omens', 'Debian GNU/Linux', 'The Gruffalo', 'The Light Fantastic']
+            'Mix of integer and string condition '           | '//books[@lang="German" and @price>38]'                            || 1                  | ['Debian GNU/Linux']
+    }
+
     def 'Cps Path query for leaf value(s) with #scenario.'() {
         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, fetchDescendantsOption)
@@ -170,6 +193,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
         where: 'the following data is used'
             scenario                                                   | cpsPath                                                                || expectedBookTitles
             'one leaf'                                                 | '//books[@price=14]'                                                   || ['The Light Fantastic']
+            'one leaf with ">" condition'                              | '//books[@price>14]'                                                   || ['A Book with No Language', 'Annihilation', 'Debian GNU/Linux', 'The Gruffalo']
             'one text'                                                 | '//books/authors[text()="Terry Pratchett"]'                            || ['Good Omens', 'The Colour of Magic', 'The Light Fantastic']
             'more than one leaf'                                       | '//books[@price=12 and @lang="English"]'                               || ['The Colour of Magic']
             'more than one leaf has "OR" condition'                    | '//books[@lang="English" or @price=15]'                                || ['Annihilation', 'Good Omens', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic']
@@ -229,9 +253,13 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
 
     def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
         when: 'trying to execute a query with a syntax (parsing) error'
-            objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
+            objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
         then: 'a cps path exception is thrown'
             thrown(CpsPathException)
+        where: 'the following data is used'
+            scenario                           | cpsPath
+            'cpsPath that cannot be parsed'    | 'cpsPath that cannot be parsed'
+            'String with comparative operator' | '//books[@lang>"German" and @price>10]'
     }
 
     def 'Cps Path query across anchors with #scenario.'() {