ATLAS-4112 : Basic Search : Attribute search of QualifiedName beginswith operator not returning results when the value ends with a digit+dot

Signed-off-by: Sarath Subramanian <sarath@apache.org>
diff --git a/intg/src/main/java/org/apache/atlas/type/AtlasStructType.java b/intg/src/main/java/org/apache/atlas/type/AtlasStructType.java
index 0509809..d89aca2 100644
--- a/intg/src/main/java/org/apache/atlas/type/AtlasStructType.java
+++ b/intg/src/main/java/org/apache/atlas/type/AtlasStructType.java
@@ -960,10 +960,14 @@
         }
 
         public static String escapeIndexQueryValue(String value) {
-            return escapeIndexQueryValue(value, false);
+            return escapeIndexQueryValue(value, false, true);
         }
 
         public static String escapeIndexQueryValue(String value, boolean allowWildcard) {
+            return escapeIndexQueryValue(value, allowWildcard, true);
+        }
+
+        public static String escapeIndexQueryValue(String value, boolean allowWildcard, boolean shouldQuote) {
             String  ret        = value;
             boolean quoteValue = false;
 
@@ -977,7 +981,7 @@
                         sb.append('\\');
                     }
 
-                    if (!quoteValue) {
+                    if (shouldQuote && !quoteValue) {
                         quoteValue = shouldQuoteIndexQueryForChar(c);
                     }
 
@@ -987,7 +991,7 @@
                 ret = sb.toString();
             } else if (value != null) {
                 for (int i = 0; i < value.length(); i++) {
-                    if (shouldQuoteIndexQueryForChar(value.charAt(i))) {
+                    if (shouldQuote && shouldQuoteIndexQueryForChar(value.charAt(i))) {
                         quoteValue = true;
 
                         break;
@@ -1047,12 +1051,50 @@
                 case ':':
                 case '\\':
                 case '/':
+                case ' ':
                     return true;
             }
 
             return false;
         }
 
+        public static boolean hastokenizeChar(String value) {
+            if (value != null) {
+                for (int i = 0; i < value.length(); i++) {
+                    if (hastokenizeChar(value, i)) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+
+        private static boolean hastokenizeChar(String value, int i) {
+            char c = value.charAt(i);
+            if (!Character.isLetterOrDigit(c)) {
+                switch (c) {
+                    case '_':
+                        return false;
+                    case '.':
+                    case ':':
+                    case '\'':
+                        if (i > 0 && !Character.isAlphabetic(value.charAt(i - 1))) {
+                            return true;
+                        }
+                        if (i < value.length() - 1 && !Character.isAlphabetic(value.charAt(i + 1))) {
+                            return true;
+                        }
+                        return false;
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
         private static boolean shouldQuoteIndexQueryForChar(char c) {
             switch (c) {
                 case '@':
diff --git a/repository/src/main/java/org/apache/atlas/discovery/SearchProcessor.java b/repository/src/main/java/org/apache/atlas/discovery/SearchProcessor.java
index 01daf53..275fc78 100644
--- a/repository/src/main/java/org/apache/atlas/discovery/SearchProcessor.java
+++ b/repository/src/main/java/org/apache/atlas/discovery/SearchProcessor.java
@@ -26,6 +26,7 @@
 import org.apache.atlas.model.discovery.SearchParameters.FilterCriteria;
 import org.apache.atlas.model.discovery.SearchParameters.FilterCriteria.Condition;
 import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
+import org.apache.atlas.model.typedef.AtlasStructDef;
 import org.apache.atlas.repository.Constants;
 import org.apache.atlas.repository.graph.GraphHelper;
 import org.apache.atlas.repository.graphdb.AtlasGraphQuery;
@@ -459,26 +460,31 @@
     }
 
     private boolean isIndexSearchable(FilterCriteria filterCriteria, AtlasStructType structType) throws AtlasBaseException {
-        String      qualifiedName = structType.getVertexPropertyName(filterCriteria.getAttributeName());
-        Set<String> indexedKeys   = context.getIndexedKeys();
-        boolean     ret           = indexedKeys != null && indexedKeys.contains(qualifiedName);
+        String      attributeName  = filterCriteria.getAttributeName();
+        String      attributeValue = filterCriteria.getAttributeValue();
+        AtlasType   attributeType  = structType.getAttributeType(attributeName);
+        String      typeName       = attributeType.getTypeName();
+        String      qualifiedName  = structType.getVertexPropertyName(attributeName);
+        Set<String> indexedKeys    = context.getIndexedKeys();
+        boolean     ret            = indexedKeys != null && indexedKeys.contains(qualifiedName);
+
+        SearchParameters.Operator                  operator  = filterCriteria.getOperator();
+        AtlasStructDef.AtlasAttributeDef.IndexType indexType = structType.getAttributeDef(attributeName).getIndexType();
 
         if (ret) { // index exists
             // for string type attributes, don't use index query in the following cases:
             //   - operation is NEQ, as it might return fewer entries due to tokenization of vertex property value
             //   - value-to-compare has special characters
-            AtlasType attributeType = structType.getAttributeType(filterCriteria.getAttributeName());
-
-            if (AtlasBaseTypeDef.ATLAS_TYPE_STRING.equals(attributeType.getTypeName())) {
-                if (filterCriteria.getOperator() == SearchParameters.Operator.NEQ || filterCriteria.getOperator() == SearchParameters.Operator.NOT_CONTAINS) {
+            if (AtlasBaseTypeDef.ATLAS_TYPE_STRING.equals(typeName)) {
+                if (operator == SearchParameters.Operator.NEQ || operator == SearchParameters.Operator.NOT_CONTAINS) {
                     if (LOG.isDebugEnabled()) {
-                        LOG.debug("{} operator found for string attribute {}, deferring to in-memory or graph query (might cause poor performance)", filterCriteria.getOperator(), qualifiedName);
+                        LOG.debug("{} operator found for string attribute {}, deferring to in-memory or graph query (might cause poor performance)", operator, qualifiedName);
                     }
 
                     ret = false;
-                } else if (hasIndexQuerySpecialChar(filterCriteria.getAttributeValue()) && !isPipeSeparatedSystemAttribute(filterCriteria.getAttributeName())) {
+                } else if (operator == SearchParameters.Operator.CONTAINS && AtlasAttribute.hastokenizeChar(attributeValue) && indexType == null) { // indexType = TEXT
                     if (LOG.isDebugEnabled()) {
-                        LOG.debug("special characters found in filter value {}, deferring to in-memory or graph query (might cause poor performance)", filterCriteria.getAttributeValue());
+                        LOG.debug("{} operator found for string (TEXT) attribute {} and special characters found in filter value {}, deferring to in-memory or graph query (might cause poor performance)", attributeValue);
                     }
 
                     ret = false;
@@ -488,7 +494,7 @@
 
         if (LOG.isDebugEnabled()) {
             if (!ret) {
-                LOG.debug("Not using index query for: attribute='{}', operator='{}', value='{}'", qualifiedName, filterCriteria.getOperator(), filterCriteria.getAttributeValue());
+                LOG.debug("Not using index query for: attribute='{}', operator='{}', value='{}'", qualifiedName, operator, attributeValue);
             }
         }
 
@@ -788,14 +794,36 @@
                         ret = String.format(OPERATOR_MAP.get(op), qualifiedName, rangeStartIndexQueryValue, rangeEndIndexQueryValue);
                     }
                 } else {
-                     // map '__customAttributes' 'CONTAINS' operator to 'EQ' operator (solr limitation for json serialized string search)
-                     // map '__customAttributes' value from 'key1=value1' to '\"key1\":\"value1\"' (escape special characters and surround with quotes)
-                     String escapeIndexQueryValue = AtlasAttribute.escapeIndexQueryValue(attrVal);
-                     if (attrName.equals(CUSTOM_ATTRIBUTES_PROPERTY_KEY) && op == SearchParameters.Operator.CONTAINS) {
-                         ret = String.format(OPERATOR_MAP.get(op), qualifiedName, getCustomAttributeIndexQueryValue(escapeIndexQueryValue, false));
-                     } else {
-                          ret = String.format(OPERATOR_MAP.get(op), qualifiedName, escapeIndexQueryValue);
-                     }
+                    String escapeIndexQueryValue;
+                    boolean replaceWildcardChar = false;
+
+                    AtlasStructDef.AtlasAttributeDef def = type.getAttributeDef(attrName);
+
+                    //when wildcard search -> escape special Char, don't quote
+                    //      when  tokenized characters + index field Type TEXT -> remove wildcard '*' from query
+                    if (!isPipeSeparatedSystemAttribute(attrName)
+                            && (op == SearchParameters.Operator.CONTAINS || op == SearchParameters.Operator.STARTS_WITH || op == SearchParameters.Operator.ENDS_WITH)
+                            && def.getTypeName().equalsIgnoreCase(AtlasBaseTypeDef.ATLAS_TYPE_STRING)) {
+
+                        escapeIndexQueryValue  = AtlasAttribute.escapeIndexQueryValue(attrVal, false, false);
+                        if (def.getIndexType() == null && AtlasAttribute.hastokenizeChar(attrVal)) {
+                            replaceWildcardChar = true;
+                        }
+                    } else {
+                        escapeIndexQueryValue = AtlasAttribute.escapeIndexQueryValue(attrVal);
+                    }
+
+                    String operatorStr = OPERATOR_MAP.get(op);
+                    if (replaceWildcardChar) {
+                        operatorStr = operatorStr.replace("*", "");
+                    }
+
+                    // map '__customAttributes' value from 'key1=value1' to '\"key1\":\"value1\"' (escape special characters and surround with quotes)
+                    if (attrName.equals(CUSTOM_ATTRIBUTES_PROPERTY_KEY) && op == SearchParameters.Operator.CONTAINS) {
+                        ret = String.format(operatorStr, qualifiedName, getCustomAttributeIndexQueryValue(escapeIndexQueryValue, false));
+                    } else {
+                        ret = String.format(operatorStr, qualifiedName, escapeIndexQueryValue);
+                    }
                 }
             }
         } catch (AtlasBaseException ex) {
diff --git a/repository/src/test/java/org/apache/atlas/discovery/AtlasDiscoveryServiceTest.java b/repository/src/test/java/org/apache/atlas/discovery/AtlasDiscoveryServiceTest.java
index 0da60d3..9846d43 100644
--- a/repository/src/test/java/org/apache/atlas/discovery/AtlasDiscoveryServiceTest.java
+++ b/repository/src/test/java/org/apache/atlas/discovery/AtlasDiscoveryServiceTest.java
@@ -18,20 +18,21 @@
 package org.apache.atlas.discovery;
 
 import org.apache.atlas.ApplicationProperties;
+import org.apache.atlas.AtlasClient;
 import org.apache.atlas.BasicTestSetup;
 import org.apache.atlas.TestModules;
 import org.apache.atlas.exception.AtlasBaseException;
+import org.apache.atlas.model.discovery.AtlasSearchResult;
 import org.apache.atlas.model.discovery.SearchParameters;
 import org.apache.atlas.model.instance.AtlasClassification;
+import org.apache.atlas.model.instance.AtlasEntity;
 import org.apache.atlas.model.instance.AtlasEntityHeader;
 import org.apache.atlas.model.instance.EntityMutationResponse;
 import org.apache.atlas.repository.graph.AtlasGraphProvider;
+import org.apache.atlas.repository.store.graph.v2.AtlasEntityStream;
 import org.apache.commons.collections.CollectionUtils;
 import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Guice;
-import org.testng.annotations.Test;
+import org.testng.annotations.*;
 
 import javax.inject.Inject;
 import java.util.Arrays;
@@ -39,7 +40,8 @@
 import java.util.List;
 
 import static org.apache.atlas.model.discovery.SearchParameters.*;
-import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.*;
+import static org.testng.Assert.assertNotNull;
 
 @Guice(modules = TestModules.TestOnlyModule.class)
 public class AtlasDiscoveryServiceTest extends BasicTestSetup {
@@ -54,6 +56,7 @@
         ApplicationProperties.get().setProperty(ApplicationProperties.ENABLE_FREETEXT_SEARCH_CONF, true);
         setupTestData();
         createDimensionalTaggedEntity("sales");
+        createSpecialCharTestEntities();
     }
 
     /*  TermSearchProcessor(TSP),
@@ -322,6 +325,345 @@
         assertEquals(entityHeaders.size(), 4);
     }
 
+    String spChar1   = "default.test_dot_name";
+    String spChar2   = "default.test_dot_name@db.test_db";
+    String spChar3   = "default.test_dot_name_12.col1@db1";
+    String spChar4   = "default_.test_dot_name";
+
+    String spChar5   = "default.test_colon_name:test_db";
+    String spChar6   = "default.test_colon_name:-test_db";
+    String spChar7   = "crn:def:default:name-76778-87e7-23@test";
+    String spChar8   = "default.:test_db_name";
+
+    String spChar9   = "default.customer's_name";
+    String spChar10  = "default.customers'_data_name";
+
+    String spChar11  = "search_with space@name";
+    String spChar12  = "search_with space 123@name";
+
+    //SearchProcessor.isIndexQuerySpecialChar
+    String spChar13  = "search_with_special-char#having$and%inthename=attr";
+    String spChar14  = "search_with_specialChar!name";
+    String spChar15  = "search_with_star*in_name";
+    String spChar16  = "search_with_star5.*5_inname";
+    String spChar17  = "search_quest?n_name";
+
+    String spChar18  = "/warehouse/tablespace/external/hive/name/hortonia_bank";
+    String spChar19  = "/warehouse/tablespace/external/hive/name/exis_bank";
+
+
+    @DataProvider(name = "specialCharSearchContains")
+    private Object[][] specialCharSearchContains() {
+        return new Object[][]{
+                {"name",Operator.CONTAINS,"test_dot",4},
+                {"name",Operator.CONTAINS,"test_dot_name_",1},
+                {"name",Operator.CONTAINS,"test_colon_name",2},
+                {"name",Operator.CONTAINS,"def:default:name",1},
+                {"name",Operator.CONTAINS,"space 12",1},
+                {"name",Operator.CONTAINS,"with space",2},
+                {"name",Operator.CONTAINS,"Char!name",1},
+                {"name",Operator.CONTAINS,"with_star",2},
+                {"name",Operator.CONTAINS,"/external/hive/name/",2},
+
+                {"name",Operator.CONTAINS,"test_dot_name@db",1},
+                {"name",Operator.CONTAINS,"name@db",1},
+                {"name",Operator.CONTAINS,"def:default:name-",1},
+                {"name",Operator.CONTAINS,"star*in",1},
+                {"name",Operator.CONTAINS,"Char!na",1},
+                {"name",Operator.CONTAINS,"ith spac",2},
+                {"name",Operator.CONTAINS,"778-87",1},
+
+                {"qualifiedName",Operator.CONTAINS,"test_dot",4},
+                {"qualifiedName",Operator.CONTAINS,"test_dot_qf_",1},
+                {"qualifiedName",Operator.CONTAINS,"test_colon_qf",2},
+                {"qualifiedName",Operator.CONTAINS,"def:default:qf",1},
+                {"qualifiedName",Operator.CONTAINS,"space 12",1},
+                {"qualifiedName",Operator.CONTAINS,"with space",2},
+                {"qualifiedName",Operator.CONTAINS,"Char!qf",1},
+                {"qualifiedName",Operator.CONTAINS,"with_star",2},
+                {"qualifiedName",Operator.CONTAINS,"/external/hive/qf/",2},
+
+                {"qualifiedName",Operator.CONTAINS,"test_dot_qf@db",1},
+                {"qualifiedName",Operator.CONTAINS,"qf@db",1},
+                {"qualifiedName",Operator.CONTAINS,"def:default:qf-",1},
+                {"qualifiedName",Operator.CONTAINS,"star*in",1},
+                {"qualifiedName",Operator.CONTAINS,"Char!q",1},
+                {"qualifiedName",Operator.CONTAINS,"ith spac",2},
+                {"qualifiedName",Operator.CONTAINS,"778-87",1},
+        };
+    }
+
+    @DataProvider(name = "specialCharSearchName")
+    private Object[][] specialCharSearchName() {
+        return new Object[][]{
+
+                {"name",Operator.STARTS_WITH,"default.test_dot_",3},
+
+                {"name",Operator.STARTS_WITH,"default.test_dot_name@db.test",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name@db.",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name",3},
+                {"name",Operator.ENDS_WITH,"test_db",3},
+
+                {"name",Operator.STARTS_WITH,"default.test_dot_name_12.col1@db",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name_12.col1@",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name_12.col1",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name_12.col",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name_12.",1},
+                {"name",Operator.STARTS_WITH,"default.test_dot_name_12",1},
+                {"name",Operator.ENDS_WITH,"col1@db1",1},
+
+                {"name",Operator.STARTS_WITH,"default_.test_dot",1},
+                {"name",Operator.ENDS_WITH,"test_dot_name",2},
+
+                {"name",Operator.STARTS_WITH,"default.test_colon_name:test_",1},
+
+                {"name",Operator.STARTS_WITH,"default.test_colon_name:-test_",1},
+                {"name",Operator.STARTS_WITH,"default.test_colon_name:-",1},
+                {"name",Operator.STARTS_WITH,"default.test_colon",2},
+
+                {"name",Operator.STARTS_WITH,"crn:def:default:name-76778-87e7-23@",1},
+                {"name",Operator.STARTS_WITH,"crn:def:default:name-76778-87e7-",1},
+                {"name",Operator.STARTS_WITH,"crn:def:default:",1},
+
+                {"name",Operator.STARTS_WITH,"default.:test_db",1},
+                {"name",Operator.ENDS_WITH,"test_db_name",1},
+
+                {"name",Operator.STARTS_WITH,"default.customer's",1},
+                {"name",Operator.ENDS_WITH,"mer's_name",1},
+
+                {"name",Operator.STARTS_WITH,"default.customers'_data",1},
+                {"name",Operator.ENDS_WITH,"customers'_data_name",1},
+
+                {"name",Operator.STARTS_WITH,"search_with space",2},
+                {"name",Operator.STARTS_WITH,"search_with space ",1},
+                {"name",Operator.STARTS_WITH,"search_with space 123@",1},
+                {"name",Operator.STARTS_WITH,"search_with space 1",1},
+
+                {"name",Operator.STARTS_WITH,"search_with_special-char#having$and%inthename=",1},
+                {"name",Operator.STARTS_WITH,"search_with_special-char#having$and%in",1},
+                {"name",Operator.STARTS_WITH,"search_with_special-char#having$",1},
+                {"name",Operator.STARTS_WITH,"search_with_special-char#h",1},
+                {"name",Operator.STARTS_WITH,"search_with_special",2},
+                {"name",Operator.STARTS_WITH,"search_with_spe",2},
+
+                {"name",Operator.STARTS_WITH,"search_with_specialChar!",1},
+
+                {"name",Operator.STARTS_WITH,"search_with_star*in",1},
+
+                {"name",Operator.ENDS_WITH,"5.*5_inname",1},
+                {"name",Operator.STARTS_WITH,"search_with_star5.*5_",1},
+
+                {"name",Operator.STARTS_WITH,"search_quest?n_",1},
+
+                {"name",Operator.STARTS_WITH,"/warehouse/tablespace/external/hive/name/hortonia",1},
+                {"name",Operator.STARTS_WITH,"/warehouse/tablespace/external/hive/name/",2},
+
+        };
+    }
+
+    @DataProvider(name = "specialCharSearchQFName")
+    private Object[][] specialCharSearchQFName() {
+        return new Object[][]{
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_",3},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf@db.test",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf@db.",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf",3},
+                {"qualifiedName",Operator.ENDS_WITH,"test_db",3},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf_12.col1@db",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf_12.col1@",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf_12.col1",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf_12.col",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf_12.",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_dot_qf_12",1},
+                {"qualifiedName",Operator.ENDS_WITH,"col1@db1",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default_.test_dot",1},
+                {"qualifiedName",Operator.ENDS_WITH,"test_dot_qf",2},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_colon_qf:test_",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_colon_qf:-test_",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_colon_qf:-",1},
+                {"qualifiedName",Operator.STARTS_WITH,"default.test_colon",2},
+
+                {"qualifiedName",Operator.STARTS_WITH,"crn:def:default:qf-76778-87e7-23@",1},
+                {"qualifiedName",Operator.STARTS_WITH,"crn:def:default:qf-76778-87e7-",1},
+                {"qualifiedName",Operator.STARTS_WITH,"crn:def:default:",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.:test_db",1},
+                {"qualifiedName",Operator.ENDS_WITH,"test_db_qf",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.customer's",1},
+                {"qualifiedName",Operator.ENDS_WITH,"mer's_qf",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"default.customers'_data",1},
+                {"qualifiedName",Operator.ENDS_WITH,"customers'_data_qf",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"search_with space",2},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with space ",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with space 123@",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with space 1",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_special-char#having$and%intheqf=",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_special-char#having$and%in",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_special-char#having$",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_special-char#h",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_special",2},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_spe",2},
+
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_specialChar!",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_star*in",1},
+
+                {"qualifiedName",Operator.ENDS_WITH,"5.*5_inqf",1},
+                {"qualifiedName",Operator.STARTS_WITH,"search_with_star5.*5_",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"search_quest?n_",1},
+
+                {"qualifiedName",Operator.STARTS_WITH,"/warehouse/tablespace/external/hive/qf/hortonia",1},
+                {"qualifiedName",Operator.STARTS_WITH,"/warehouse/tablespace/external/hive/qf/",2},
+
+        };
+    }
+
+
+    @DataProvider(name = "specialCharSearchEQ")
+    private Object[][] specialCharSearch() {
+        return new Object[][]{
+                {"name",Operator.EQ,spChar1,1},
+                {"name",Operator.EQ,spChar2,1},
+                {"name",Operator.EQ,spChar3,1},
+                {"name",Operator.EQ,spChar4,1},
+                {"name",Operator.EQ,spChar5,1},
+                {"name",Operator.EQ,spChar6,1},
+                {"name",Operator.EQ,spChar7,1},
+                {"name",Operator.EQ,spChar8,1},
+                {"name",Operator.EQ,spChar9,1},
+                {"name",Operator.EQ,spChar10,1},
+                {"name",Operator.EQ,spChar11,1},
+                {"name",Operator.EQ,spChar12,1},
+                {"name",Operator.EQ,spChar13,1},
+                {"name",Operator.EQ,spChar14,1},
+                {"name",Operator.EQ,spChar15,1},
+                {"name",Operator.EQ,spChar16,1},
+                {"name",Operator.EQ,spChar17,1},
+                {"name",Operator.EQ,spChar18,1},
+                {"name",Operator.EQ,spChar19,1},
+
+                {"qualifiedName",Operator.EQ,spChar1.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar2.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar3.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar4.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar5.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar6.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar7.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar8.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar9.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar10.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar11.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar12.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar13.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar14.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar15.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar16.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar17.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar18.replace("name","qf"),1},
+                {"qualifiedName",Operator.EQ,spChar19.replace("name","qf"),1},
+        };
+    }
+
+
+    public void createSpecialCharTestEntities() throws AtlasBaseException {
+
+        List<String> nameList = Arrays.asList(spChar1,spChar2,spChar3,spChar4,spChar5,spChar6,spChar7,spChar8,spChar9,spChar10,spChar11,spChar12,spChar13,spChar14,spChar15,spChar16,spChar17,spChar18,spChar19);
+        for (String nameStr : nameList) {
+            AtlasEntity entityToDelete = new AtlasEntity(HIVE_TABLE_TYPE);
+            entityToDelete.setAttribute("name", nameStr);
+            entityToDelete.setAttribute(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME, "qualifiedName"+System.currentTimeMillis());
+
+            //create entity
+            EntityMutationResponse response = entityStore.createOrUpdate(new AtlasEntityStream(new AtlasEntity.AtlasEntitiesWithExtInfo(entityToDelete)), false);
+        }
+
+        List<String> qfList = nameList;
+
+        for (String qfStr : qfList) {
+            qfStr = qfStr.replace("name","qf");
+            AtlasEntity entityToDelete = new AtlasEntity(HIVE_TABLE_TYPE);
+            entityToDelete.setAttribute("name", "name"+System.currentTimeMillis());
+            entityToDelete.setAttribute(AtlasClient.REFERENCEABLE_ATTRIBUTE_NAME, qfStr);
+
+            //create entity
+            EntityMutationResponse response = entityStore.createOrUpdate(new AtlasEntityStream(new AtlasEntity.AtlasEntitiesWithExtInfo(entityToDelete)), false);
+
+        }
+    }
+
+    @Test(dataProvider = "specialCharSearchEQ")
+    public void specialCharSearchAssertEq(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
+        SearchParameters params = new SearchParameters();
+        params.setTypeName(HIVE_TABLE_TYPE);
+        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName,operator, attrValue);
+        params.setEntityFilters(filterCriteria);
+        params.setLimit(20);
+
+       AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
+        assertSearchResult(searchResult,expected, attrValue);
+    }
+
+    @Test(dataProvider = "specialCharSearchContains")
+    public void specialCharSearchAssertContains(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
+        SearchParameters params = new SearchParameters();
+        params.setTypeName(HIVE_TABLE_TYPE);
+        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName,operator, attrValue);
+        params.setEntityFilters(filterCriteria);
+        params.setLimit(20);
+
+        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
+        assertSearchResult(searchResult,expected, attrValue);
+    }
+
+    @Test(dataProvider = "specialCharSearchName")
+    public void specialCharSearchAssertName(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
+        SearchParameters params = new SearchParameters();
+        params.setTypeName(HIVE_TABLE_TYPE);
+        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName,operator, attrValue);
+        params.setEntityFilters(filterCriteria);
+        params.setLimit(20);
+
+        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
+        assertSearchResult(searchResult,expected, attrValue);
+    }
+
+    @Test(dataProvider = "specialCharSearchQFName")
+    public void specialCharSearchAssertQFName(String attrName, SearchParameters.Operator operator, String attrValue, int expected) throws AtlasBaseException {
+        SearchParameters params = new SearchParameters();
+        params.setTypeName(HIVE_TABLE_TYPE);
+        SearchParameters.FilterCriteria filterCriteria = getSingleFilterCondition(attrName,operator, attrValue);
+        params.setEntityFilters(filterCriteria);
+        params.setLimit(20);
+
+        AtlasSearchResult searchResult = discoveryService.searchWithParameters(params);
+        assertSearchResult(searchResult,expected, attrValue);
+    }
+
+    private void assertSearchResult(AtlasSearchResult searchResult, int expected, String query) {
+        assertNotNull(searchResult);
+        if(expected == 0) {
+            assertTrue(searchResult.getAttributes() == null || CollectionUtils.isEmpty(searchResult.getAttributes().getValues()));
+            assertNull(searchResult.getEntities(), query);
+        } else if(searchResult.getEntities() != null) {
+            assertEquals(searchResult.getEntities().size(), expected, query);
+        } else {
+            assertNotNull(searchResult.getAttributes());
+            assertNotNull(searchResult.getAttributes().getValues());
+            assertEquals(searchResult.getAttributes().getValues().size(), expected, query);
+        }
+    }
+
     private void createDimensionalTaggedEntity(String name) throws AtlasBaseException {
         EntityMutationResponse resp = createDummyEntity(name, HIVE_TABLE_TYPE);
         AtlasEntityHeader entityHeader = resp.getCreatedEntities().get(0);