com.yahoo.schema.expressiontransforms.BooleanExpressionTransformer Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema.expressiontransforms;
import com.yahoo.searchlib.rankingexpression.evaluation.BooleanValue;
import com.yahoo.searchlib.rankingexpression.rule.OperationNode;
import com.yahoo.searchlib.rankingexpression.rule.Operator;
import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
import com.yahoo.searchlib.rankingexpression.rule.ConstantNode;
import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
import com.yahoo.searchlib.rankingexpression.rule.IfNode;
import com.yahoo.searchlib.rankingexpression.transform.ExpressionTransformer;
import com.yahoo.searchlib.rankingexpression.transform.TransformContext;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
/**
* Transforms
* a && b into if(a, b, false)
* and
* a || b into if(a, true, b)
* to avoid computing b if a is false and true respectively.
*
* This may increase performance since boolean expressions are not short-circuited.
*
* @author bratseth
*/
public class BooleanExpressionTransformer extends ExpressionTransformer {
@Override
public ExpressionNode transform(ExpressionNode node, TransformContext context) {
if (node instanceof CompositeNode composite)
node = transformChildren(composite, context);
if (node instanceof OperationNode arithmetic)
node = transformBooleanArithmetics(arithmetic, context);
return node;
}
private ExpressionNode transformBooleanArithmetics(OperationNode node, TransformContext context) {
Iterator child = node.children().iterator();
// Transform in precedence order:
Deque stack = new ArrayDeque<>();
stack.push(new ChildNode(null, child.next()));
for (Iterator it = node.operators().iterator(); it.hasNext() && child.hasNext();) {
Operator op = it.next();
if ( ! stack.isEmpty()) {
while (stack.size() > 1 && ! op.hasPrecedenceOver(stack.peek().op)) {
popStack(stack, context);
}
}
stack.push(new ChildNode(op, child.next()));
}
while (stack.size() > 1)
popStack(stack, context);
return stack.getFirst().child;
}
private void popStack(Deque stack, TransformContext context) {
ChildNode rhs = stack.pop();
ChildNode lhs = stack.peek();
// isDefinitelyPrimitive is expensive so only invoke it when necessary
ExpressionNode combination;
if (rhs.op == Operator.and && isDefinitelyPrimitive(lhs.child, context) && isDefinitelyPrimitive(rhs.child, context))
combination = andByIfNode(lhs.child, rhs.child);
else if (rhs.op == Operator.or && isDefinitelyPrimitive(lhs.child, context) && isDefinitelyPrimitive(rhs.child, context))
combination = orByIfNode(lhs.child, rhs.child);
else {
combination = resolve(lhs, rhs);
lhs.artificial = true;
}
lhs.child = combination;
}
private boolean isDefinitelyPrimitive(ExpressionNode node, TransformContext context) {
try {
return node.type(context.types()).rank() == 0;
}
catch (IllegalArgumentException e) {
// Types can only be reliably resolved top down, which has not done here.
// E.g
// function(nameArg) {
// attribute(nameArg)
// }
// is supported.
// So, we return false when something cannot be resolved.
return false;
}
}
private static OperationNode resolve(ChildNode left, ChildNode right) {
if (! (left.child instanceof OperationNode) && ! (right.child instanceof OperationNode))
return new OperationNode(left.child, right.op, right.child);
// Collapse inserted ArithmeticNodes
List joinedOps = new ArrayList<>();
joinOps(left, joinedOps);
joinedOps.add(right.op);
joinOps(right, joinedOps);
List joinedChildren = new ArrayList<>();
joinChildren(left, joinedChildren);
joinChildren(right, joinedChildren);
return new OperationNode(joinedChildren, joinedOps);
}
private static void joinOps(ChildNode node, List joinedOps) {
if (node.artificial && node.child instanceof OperationNode operationNode)
joinedOps.addAll(operationNode.operators());
}
private static void joinChildren(ChildNode node, List joinedChildren) {
if (node.artificial && node.child instanceof OperationNode operationNode)
joinedChildren.addAll(operationNode.children());
else
joinedChildren.add(node.child);
}
private IfNode andByIfNode(ExpressionNode a, ExpressionNode b) {
return new IfNode(a, b, new ConstantNode(new BooleanValue(false)));
}
private IfNode orByIfNode(ExpressionNode a, ExpressionNode b) {
return new IfNode(a, new ConstantNode(new BooleanValue(true)), b);
}
/** A child with the operator to be applied to it when combining it with the previous child. */
private static class ChildNode {
final Operator op;
ExpressionNode child;
boolean artificial;
public ChildNode(Operator op, ExpressionNode child) {
this.op = op;
this.child = child;
}
@Override
public String toString() {
return child.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy