Commit 40e2e37d by Madhan Neethiraj

ATLAS-1630: fix incorrect pagination of results in basic search (#3)

parent c572e541
...@@ -37,6 +37,7 @@ import org.apache.atlas.query.SelectExpressionHelper; ...@@ -37,6 +37,7 @@ import org.apache.atlas.query.SelectExpressionHelper;
import org.apache.atlas.repository.Constants; import org.apache.atlas.repository.Constants;
import org.apache.atlas.repository.MetadataRepository; import org.apache.atlas.repository.MetadataRepository;
import org.apache.atlas.repository.graph.AtlasGraphProvider; import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.repository.graph.GraphHelper;
import org.apache.atlas.repository.graphdb.AtlasGraph; import org.apache.atlas.repository.graphdb.AtlasGraph;
import org.apache.atlas.repository.graphdb.AtlasIndexQuery; import org.apache.atlas.repository.graphdb.AtlasIndexQuery;
import org.apache.atlas.repository.graphdb.AtlasIndexQuery.Result; import org.apache.atlas.repository.graphdb.AtlasIndexQuery.Result;
...@@ -64,6 +65,7 @@ import java.util.HashMap; ...@@ -64,6 +65,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import static org.apache.atlas.AtlasErrorCode.DISCOVERY_QUERY_FAILED; import static org.apache.atlas.AtlasErrorCode.DISCOVERY_QUERY_FAILED;
import static org.apache.atlas.AtlasErrorCode.UNKNOWN_TYPENAME; import static org.apache.atlas.AtlasErrorCode.UNKNOWN_TYPENAME;
...@@ -166,9 +168,9 @@ public class EntityDiscoveryService implements AtlasDiscoveryService { ...@@ -166,9 +168,9 @@ public class EntityDiscoveryService implements AtlasDiscoveryService {
LOG.debug("Executing basic search query: {} with type: {} and classification: {}", query, typeName, classification); LOG.debug("Executing basic search query: {} with type: {} and classification: {}", query, typeName, classification);
} }
Map<String, Object> bindings = new HashMap<>(); final QueryParams params = validateSearchParams(limit, offset);
QueryParams params = validateSearchParams(limit, offset); Set<String> typeNames = null;
String basicQuery = "g.V()"; Set<String> classificationNames = null;
if (StringUtils.isNotEmpty(typeName)) { if (StringUtils.isNotEmpty(typeName)) {
AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName); AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName);
...@@ -177,9 +179,7 @@ public class EntityDiscoveryService implements AtlasDiscoveryService { ...@@ -177,9 +179,7 @@ public class EntityDiscoveryService implements AtlasDiscoveryService {
throw new AtlasBaseException(UNKNOWN_TYPENAME, typeName); throw new AtlasBaseException(UNKNOWN_TYPENAME, typeName);
} }
bindings.put("typeNames", entityType.getTypeAndAllSubTypes()); typeNames = entityType.getTypeAndAllSubTypes();
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.BASIC_SEARCH_TYPE_FILTER);
ret.setType(typeName); ret.setType(typeName);
} }
...@@ -191,50 +191,103 @@ public class EntityDiscoveryService implements AtlasDiscoveryService { ...@@ -191,50 +191,103 @@ public class EntityDiscoveryService implements AtlasDiscoveryService {
throw new AtlasBaseException(CLASSIFICATION_NOT_FOUND, classification); throw new AtlasBaseException(CLASSIFICATION_NOT_FOUND, classification);
} }
bindings.put("traitNames", classificationType.getTypeAndAllSubTypes()); classificationNames = classificationType.getTypeAndAllSubTypes();
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.BASIC_SEARCH_CLASSIFICATION_FILTER);
ret.setClassification(classification); ret.setClassification(classification);
} }
// if query was provided, perform indexQuery and filter for typeName & classification in memory; this approach
// results in a faster and accurate results than using CONTAINS/CONTAINS_PREFIX filter on entityText property
if (StringUtils.isNotEmpty(query)) { if (StringUtils.isNotEmpty(query)) {
bindings.put("queryStr", query); final String idxQuery = String.format("v.\"%s\":(%s)", Constants.ENTITY_TEXT_PROPERTY_KEY, query);
final Iterator<Result<?,?>> qryResult = graph.indexQuery(Constants.FULLTEXT_INDEX, idxQuery).vertices();
final int startIdx = params.offset();
final int resultSize = params.limit();
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.BASIC_SEARCH_QUERY_FILTER); int resultIdx = 0;
ret.setQueryText(query); while (qryResult.hasNext()) {
} AtlasVertex<?,?> vertex = qryResult.next().getVertex();
bindings.put("offset", params.offset()); String vertexTypeName = GraphHelper.getTypeName(vertex);
bindings.put("limit", params.limit());
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.TO_RANGE_LIST); // skip non-entity vertices
if (StringUtils.isEmpty(vertexTypeName) || StringUtils.isEmpty(GraphHelper.getGuid(vertex))) {
continue;
}
ScriptEngine scriptEngine = graph.getGremlinScriptEngine(); if (typeNames != null && !typeNames.contains(vertexTypeName)) {
continue;
}
try { if (classificationNames != null) {
Object result = graph.executeGremlinScript(scriptEngine, bindings, basicQuery, false); List<String> traitNames = GraphHelper.getTraitNames(vertex);
if (result instanceof List && CollectionUtils.isNotEmpty((List) result)) { if (CollectionUtils.isEmpty(traitNames) ||
List queryResult = (List) result; !CollectionUtils.containsAny(classificationNames, traitNames)) {
Object firstElement = queryResult.get(0); continue;
}
}
if (firstElement instanceof AtlasVertex) { resultIdx++;
for (Object element : queryResult) {
if (element instanceof AtlasVertex) {
ret.addEntity(entityRetriever.toAtlasEntityHeader((AtlasVertex) element));
} else { if (resultIdx <= startIdx) {
LOG.warn("searchUsingBasicQuery({}): expected an AtlasVertex; found unexpected entry in result {}", basicQuery, element); continue;
}
AtlasEntityHeader header = entityRetriever.toAtlasEntityHeader(vertex);
ret.addEntity(header);
if (ret.getEntities().size() == resultSize) {
break;
}
}
} else {
final Map<String, Object> bindings = new HashMap<>();
String basicQuery = "g.V()";
if (typeNames != null) {
bindings.put("typeNames", typeNames);
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.BASIC_SEARCH_TYPE_FILTER);
}
if (classificationNames != null) {
bindings.put("traitNames", classificationNames);
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.BASIC_SEARCH_CLASSIFICATION_FILTER);
}
bindings.put("startIdx", params.offset());
bindings.put("endIdx", params.offset() + params.limit());
basicQuery += gremlinQueryProvider.getQuery(AtlasGremlinQuery.TO_RANGE_LIST);
ScriptEngine scriptEngine = graph.getGremlinScriptEngine();
try {
Object result = graph.executeGremlinScript(scriptEngine, bindings, basicQuery, false);
if (result instanceof List && CollectionUtils.isNotEmpty((List) result)) {
List queryResult = (List) result;
Object firstElement = queryResult.get(0);
if (firstElement instanceof AtlasVertex) {
for (Object element : queryResult) {
if (element instanceof AtlasVertex) {
ret.addEntity(entityRetriever.toAtlasEntityHeader((AtlasVertex) element));
} else {
LOG.warn("searchUsingBasicQuery({}): expected an AtlasVertex; found unexpected entry in result {}", basicQuery, element);
}
} }
} }
} }
} catch (ScriptException e) {
throw new AtlasBaseException(DISCOVERY_QUERY_FAILED, basicQuery);
} finally {
graph.releaseGremlinScriptEngine(scriptEngine);
} }
} catch (ScriptException e) {
throw new AtlasBaseException(DISCOVERY_QUERY_FAILED, basicQuery);
} finally {
graph.releaseGremlinScriptEngine(scriptEngine);
} }
return ret; return ret;
......
...@@ -65,14 +65,12 @@ public class AtlasGremlin2QueryProvider extends AtlasGremlinQueryProvider { ...@@ -65,14 +65,12 @@ public class AtlasGremlin2QueryProvider extends AtlasGremlinQueryProvider {
"(it.object.'__superTypeNames'.contains('DataSet')) : false)})." + "(it.object.'__superTypeNames'.contains('DataSet')) : false)})." +
"path().toList()"; "path().toList()";
case BASIC_SEARCH_QUERY_FILTER:
return ".has('entityText', com.thinkaurelius.titan.core.attribute.Text.CONTAINS, queryStr)";
case BASIC_SEARCH_TYPE_FILTER: case BASIC_SEARCH_TYPE_FILTER:
return ".has('__typeName', T.in, typeNames)"; return ".has('__typeName', T.in, typeNames)";
case BASIC_SEARCH_CLASSIFICATION_FILTER: case BASIC_SEARCH_CLASSIFICATION_FILTER:
return ".has('__traitNames', T.in, traitNames)"; return ".has('__traitNames', T.in, traitNames)";
case TO_RANGE_LIST: case TO_RANGE_LIST:
return " [offset..<limit].toList()"; return " [startIdx..<endIdx].toList()";
} }
// Should never reach this point // Should never reach this point
return null; return null;
......
...@@ -56,7 +56,6 @@ public abstract class AtlasGremlinQueryProvider { ...@@ -56,7 +56,6 @@ public abstract class AtlasGremlinQueryProvider {
PARTIAL_LINEAGE, PARTIAL_LINEAGE,
// Discovery Queries // Discovery Queries
BASIC_SEARCH_QUERY_FILTER,
BASIC_SEARCH_TYPE_FILTER, BASIC_SEARCH_TYPE_FILTER,
BASIC_SEARCH_CLASSIFICATION_FILTER, BASIC_SEARCH_CLASSIFICATION_FILTER,
TO_RANGE_LIST TO_RANGE_LIST
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment