Ordering of leaf elements to support combination of AND/OR in cps-path 14/134314/4
authorRudrangi Anupriya <ra00745022@techmahindra.com>
Tue, 25 Apr 2023 07:43:16 +0000 (13:13 +0530)
committerRudrangi Anupriya <ra00745022@techmahindra.com>
Tue, 25 Apr 2023 16:15:40 +0000 (21:45 +0530)
Issue-ID: CPS-1629
Change-Id: Ib9df87e8171f4d53c49d80d27e3c6cf75aa3b209
Signed-off-by: Rudrangi Anupriya <ra00745022@techmahindra.com>
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
docs/cps-path.rst
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy

index 854450c..f44e310 100644 (file)
@@ -24,7 +24,7 @@ package org.onap.cps.cpspath.parser;
 import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -47,7 +47,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
 
     final CpsPathQuery cpsPathQuery = new CpsPathQuery();
 
-    final Map<String, Object> leavesData = new HashMap<>();
+    final Map<String, Object> leavesData = new LinkedHashMap<>();
 
     final StringBuilder normalizedXpathBuilder = new StringBuilder();
 
index 96fdf88..9552a4d 100644 (file)
@@ -84,6 +84,8 @@ class CpsPathQuerySpec extends Specification {
             'parent & child with more than one attribute has OR operator' | '/parent/child[@key1=1 or @key2="abc"]/child2'                 || "/parent/child[@key1='1' or @key2='abc']/child2"
             'parent & child with multiple AND  operators'                 | '/parent/child[@key1=1 and @key2="abc" and @key="xyz"]/child2' || "/parent/child[@key1='1' and @key2='abc' and @key='xyz']/child2"
             'parent & child with multiple OR  operators'                  | '/parent/child[@key1=1 or @key2="abc" or @key="xyz"]/child2'   || "/parent/child[@key1='1' or @key2='abc' or @key='xyz']/child2"
+            'parent & child with multiple AND/OR combination'             | '/parent/child[@key1=1 and @key2="abc" or @key="xyz"]/child2'  || "/parent/child[@key1='1' and @key2='abc' or @key='xyz']/child2"
+            'parent & child with multiple OR/AND combination'             | '/parent/child[@key1=1 or @key2="abc" and @key="xyz"]/child2'  || "/parent/child[@key1='1' or @key2='abc' and @key='xyz']/child2"
     }
 
     def 'Parse xpath to form the Normalized xpath containing #scenario.'() {
@@ -105,14 +107,17 @@ class CpsPathQuerySpec extends Specification {
         and: 'the right parameters are set'
             result.descendantName == "child"
             result.leavesData.size() == expectedNumberOfLeaves
+        and: 'the given operator(s) returns in the correct order'
             result.booleanOperatorsType == expectedOperators
         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/or operator' | '//child[@int-leaf=5 and @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2                      || ['and', 'and']
-            'more than one attribute has combinations or/and operator' | '//child[@int-leaf=5 or @leaf-name="leaf value" or @leaf-name="leaf value1" ]'   || 2                      || ['or', 'or']
+            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']
     }
 
     def 'Parse #scenario cps path with text function condition'() {
@@ -136,7 +141,7 @@ 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'() {
+    def 'Parse 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'
index f321adf..252310d 100644 (file)
@@ -1,6 +1,7 @@
 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
 .. http://creativecommons.org/licenses/by/4.0
 .. Copyright (C) 2021-2022 Nordix Foundation
+.. Modifications Copyright (C) 2023 TechMahindra Ltd
 
 .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
 .. _path:
@@ -222,7 +223,7 @@ descendant-path
 leaf-conditions
 ---------------
 
-**Syntax**: ``<xpath> '[' @<leaf-name1> '=' <leaf-value1> ( ' and ' @<leaf-name> '=' <leaf-value> )* ']'``
+**Syntax**: ``<xpath> '[' @<leaf-name1> '=' <leaf-value1> ( ' <and|or> ' @<leaf-name> '=' <leaf-value> )* ']'``
   - ``xpath``: Absolute or descendant or xpath to the (list) node which elements will be queried.
   - ``leaf-name``: The name of the leaf which value needs to be compared.
   - ``leaf-value``: The required value of the leaf.
@@ -232,10 +233,13 @@ leaf-conditions
   - ``//categories[@name="Kids"]``
   - ``//categories[@name='Kids']``
   - ``//categories[@code='1']/books/book[@title='Dune' and @price=5]``
+  - ``//categories[@code='1']/books/book[@title='xyz' or @price=15]``
   - ``//categories[@code=1]``
 **Limitations**
   - Only the last list or container can be queried leaf values. Any ancestor list will have to be referenced by its key name-value pair(s).
-  - Multiple attributes can only be combined using ``and``. ``or`` and bracketing is not supported.
+  - When mixing ``and/or`` operators, ``and`` has precedence over ``or`` . So ``and`` operators get evaluated first.
+  - Bracketing is not supported.
+  - Leaf names are not validated so ``or`` operations with invalid leaf names will silently be ignored.
   - Only leaves can be used, leaf-list are not supported.
   - Only string and integer values are supported, boolean and float values are not supported.
   - The key should be supplied with correct data type for it to be queried from DB. In the last example above the attribute code is of type
index 38bb4de..d8f7147 100644 (file)
@@ -63,19 +63,21 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             assert result.leaves.sort() == expectedLeaves.sort()
             println(expectedLeaves.toArray())
         where: 'the following data is used'
-            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]],
-                                                                                                                                          [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]],
-                                                                                                                                          [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]],
-                                                                                                                                          [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]]
-            'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]'                       || 2                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
-                                                                                                                                          [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
-            'combination of multiple AND'           | '//books[@lang="English" and @price=15 and @edition=1983]' || 0                  | []
-            'combination of multiple OR'            | '//books[ @title="Matilda" or @price=15 or @edition=1983]' || 3                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
-                                                                                                                                          [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]],
-                                                                                                                                          [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
+            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]],
+                                                                                                                                                [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]],
+                                                                                                                                                [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]],
+                                                                                                                                                [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]]
+            'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]'                             || 2                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
+                                                                                                                                                [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
+            'combination of multiple AND'           | '//books[@lang="English" and @price=15 and @edition=1983]'       || 0                  | []
+            'combination of multiple OR'            | '//books[ @title="Matilda" or @price=15 or @edition=1983]'       || 3                  | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
+                                                                                                                                                [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]],
+                                                                                                                                                [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
+            'combination of AND/OR'                 | '//books[@edition=1983 and @price=15 or @title="Good Omens"]'    || 1                  | [[lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]]]
+            '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 for leaf value(s) with #scenario.'() {
@@ -170,17 +172,19 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
             def bookTitles = result.collect { it.getLeaves().get('title') }
             assert bookTitles.sort() == expectedBookTitles.sort()
         where: 'the following data is used'
-            scenario                                                   | cpsPath                                                    || expectedBookTitles
-            'one leaf'                                                 | '//books[@price=14]'                                       || ['The Light Fantastic']
-            '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']
-            'more than one leaf has "OR" condition with non-json data' | '//books[@title="xyz" or @price=13]'                       || ['Good Omens']
-            'more than one leaf has multiple AND'                      | '//books[@lang="English" and @price=13 and @edition=1983]' || []
-            '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']
+            scenario                                                   | cpsPath                                                                || expectedBookTitles
+            'one leaf'                                                 | '//books[@price=14]'                                                   || ['The Light Fantastic']
+            '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']
+            'more than one leaf has "OR" condition with non-json data' | '//books[@title="xyz" or @price=13]'                                   || ['Good Omens']
+            'more than one leaf has multiple AND'                      | '//books[@lang="English" and @price=13 and @edition=1983]'             || []
+            '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']
+            'more than one leaf has combination of AND/OR'             | '//books[@edition=1983 and @price=13 or @title="Good Omens"]'          || ['Good Omens']
+            'more than one leaf has OR/AND'                            | '//books[@title="The Light Fantastic" or @price=11 and @edition=1983]' || ['The Light Fantastic']
+            '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.'() {