package org.vertexium.cypher.executor;
import com.google.common.collect.Lists;
import org.vertexium.Element;
import org.vertexium.Vertex;
import org.vertexium.VertexiumException;
import org.vertexium.cypher.VertexiumCypherQueryContext;
import org.vertexium.cypher.VertexiumCypherScope;
import org.vertexium.cypher.ast.model.*;
import org.vertexium.cypher.exceptions.VertexiumCypherNotImplemented;
import org.vertexium.cypher.exceptions.VertexiumCypherTypeErrorException;
import org.vertexium.cypher.functions.CypherFunction;
import org.vertexium.cypher.utils.MapUtils;
import org.vertexium.cypher.utils.ObjectUtils;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.vertexium.util.StreamUtils.stream;
public class ExpressionExecutor {
public Object executeExpression(VertexiumCypherQueryContext ctx, CypherAstBase expression, ExpressionScope scope) {
if (expression == null) {
return null;
}
if (expression instanceof CypherExpression) {
if (expression instanceof CypherBinaryExpression) {
return executeBinaryExpression(ctx, (CypherBinaryExpression) expression, scope);
} else if (expression instanceof CypherComparisonExpression) {
return executeComparisonExpression(ctx, (CypherComparisonExpression) expression, scope);
} else if (expression instanceof CypherUnaryExpression) {
return executeUnaryExpression(ctx, (CypherUnaryExpression) expression, scope);
} else if (expression instanceof CypherTrueExpression) {
return true;
} else if (expression instanceof CypherNegateExpression) {
return executeNegateExpression(ctx, (CypherNegateExpression) expression, scope);
}
throw new VertexiumCypherNotImplemented("" + expression);
}
if (expression instanceof CypherListLiteral) {
//noinspection unchecked
CypherListLiteral<? extends CypherAstBase> list = (CypherListLiteral<? extends CypherAstBase>) expression;
return executeList(ctx, list, scope);
}
if (expression instanceof CypherLiteral) {
CypherLiteral literal = (CypherLiteral) expression;
return literal.getValue();
}
if (expression instanceof CypherVariable) {
CypherVariable variable = (CypherVariable) expression;
return executeObject(ctx, executeVariable(ctx, variable, scope), scope);
}
if (expression instanceof CypherLookup) {
CypherLookup lookup = (CypherLookup) expression;
return executeLookup(ctx, lookup, scope);
}
if (expression instanceof CypherFunctionInvocation) {
CypherFunctionInvocation functionInvocation = (CypherFunctionInvocation) expression;
return executeFunctionInvocation(ctx, functionInvocation, scope);
}
if (expression instanceof CypherIn) {
CypherIn in = (CypherIn) expression;
return executeIn(ctx, in, scope);
}
if (expression instanceof CypherArrayAccess) {
CypherArrayAccess arrayAccess = (CypherArrayAccess) expression;
return executeArrayAccess(ctx, arrayAccess, scope);
}
if (expression instanceof CypherArraySlice) {
CypherArraySlice arraySlice = (CypherArraySlice) expression;
return executeArraySlice(ctx, arraySlice, scope);
}
if (expression instanceof CypherParameter) {
CypherParameter parameter = (CypherParameter) expression;
return executeParameter(ctx, parameter);
}
if (expression instanceof CypherIsNull) {
CypherIsNull isNull = (CypherIsNull) expression;
return executeIsNull(ctx, isNull, scope);
}
if (expression instanceof CypherIsNotNull) {
CypherIsNotNull isNotNull = (CypherIsNotNull) expression;
return executeIsNotNull(ctx, isNotNull, scope);
}
if (expression instanceof CypherListComprehension) {
CypherListComprehension listComprehension = (CypherListComprehension) expression;
return executeListComprehension(ctx, listComprehension, scope);
}
if (expression instanceof CypherStringMatch) {
CypherStringMatch startWith = (CypherStringMatch) expression;
return executeStringMatch(ctx, startWith, scope);
}
if (expression instanceof CypherPatternComprehension) {
CypherPatternComprehension patternComprehension = (CypherPatternComprehension) expression;
VertexiumCypherScope matchScope = scope instanceof VertexiumCypherScope
? (VertexiumCypherScope) scope
: VertexiumCypherScope.newSingleItemScope((VertexiumCypherScope.Item) scope);
VertexiumCypherScope results = ctx.getMatchClauseExecutor().execute(
ctx,
Lists.newArrayList(patternComprehension.getMatchClause()),
matchScope
);
return results.stream()
.map(item -> executeExpression(ctx, patternComprehension.getExpression(), item))
.collect(Collectors.toList());
}
throw new VertexiumException("not implemented \"" + expression.getClass().getName() + "\": " + expression);
}
private Object executeNegateExpression(VertexiumCypherQueryContext ctx, CypherNegateExpression expression, ExpressionScope scope) {
Object value = executeExpression(ctx, expression.getValue(), scope);
if (value instanceof Number) {
if (value instanceof Double) {
return -(Double) value;
}
if (value instanceof Integer) {
return -(Integer) value;
}
if (value instanceof Long) {
return -(Long) value;
}
return -((Number) value).doubleValue();
}
throw new VertexiumException("not implemented");
}
private Object executeStringMatch(VertexiumCypherQueryContext ctx, CypherStringMatch stringMatch, ExpressionScope scope) {
Object value = executeExpression(ctx, stringMatch.getValueExpression(), scope);
Object stringObj = executeExpression(ctx, stringMatch.getStringExpression(), scope);
if (stringObj == null) {
return null;
}
if (!(stringObj instanceof String)) {
return null;
}
String string = (String) stringObj;
if (value == null) {
return null;
}
switch (stringMatch.getOp()) {
case STARTS_WITH:
return value.toString().startsWith(string);
case ENDS_WITH:
return value.toString().endsWith(string);
case CONTAINS:
return value.toString().contains(string);
default:
throw new VertexiumException("unhandled string match: " + stringMatch.getOp());
}
}
private Object executeListComprehension(VertexiumCypherQueryContext ctx, CypherListComprehension listComprehension, ExpressionScope scope) {
Stream<ExpressionScope> itemScopes = executeFilterExpression(ctx, listComprehension.getFilterExpression(), scope);
if (listComprehension.getExpression() == null) {
return itemScopes;
}
return itemScopes
.map(itemScope -> executeExpression(ctx, listComprehension.getExpression(), itemScope))
.collect(Collectors.toList());
}
private Stream<ExpressionScope> executeFilterExpression(
VertexiumCypherQueryContext ctx,
CypherFilterExpression filterExpression,
ExpressionScope scope
) {
String name = filterExpression.getIdInCol().getVariable().getName();
Object values = executeExpression(ctx, filterExpression.getIdInCol().getExpression(), scope);
Stream<ExpressionScope> results = stream(toIterable(values))
.map(value -> VertexiumCypherScope.newMapItem(name, value, scope));
if (filterExpression.getWhere() != null) {
throw new VertexiumCypherNotImplemented("where");
}
return results;
}
private Object executeObject(VertexiumCypherQueryContext ctx, Object o, ExpressionScope scope) {
if (o instanceof CypherAstBase) {
return executeExpression(ctx, (CypherAstBase) o, scope);
}
return o;
}
private Object executeIsNotNull(VertexiumCypherQueryContext ctx, CypherIsNotNull isNotNull, ExpressionScope clauseResult) {
Object value = ctx.getExpressionExecutor().executeExpression(ctx, isNotNull.getValueExpression(), clauseResult);
return value != null;
}
private Object executeIsNull(VertexiumCypherQueryContext ctx, CypherIsNull isNull, ExpressionScope scope) {
Object value = ctx.getExpressionExecutor().executeExpression(ctx, isNull.getValueExpression(), scope);
return value == null;
}
private Object executeParameter(VertexiumCypherQueryContext ctx, CypherParameter parameter) {
if (parameter instanceof CypherNameParameter) {
CypherNameParameter nameParameter = (CypherNameParameter) parameter;
return ctx.getParameters().get(nameParameter.getParameterName());
} else if (parameter instanceof CypherIndexedParameter) {
CypherIndexedParameter indexedParameter = (CypherIndexedParameter) parameter;
return ctx.getParameters().get(Integer.toString(indexedParameter.getIndex()));
}
throw new VertexiumException("not implemented");
}
private Object executeArraySlice(VertexiumCypherQueryContext ctx, CypherArraySlice arraySlice, ExpressionScope scope) {
Object array = ctx.getExpressionExecutor().executeExpression(ctx, arraySlice.getArrayExpression(), scope);
Object sliceFromObj = ctx.getExpressionExecutor().executeExpression(ctx, arraySlice.getSliceFrom(), scope);
Object sliceToObj = ctx.getExpressionExecutor().executeExpression(ctx, arraySlice.getSliceTo(), scope);
if (!(sliceFromObj instanceof Number)) {
throw new VertexiumException("expected integer from, found " + sliceFromObj.getClass().getName());
}
int sliceFrom = ((Number) sliceFromObj).intValue();
if (!(sliceToObj instanceof Number)) {
throw new VertexiumException("expected integer to, found " + sliceToObj.getClass().getName());
}
int sliceTo = ((Number) sliceToObj).intValue();
Iterable<?> it = toIterable(array);
return stream(it)
.skip(sliceFrom)
.limit(sliceTo - sliceFrom)
.collect(Collectors.toList());
}
private Object executeArrayAccess(VertexiumCypherQueryContext ctx, CypherArrayAccess arrayAccess, ExpressionScope scope) {
Object array = ctx.getExpressionExecutor().executeExpression(ctx, arrayAccess.getArrayExpression(), scope);
if (array == null) {
return null;
}
Object indexObj = ctx.getExpressionExecutor().executeExpression(ctx, arrayAccess.getIndexExpression(), scope);
if (array instanceof Element) {
Element element = (Element) array;
if (indexObj instanceof String) {
String propertyName = (String) indexObj;
return element.getPropertyValue(propertyName);
}
throw new VertexiumCypherTypeErrorException("expected string property name, found " + indexObj.getClass().getName());
}
if (array instanceof Map) {
Map map = (Map) array;
if (indexObj instanceof String) {
String propertyName = (String) indexObj;
return map.get(propertyName);
}
throw new VertexiumCypherTypeErrorException("MapElementAccessByNonString: expected string, found " + indexObj.getClass().getName());
}
if (array instanceof List || array instanceof CypherListLiteral) {
if (indexObj instanceof Long) {
indexObj = ((Long) indexObj).intValue();
}
if (indexObj instanceof Integer) {
int index = (int) indexObj;
if (array instanceof CypherListLiteral) {
return ((CypherListLiteral) array).get(index);
} else if (array instanceof List) {
return ((List) array).get(index);
}
}
throw new VertexiumCypherTypeErrorException("ListElementAccessByNonInteger: expected integer, found " + indexObj.getClass().getName());
}
throw new VertexiumCypherTypeErrorException("InvalidElementAccess: unexpected object access, found object " + array.getClass().getName() + ": " + array + ", index " + indexObj.getClass().getName() + ": " + indexObj);
}
private Object executeIn(VertexiumCypherQueryContext ctx, CypherIn in, ExpressionScope scope) {
Object value = ctx.getExpressionExecutor().executeExpression(ctx, in.getValueExpression(), scope);
Object array = ctx.getExpressionExecutor().executeExpression(ctx, in.getArrayExpression(), scope);
if (value == null) {
if (array == null) {
return null;
}
Iterable<?> it = toIterable(array);
if (!it.iterator().hasNext()) {
return false;
}
return null;
}
Iterable<?> it = toIterable(array);
boolean hasNullValue = false;
for (Object o : it) {
if (o == null) {
hasNullValue = true;
continue;
}
if (o instanceof CypherLiteral) {
o = ((CypherLiteral) o).getValue();
}
if (o.equals(value)) {
return true;
}
}
if (hasNullValue) {
return null;
}
return false;
}
private Iterable<?> toIterable(Object obj) {
Iterable<?> it;
if (obj instanceof CypherListLiteral || obj instanceof List || obj instanceof Set) {
it = (Iterable) obj;
} else {
throw new VertexiumCypherNotImplemented("expected iterable, found " + obj == null ? null : obj.getClass().getName());
}
return it;
}
private Object executeUnaryExpression(
VertexiumCypherQueryContext ctx,
CypherUnaryExpression expression,
ExpressionScope scope
) {
Object value = ctx.getExpressionExecutor().executeExpression(ctx, expression.getExpression(), scope);
switch (expression.getOp()) {
case NOT:
return executeNOT(value);
default:
throw new VertexiumCypherNotImplemented("" + expression.getOp());
}
}
private Object executeNOT(Object value) {
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return !((boolean) value);
}
throw new VertexiumException("could not NOT: " + value.getClass().getName());
}
private Object executeFunctionInvocation(
VertexiumCypherQueryContext ctx,
CypherFunctionInvocation functionInvocation,
ExpressionScope scope
) {
CypherFunction fn = ctx.getFunction(functionInvocation.getFunctionName());
return fn.invoke(ctx, functionInvocation.getArguments(), scope);
}
private List<Object> executeList(VertexiumCypherQueryContext context, CypherListLiteral<? extends CypherAstBase> list, ExpressionScope scope) {
return list.stream()
.map(i -> executeExpression(context, i, scope))
.collect(Collectors.toList());
}
private Object executeLookup(VertexiumCypherQueryContext ctx, CypherLookup expression, ExpressionScope scope) {
Object item = executeExpression(ctx, expression.getAtom(), scope);
return executeLookup(ctx, item, expression, scope);
}
private Object executeLookup(VertexiumCypherQueryContext ctx, Object item, CypherLookup expression, ExpressionScope scope) {
if (item == null) {
return null;
}
if (item instanceof Map) {
if (expression.hasLabels()) {
throw new VertexiumException("lookup using labels from a map is not supported");
}
Object value = MapUtils.getByExpression((Map) item, expression.getProperty());
if (value == null) {
return null;
}
if (value instanceof Element) {
return value;
}
if (value instanceof CypherAstBase) {
return executeExpression(ctx, (CypherAstBase) value, scope);
}
return value;
}
if (item instanceof Element) {
Element element = (Element) item;
if (expression.hasLabels()) {
if (element instanceof Vertex) {
if (expression.getProperty() != null) {
throw new VertexiumException("cannot have labels and properties");
}
return expression.getLabels().stream()
.anyMatch(l -> ctx.getVertexLabels((Vertex) element)
.contains(ctx.normalizeLabelName(l.getValue()))
);
}
throw new VertexiumCypherNotImplemented("label lookup");
}
if (expression.getProperty() == null) {
return element;
}
return element.getPropertyValue(ctx.normalizePropertyName(expression.getProperty()));
}
if (item instanceof Collection) {
Collection<?> list = (Collection) item;
return list.stream()
.map(listItem -> {
Object results = executeLookup(ctx, listItem, expression, scope);
return results;
})
.collect(Collectors.toList());
}
throw new VertexiumCypherTypeErrorException(item, Element.class, Map.class, Collection.class, null);
}
private Object executeComparisonExpression(
VertexiumCypherQueryContext context,
CypherComparisonExpression expression,
ExpressionScope scope
) {
Object left = executeExpression(context, expression.getLeft(), scope);
Object right = executeExpression(context, expression.getRight(), scope);
String op = expression.getOp();
switch (op) {
case "=":
case "<":
case "<=":
case ">":
case ">=":
case "<>":
if (left == null && right == null) {
return null;
}
if (left == null) {
return false;
}
return compare(left, op, right);
default:
throw new VertexiumException("comparison not implemented: " + op);
}
}
private Object compare(Object left, String op, Object right) {
switch (op) {
case "<":
case "<=":
case ">":
case ">=":
if (left == null || right == null) {
return false;
}
if (left instanceof Number && right instanceof Number) {
// numbers are ok
} else if (left.getClass().equals(right.getClass())) {
// same types are ok
} else {
return false;
}
}
int comp = ObjectUtils.compare(left, right);
switch (op) {
case "=":
return comp == 0;
case "<":
return comp < 0;
case "<=":
return comp <= 0;
case ">":
return comp > 0;
case ">=":
return comp >= 0;
case "<>":
return comp != 0;
default:
throw new VertexiumCypherNotImplemented("unexpected op: " + op);
}
}
private Object executeBinaryExpression(
VertexiumCypherQueryContext ctx,
CypherBinaryExpression expression,
ExpressionScope scope
) {
Object left = executeExpression(ctx, expression.getLeft(), scope);
switch (expression.getOp()) {
case ADD:
return executeADD(ctx, left, expression.getRight(), scope);
case MULTIPLY:
return executeMULTIPLY(ctx, left, expression.getRight(), scope);
case MINUS:
return executeMINUS(ctx, left, expression.getRight(), scope);
case DIVIDE:
return executeDIVIDE(ctx, left, expression.getRight(), scope);
case AND:
return executeAND(ctx, left, expression.getRight(), scope);
case OR:
return executeOR(ctx, left, expression.getRight(), scope);
case XOR:
return executeXOR(ctx, left, expression.getRight(), scope);
case MOD:
return executeMOD(ctx, left, expression.getRight(), scope);
case POWER:
return executePOWER(ctx, left, expression.getRight(), scope);
default:
throw new VertexiumException("Unhandled binary op: " + expression.getOp());
}
}
private Object executeMULTIPLY(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (!(left instanceof Number)) {
throw new VertexiumCypherTypeErrorException(left, Number.class);
}
if (!(right instanceof Number)) {
throw new VertexiumCypherTypeErrorException(right, Number.class);
}
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || leftNumber instanceof Float
|| rightNumber instanceof Double || rightNumber instanceof Float) {
return leftNumber.doubleValue() * rightNumber.doubleValue();
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
return leftNumber.longValue() * rightNumber.longValue();
}
return leftNumber.intValue() * rightNumber.intValue();
}
private Object executeMINUS(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (!(left instanceof Number)) {
throw new VertexiumCypherTypeErrorException(left, Number.class);
}
if (!(right instanceof Number)) {
throw new VertexiumCypherTypeErrorException(right, Number.class);
}
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || leftNumber instanceof Float
|| rightNumber instanceof Double || rightNumber instanceof Float) {
return leftNumber.doubleValue() - rightNumber.doubleValue();
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
return leftNumber.longValue() - rightNumber.longValue();
}
return leftNumber.intValue() - rightNumber.intValue();
}
private Object executeDIVIDE(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (!(left instanceof Number)) {
throw new VertexiumCypherTypeErrorException(left, Number.class);
}
if (!(right instanceof Number)) {
throw new VertexiumCypherTypeErrorException(right, Number.class);
}
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || leftNumber instanceof Float
|| rightNumber instanceof Double || rightNumber instanceof Float) {
return leftNumber.doubleValue() / rightNumber.doubleValue();
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
return leftNumber.longValue() / rightNumber.longValue();
}
return leftNumber.intValue() / rightNumber.intValue();
}
private Object executeMOD(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (!(left instanceof Number)) {
throw new VertexiumCypherTypeErrorException(left, Number.class);
}
if (!(right instanceof Number)) {
throw new VertexiumCypherTypeErrorException(right, Number.class);
}
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || leftNumber instanceof Float
|| rightNumber instanceof Double || rightNumber instanceof Float) {
return leftNumber.doubleValue() % rightNumber.doubleValue();
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
return leftNumber.longValue() % rightNumber.longValue();
}
return leftNumber.intValue() % rightNumber.intValue();
}
private Object executePOWER(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (!(left instanceof Number)) {
throw new VertexiumCypherTypeErrorException(left, Number.class);
}
if (!(right instanceof Number)) {
throw new VertexiumCypherTypeErrorException(right, Number.class);
}
Number leftNumber = (Number) left;
Number rightNumber = (Number) right;
if (leftNumber instanceof Double || leftNumber instanceof Float
|| rightNumber instanceof Double || rightNumber instanceof Float) {
return Math.pow(leftNumber.doubleValue(), rightNumber.doubleValue());
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
return (long) Math.pow(leftNumber.longValue(), rightNumber.longValue());
}
return (long) Math.pow(leftNumber.intValue(), rightNumber.intValue());
}
private Object executeADD(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
Object right = executeExpression(ctx, rightExpression, scope);
if (left instanceof String) {
return ((String) left) + right;
}
if (left instanceof Number && right instanceof Number) {
return ObjectUtils.addNumbers((Number) left, (Number) right);
}
if (left instanceof List && right instanceof List) {
ArrayList<Object> results = new ArrayList<>();
results.addAll((List) left);
results.addAll((List) right);
return results;
}
if (left instanceof List) {
ArrayList<Object> results = new ArrayList<>();
results.addAll((List) left);
results.add(right);
return results;
}
if (right == null) {
return null;
}
throw new VertexiumException("add not implemented left:" + left.getClass().getName() + ", right:" + right.getClass().getName());
}
private Object executeAND(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
if (left == null) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (right instanceof Boolean) {
boolean b = (boolean) right;
if (b) {
return null;
} else {
return false;
}
}
}
if (left instanceof Boolean) {
boolean bLeft = (boolean) left;
if (!bLeft) {
return false;
}
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (right instanceof Boolean) {
return right;
}
throw new VertexiumException("unexpected value in AND expression: " + right);
}
throw new VertexiumException("unexpected value in AND expression: " + left);
}
private Object executeOR(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
if (left == null) {
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (right instanceof Boolean) {
boolean b = (boolean) right;
if (b) {
return true;
} else {
return null;
}
}
}
if (left instanceof Boolean) {
boolean bLeft = (boolean) left;
if (bLeft) {
return true;
}
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
if (right instanceof Boolean) {
return right;
}
throw new VertexiumException("unexpected value in OR expression: " + right);
}
throw new VertexiumException("unexpected value in OR expression: " + left);
}
private Object executeXOR(VertexiumCypherQueryContext ctx, Object left, CypherAstBase rightExpression, ExpressionScope scope) {
if (left == null) {
return null;
}
Object right = executeExpression(ctx, rightExpression, scope);
if (right == null) {
return null;
}
throw new VertexiumCypherNotImplemented("XOR " + left + ", " + right);
}
private Object executeVariable(VertexiumCypherQueryContext ctx, CypherVariable expression, ExpressionScope scope) {
if (scope == null) {
throw new VertexiumException("Could not get variable \"" + expression.getName() + "\" last results were null");
}
return scope.getByName(expression.getName());
}
Stream<VertexiumCypherScope.Item> applyWhereToResults(
VertexiumCypherQueryContext ctx,
Stream<VertexiumCypherScope.Item> rows,
CypherAstBase whereExpression
) {
return rows
.filter(row -> {
Object result = executeExpression(ctx, whereExpression, row);
return ObjectUtils.compare(true, result) == 0;
});
}
}