ATLAS-1807 : Enhance DSL query to support like operator for wildcard search
diff --git a/repository/src/main/java/org/apache/atlas/gremlin/Gremlin2ExpressionFactory.java b/repository/src/main/java/org/apache/atlas/gremlin/Gremlin2ExpressionFactory.java
index 807dd05..5f8bb80 100644
--- a/repository/src/main/java/org/apache/atlas/gremlin/Gremlin2ExpressionFactory.java
+++ b/repository/src/main/java/org/apache/atlas/gremlin/Gremlin2ExpressionFactory.java
@@ -40,6 +40,7 @@
import org.apache.atlas.groovy.RangeExpression;
import org.apache.atlas.groovy.TernaryOperatorExpression;
import org.apache.atlas.groovy.TraversalStepType;
+import org.apache.atlas.query.Expressions;
import org.apache.atlas.query.GraphPersistenceStrategies;
import org.apache.atlas.query.TypeUtils.FieldInfo;
import org.apache.atlas.typesystem.types.IDataType;
@@ -147,6 +148,37 @@
return new FunctionCallExpression(TraversalStepType.FILTER, parent, HAS_METHOD, propertyNameExpr, op, requiredValue);
}
+ @Override
+ public GroovyExpression generateLikeExpressionUsingFilter(GroovyExpression parent, String propertyName, GroovyExpression propertyValue) throws AtlasException {
+ GroovyExpression itExpr = getItVariable();
+ GroovyExpression nameExpr = new FieldExpression(itExpr, propertyName);
+ GroovyExpression matchesExpr = new FunctionCallExpression(nameExpr, MATCHES, escapePropertyValue(propertyValue));
+ GroovyExpression closureExpr = new ClosureExpression(matchesExpr);
+ GroovyExpression filterExpr = new FunctionCallExpression(parent, FILTER_METHOD, closureExpr);
+
+ return filterExpr;
+ }
+
+ private GroovyExpression escapePropertyValue(GroovyExpression propertyValue) {
+ GroovyExpression ret = propertyValue;
+
+ if (propertyValue instanceof LiteralExpression) {
+ LiteralExpression exp = (LiteralExpression) propertyValue;
+
+ if (exp != null && exp.getValue() instanceof String) {
+ String stringValue = (String) exp.getValue();
+
+ // replace '*' with ".*", replace '?' with '.'
+ stringValue = stringValue.replaceAll("\\*", ".*")
+ .replaceAll("\\?", ".");
+
+ ret = new LiteralExpression(stringValue);
+ }
+ }
+
+ return ret;
+ }
+
private GroovyExpression gremlin2CompOp(String op) throws AtlasException {
GroovyExpression tExpr = new IdentifierExpression("T");
diff --git a/repository/src/main/java/org/apache/atlas/gremlin/Gremlin3ExpressionFactory.java b/repository/src/main/java/org/apache/atlas/gremlin/Gremlin3ExpressionFactory.java
index 9f68c9a..aaec6fe 100644
--- a/repository/src/main/java/org/apache/atlas/gremlin/Gremlin3ExpressionFactory.java
+++ b/repository/src/main/java/org/apache/atlas/gremlin/Gremlin3ExpressionFactory.java
@@ -29,6 +29,7 @@
import org.apache.atlas.groovy.ComparisonExpression;
import org.apache.atlas.groovy.ComparisonExpression.ComparisonOperator;
import org.apache.atlas.groovy.ComparisonOperatorExpression;
+import org.apache.atlas.groovy.FieldExpression;
import org.apache.atlas.groovy.FunctionCallExpression;
import org.apache.atlas.groovy.GroovyExpression;
import org.apache.atlas.groovy.IdentifierExpression;
@@ -244,6 +245,37 @@
}
@Override
+ public GroovyExpression generateLikeExpressionUsingFilter(GroovyExpression parent, String propertyName, GroovyExpression propertyValue) throws AtlasException {
+ GroovyExpression itExpr = getItVariable();
+ GroovyExpression nameExpr = new FieldExpression(itExpr, propertyName);
+ GroovyExpression matchesExpr = new FunctionCallExpression(nameExpr, MATCHES, escapePropertyValue(propertyValue));
+ GroovyExpression closureExpr = new ClosureExpression(matchesExpr);
+ GroovyExpression filterExpr = new FunctionCallExpression(parent, FILTER_METHOD, closureExpr);
+
+ return filterExpr;
+ }
+
+ private GroovyExpression escapePropertyValue(GroovyExpression propertyValue) {
+ GroovyExpression ret = propertyValue;
+
+ if (propertyValue instanceof LiteralExpression) {
+ LiteralExpression exp = (LiteralExpression) propertyValue;
+
+ if (exp != null && exp.getValue() instanceof String) {
+ String stringValue = (String) exp.getValue();
+
+ // replace '*' with ".*", replace '?' with '.'
+ stringValue = stringValue.replaceAll("\\*", ".*")
+ .replaceAll("\\?", ".");
+
+ ret = new LiteralExpression(stringValue);
+ }
+ }
+
+ return ret;
+ }
+
+ @Override
protected GroovyExpression initialExpression(GroovyExpression varExpr, GraphPersistenceStrategies s) {
// this bit of groovy magic converts the set of vertices in varName into
diff --git a/repository/src/main/java/org/apache/atlas/gremlin/GremlinExpressionFactory.java b/repository/src/main/java/org/apache/atlas/gremlin/GremlinExpressionFactory.java
index ff5a58c..d603150 100644
--- a/repository/src/main/java/org/apache/atlas/gremlin/GremlinExpressionFactory.java
+++ b/repository/src/main/java/org/apache/atlas/gremlin/GremlinExpressionFactory.java
@@ -76,6 +76,7 @@
protected static final String SELECT_METHOD = "select";
protected static final String ORDER_METHOD = "order";
protected static final String FILL_METHOD = "fill";
+ protected static final String MATCHES = "matches";
public static final GremlinExpressionFactory INSTANCE = AtlasGraphProvider.getGraphInstance()
.getSupportedGremlinVersion() == GremlinVersion.THREE ? new Gremlin3ExpressionFactory()
@@ -182,6 +183,9 @@
public abstract GroovyExpression generateHasExpression(GraphPersistenceStrategies s, GroovyExpression parent,
String propertyName, String symbol, GroovyExpression requiredValue, FieldInfo fInfo) throws AtlasException;
+ public abstract GroovyExpression generateLikeExpressionUsingFilter(GroovyExpression parent, String propertyName,
+ GroovyExpression propertyValue) throws AtlasException;
+
/**
* Generates a range expression
*
diff --git a/repository/src/main/scala/org/apache/atlas/query/GremlinQuery.scala b/repository/src/main/scala/org/apache/atlas/query/GremlinQuery.scala
index 3a310a7..37015d8 100644
--- a/repository/src/main/scala/org/apache/atlas/query/GremlinQuery.scala
+++ b/repository/src/main/scala/org/apache/atlas/query/GremlinQuery.scala
@@ -437,6 +437,10 @@
genQuery(null, l, inClosure);
}
+ if (symb == "like") {
+ return GremlinExpressionFactory.INSTANCE.generateLikeExpressionUsingFilter(childExpr, qualifiedPropertyName, persistentExprValue);
+ }
+
return GremlinExpressionFactory.INSTANCE.generateHasExpression(gPersistenceBehavior, childExpr, qualifiedPropertyName, c.symbol, persistentExprValue, fInfo);
}
case fil@FilterExpression(child, condExpr) => {
diff --git a/repository/src/main/scala/org/apache/atlas/query/QueryParser.scala b/repository/src/main/scala/org/apache/atlas/query/QueryParser.scala
index 8d454e9..7b7cd98 100755
--- a/repository/src/main/scala/org/apache/atlas/query/QueryParser.scala
+++ b/repository/src/main/scala/org/apache/atlas/query/QueryParser.scala
@@ -75,6 +75,7 @@
protected val SUM = Keyword("sum")
protected val BY = Keyword("by")
protected val ORDER = Keyword("order")
+ protected val LIKE = Keyword("like")
}
trait ExpressionUtils {
@@ -312,7 +313,7 @@
def exprRight = (AND | OR) ~ compE ^^ { case op ~ c => (op, c)}
def compE =
- arithE ~ (LT | LTE | EQ | NEQ | GT | GTE) ~ arithE ^^ { case l ~ op ~ r => l.compareOp(op)(r)} |
+ arithE ~ (LT | LTE | EQ | NEQ | GT | GTE | LIKE) ~ arithE ^^ { case l ~ op ~ r => l.compareOp(op)(r)} |
arithE ~ (ISA | IS) ~ ident ^^ { case l ~ i ~ t => l.isTrait(t)} |
arithE ~ HAS ~ ident ^^ { case l ~ i ~ f => l.hasField(f)} |
arithE | countClause | maxClause | minClause | sumClause
diff --git a/repository/src/test/java/org/apache/atlas/discovery/GraphBackedDiscoveryServiceTest.java b/repository/src/test/java/org/apache/atlas/discovery/GraphBackedDiscoveryServiceTest.java
index 18573fc..3ea8f34 100755
--- a/repository/src/test/java/org/apache/atlas/discovery/GraphBackedDiscoveryServiceTest.java
+++ b/repository/src/test/java/org/apache/atlas/discovery/GraphBackedDiscoveryServiceTest.java
@@ -256,6 +256,22 @@
assertEquals(entityState, Id.EntityState.ACTIVE.name());
}
+ @DataProvider(name = "dslLikeQueriesProvider")
+ private Object[][] createDslLikeQueries() {
+ return new Object[][]{
+ {"hive_table where name like \"sa?es*\"", 3},
+ {"hive_db where name like \"R*\"", 1},
+ {"hive_db where hive_db.name like \"R???rt?*\" or hive_db.name like \"S?l?s\" or hive_db.name like\"Log*\"", 3},
+ {"hive_db where hive_db.name like \"R???rt?*\" and hive_db.name like \"S?l?s\" and hive_db.name like\"Log*\"", 0},
+ {"hive_table where name like 'sales*', db where name like 'Sa?es'", 1},
+ };
+ }
+
+ @Test(dataProvider = "dslLikeQueriesProvider")
+ public void testDslSearchUsingLikeOperator(String dslQuery, Integer expectedNumRows) throws Exception {
+ runQuery(dslQuery, expectedNumRows, 50, 0);
+ }
+
@Test(expectedExceptions = Throwable.class)
public void testSearchByDSLBadQuery() throws Exception {
String dslQuery = "from blah";
diff --git a/webapp/src/test/java/org/apache/atlas/web/resources/EntityDiscoveryJerseyResourceIT.java b/webapp/src/test/java/org/apache/atlas/web/resources/EntityDiscoveryJerseyResourceIT.java
index db3a7c3..a51f371 100755
--- a/webapp/src/test/java/org/apache/atlas/web/resources/EntityDiscoveryJerseyResourceIT.java
+++ b/webapp/src/test/java/org/apache/atlas/web/resources/EntityDiscoveryJerseyResourceIT.java
@@ -54,7 +54,7 @@
@BeforeClass
public void setUp() throws Exception {
super.setUp();
- dbName = "db" + randomString();
+ dbName = "database" + randomString();
createTypes();
createInstance(createHiveDBInstanceBuiltIn(dbName));
}
@@ -145,6 +145,22 @@
}
@Test
+ public void testLikeSearchUsingDSL() throws Exception {
+ String dslQuery = DATABASE_TYPE_BUILTIN + " where " + QUALIFIED_NAME + " like \"da?a*\"";
+
+ AtlasSearchResult searchResult = atlasClientV2.dslSearch(dslQuery);
+ assertNotNull(searchResult);
+
+ List<AtlasEntityHeader> entities = searchResult.getEntities();
+ assertNotNull(entities);
+ assertEquals(entities.size(), 1);
+
+ AtlasEntityHeader dbEntity = entities.get(0);
+ assertEquals(dbEntity.getTypeName(), DATABASE_TYPE_BUILTIN);
+ assertEquals(dbEntity.getDisplayText(), dbName);
+ }
+
+ @Test
public void testSearchFullTextOnDSLFailure() throws Exception {
String query = "*";
AtlasSearchResult searchResult = atlasClientV2.fullTextSearch(query);