Merge "Get Node API fix for attribute values with '/'"
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java
index 47a3e8f..654c1c0 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java
@@ -38,8 +38,8 @@
 @Slf4j
 public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery {
 
-    public static final String SIMILAR_TO_ABSOLUTE_PATH_PREFIX = "%/";
-    public static final String SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[[^/]*])?";
+    public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/";
+    public static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$";
 
     @PersistenceContext
     private EntityManager entityManager;
@@ -51,8 +51,8 @@
         final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId");
         final Map<String, Object> queryParameters = new HashMap<>();
         queryParameters.put("anchorId", anchorId);
-        sqlStringBuilder.append(" AND xpath SIMILAR TO :xpathRegex");
-        final String xpathRegex = getSimilarToXpathSqlRegex(cpsPathQuery);
+        sqlStringBuilder.append(" AND xpath ~ :xpathRegex");
+        final String xpathRegex = getXpathSqlRegex(cpsPathQuery);
         queryParameters.put("xpathRegex", xpathRegex);
         if (cpsPathQuery.hasLeafConditions()) {
             sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb");
@@ -68,15 +68,15 @@
         return fragmentEntities;
     }
 
-    private static String getSimilarToXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
+    private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
         final StringBuilder xpathRegexBuilder = new StringBuilder();
         if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
             xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix()));
         } else {
-            xpathRegexBuilder.append(SIMILAR_TO_ABSOLUTE_PATH_PREFIX);
+            xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX);
             xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName()));
         }
-        xpathRegexBuilder.append(SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX);
+        xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX);
         return xpathRegexBuilder.toString();
     }
 
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
index 36b378a..be2f8fe 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
@@ -50,7 +50,7 @@
             scenario                      | cpsPath                                                      | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes
             'String and no descendants'   | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS         || 1                           | 0
             'Integer and descendants'     | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]'      | INCLUDE_ALL_DESCENDANTS  || 1                           | 1
-            'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 2                           | 0
+            'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 3                           | 0
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
@@ -91,16 +91,19 @@
                 assert result[i].getXpath() == expectedXPaths[i]
             }
         where: 'the following data is used'
-            scenario                                                 | cpsPath                                 || expectedXPaths
-            'fully unique descendant name'                           | '//categories[@code=2]'                 || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"]
-            'descendant name match end of other node'                | '//book'                                || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
-            'descendant with text condition on leaf'                 | '//book/title[text()="Chapters"]'       || ["/shops/shop[@id='1']/categories[@code='2']/book"]
-            'descendant with text condition case mismatch'           | '//book/title[text()="chapters"]'       || []
-            'descendant with text condition on int leaf'             | '//book/price[text()="5"]'              || ["/shops/shop[@id='1']/categories[@code='1']/book"]
-            'descendant with text condition on leaf-list'            | '//book/labels[text()="special offer"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
-            'descendant with text condition partial match'           | '//book/labels[text()="special"]'       || []
-            'descendant with text condition (existing) empty string' | '//book/labels[text()=""]'              || ["/shops/shop[@id='1']/categories[@code='1']/book"]
-            'descendant with text condition on int leaf-list'        | '//book/editions[text()="2000"]'        || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+            scenario                                                 | cpsPath                                                || expectedXPaths
+            'fully unique descendant name'                           | '//categories[@code=2]'                                || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"]
+            'descendant name match end of other node'                | '//book'                                               || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
+            'descendant with text condition on leaf'                 | '//book/title[text()="Chapters"]'                      || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+            'descendant with text condition case mismatch'           | '//book/title[text()="chapters"]'                      || []
+            'descendant with text condition on int leaf'             | '//book/price[text()="5"]'                             || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+            'descendant with text condition on leaf-list'            | '//book/labels[text()="special offer"]'                || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+            'descendant with text condition partial match'           | '//book/labels[text()="special"]'                      || []
+            'descendant with text condition (existing) empty string' | '//book/labels[text()=""]'                             || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+            'descendant with text condition on int leaf-list'        | '//book/editions[text()="2000"]'                       || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+            'descendant name match of leaf containing /'             | '//categories/type[text()="text/with/slash"]'          || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
+            'descendant with text condition on leaf containing /'    | '//categories[@code=\'string/with/slash\']'            || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
+            'descendant with text condition on leaf containing ['    | '//book/author[@Address="String[with]square[bracket]"]'|| []
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
diff --git a/cps-ri/src/test/resources/data/cps-path-query.sql b/cps-ri/src/test/resources/data/cps-path-query.sql
index b6000cf..fa711cb 100644
--- a/cps-ri/src/test/resources/data/cps-path-query.sql
+++ b/cps-ri/src/test/resources/data/cps-path-query.sql
@@ -55,10 +55,12 @@
     (2, 1001, 1003, 1, '/shops/shop[@id=''1'']', '{"id" : 1, "type" : "bookstore"}'),
     (3, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''1'']', '{"code" : 1, "type" : "bookstore", "name": "SciFi"}'),
     (4, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''2'']', '{"code" : 2, "type" : "bookstore", "name": "Fiction"}'),
+    (31, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''string/with/slash/'']', '{"code" : "string/with/slash", "type" : "text/with/slash", "name": "Fiction"}'),
     (5, 1001, 1003, 3, '/shops/shop[@id=''1'']/categories[@code=''1'']/book', '{"price" :  5, "title" : "Dune", "labels" : ["special offer","classics",""]}'),
     (6, 1001, 1003, 4, '/shops/shop[@id=''1'']/categories[@code=''2'']/book', '{"price" : 15, "title" : "Chapters", "editions" : [2000,2010,2020]}'),
     (7, 1001, 1003, 5, '/shops/shop[@id=''1'']/categories[@code=''1'']/book/author[@FirstName=''Joe'' and @Surname=''Bloggs'']', '{"FirstName" : "Joe", "Surname": "Bloggs","title": "Dune"}'),
-    (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}');
+    (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}'),
+    (32, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Address=''string[with]square[brackets]'']', '{"FirstName" : "Joe", "Address": "string[with]square[brackets]","title": "Chapters"}');
 
     INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
     (9, 1001, 1003, 1, '/shops/shop[@id=''2'']', '{"type" : "bookstore"}'),