/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.diql.visitors;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.RuleNode;
import org.diqube.diql.ParseException;
import org.diqube.diql.antlr.DiqlBaseVisitor;
import org.diqube.diql.antlr.DiqlParser.AnyValueContext;
import org.diqube.diql.antlr.DiqlParser.BinaryComparatorContext;
import org.diqube.diql.antlr.DiqlParser.ComparisonAndContext;
import org.diqube.diql.antlr.DiqlParser.ComparisonContext;
import org.diqube.diql.antlr.DiqlParser.ComparisonLeafContext;
import org.diqube.diql.antlr.DiqlParser.ComparisonNotContext;
import org.diqube.diql.antlr.DiqlParser.ComparisonOrContext;
import org.diqube.diql.antlr.DiqlParser.ComparisonRecursiveContext;
import org.diqube.diql.request.ComparisonRequest;
import org.diqube.diql.request.ComparisonRequest.Not;
import org.diqube.diql.request.ComparisonRequest.Operator;
import org.diqube.name.FunctionBasedColumnNameBuilderFactory;
import org.diqube.name.RepeatedColumnNameGenerator;
import org.diqube.util.ColumnOrValue;
import org.diqube.util.ColumnOrValue.Type;
/**
* Visits comparisons, which may be in a WHERE clause or a HAVING clause and returns one {@link ComparisonRequest} that
* represents the whole comparison block (that means the {@link ComparisonRequest} might point to other transitive
* {@link ComparisonRequest} objects etc.).
*
* <p>
* If aggregations/projections are encountered, they are automatically added to
* {@link ExecutionRequestVisitorEnvironment#getExecutionRequest()}.
*
* @author Bastian Gloeckle
*/
public class ComparisonVisitor extends DiqlBaseVisitor<ComparisonRequest> {
/**
* When switching the operators of a comparison, the operator needs to be adjusted. This map maps from original
* operator to the operator that should be used when the operands are exchanged.
*/
private static final Map<Operator, Operator> exchangeOperandOperators;
private ExecutionRequestVisitorEnvironment env;
private List<Class<? extends ParserRuleContext>> stopParsingForContexts;
private RepeatedColumnNameGenerator repeatedColNames;
private FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory;
/**
* @param stopParsingForContexts
* Context classes which will force this visitor to stop visiting any sub-tree of that context.
*/
public ComparisonVisitor(ExecutionRequestVisitorEnvironment env, RepeatedColumnNameGenerator repeatedColNames,
FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory,
List<Class<? extends ParserRuleContext>> stopParsingForContexts) {
this.env = env;
this.repeatedColNames = repeatedColNames;
this.functionBasedColumnNameBuilderFactory = functionBasedColumnNameBuilderFactory;
this.stopParsingForContexts = stopParsingForContexts;
}
@Override
public ComparisonRequest visitComparisonLeaf(ComparisonLeafContext comparisonCtx) {
ComparisonRequest.Leaf res = new ComparisonRequest.Leaf();
AnyValueContext firstAny = comparisonCtx.getChild(AnyValueContext.class, 0);
AnyValueContext secondAny = comparisonCtx.getChild(AnyValueContext.class, 1);
ColumnOrValue firstOperand =
firstAny.accept(new AnyValueVisitor(env, repeatedColNames, functionBasedColumnNameBuilderFactory)).getLeft();
ColumnOrValue secondOperand =
secondAny.accept(new AnyValueVisitor(env, repeatedColNames, functionBasedColumnNameBuilderFactory)).getLeft();
BinaryComparatorContext comparator = comparisonCtx.getChild(BinaryComparatorContext.class, 0);
Operator operator = null;
if (comparator.getText().equals("="))
operator = Operator.EQ;
else if (comparator.getText().equals(">="))
operator = Operator.GT_EQ;
else if (comparator.getText().equals(">"))
operator = Operator.GT;
else if (comparator.getText().equals("<="))
operator = Operator.LT_EQ;
else if (comparator.getText().equals("<"))
operator = Operator.LT;
// TODO #25 check if both operands only select from literals and implement short-cut.
if (firstOperand.getType().equals(Type.LITERAL) && secondOperand.getType().equals(Type.LITERAL))
throw new ParseException("Comparisons are currently supported only with at least one side being a column.");
if (firstOperand.getType().equals(Type.LITERAL)) {
// exchange to make sure firstOperandPair contains a column, secondOperandPair contains the literal value
ColumnOrValue tmp = firstOperand;
firstOperand = secondOperand;
secondOperand = tmp;
operator = exchangeOperandOperators.get(operator);
}
res.setLeftColumnName(firstOperand.getColumnName());
res.setRight(secondOperand);
res.setOp(operator);
return res;
}
@Override
protected ComparisonRequest aggregateResult(ComparisonRequest aggregate, ComparisonRequest nextResult) {
if (aggregate == null)
return nextResult;
return aggregate;
}
@Override
public ComparisonRequest visitComparisonOr(ComparisonOrContext ctx) {
return andOrOr(ctx, true);
}
@Override
public ComparisonRequest visitComparisonAnd(ComparisonAndContext ctx) {
return andOrOr(ctx, false);
}
@Override
public ComparisonRequest visitComparisonNot(ComparisonNotContext ctx) {
ComparisonRequest child = ctx.getChild(ComparisonContext.class, 0).accept(this);
ComparisonRequest.Not res = new Not();
res.setChild(child);
return res;
}
private ComparisonRequest andOrOr(ComparisonContext ctx, boolean isOr) {
ComparisonRequest leftChild = ctx.getChild(ComparisonContext.class, 0).accept(this);
ComparisonRequest rightChild = ctx.getChild(ComparisonContext.class, 1).accept(this);
ComparisonRequest.DelegateComparisonRequest res;
if (isOr)
res = new ComparisonRequest.Or();
else
res = new ComparisonRequest.And();
res.setLeft(leftChild);
res.setRight(rightChild);
return res;
}
@Override
public ComparisonRequest visitComparisonRecursive(ComparisonRecursiveContext ctx) {
return ctx.getChild(ComparisonContext.class, 0).accept(this);
}
@Override
protected boolean shouldVisitNextChild(RuleNode node, ComparisonRequest currentResult) {
for (Class<? extends ParserRuleContext> stopClass : stopParsingForContexts) {
if (stopClass.isInstance(node))
return false;
}
return true;
}
static {
exchangeOperandOperators = new HashMap<>();
exchangeOperandOperators.put(Operator.EQ, Operator.EQ);
exchangeOperandOperators.put(Operator.GT, Operator.LT);
exchangeOperandOperators.put(Operator.GT_EQ, Operator.LT_EQ);
exchangeOperandOperators.put(Operator.LT, Operator.GT);
exchangeOperandOperators.put(Operator.LT_EQ, Operator.GT_EQ);
}
}