/** * 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 org.diqube.diql.ParseException; import org.diqube.diql.antlr.DiqlBaseVisitor; import org.diqube.diql.antlr.DiqlParser.AggregationFunctionNameContext; import org.diqube.diql.antlr.DiqlParser.AnyValueContext; import org.diqube.diql.antlr.DiqlParser.ColumnNameContext; import org.diqube.diql.antlr.DiqlParser.DecimalLiteralValueContext; import org.diqube.diql.antlr.DiqlParser.DoubleLiteralValueContext; import org.diqube.diql.antlr.DiqlParser.FunctionContext; import org.diqube.diql.antlr.DiqlParser.LiteralValueContext; import org.diqube.diql.antlr.DiqlParser.ProjectionFunctionNameContext; import org.diqube.diql.antlr.DiqlParser.StringLiteralValueContext; import org.diqube.diql.request.ExecutionRequest; import org.diqube.diql.request.FunctionRequest; import org.diqube.diql.request.FunctionRequest.Type; import org.diqube.name.FunctionBasedColumnNameBuilder; import org.diqube.name.FunctionBasedColumnNameBuilderFactory; import org.diqube.name.RepeatedColumnNameGenerator; import org.diqube.util.ColumnOrValue; import org.diqube.util.Pair; /** * Parses and {@link AnyValueContext} including references to literal values, column names and aggregate and projection * functions and any hierarchy thereof. * * <p> * Any projection/grouping is added correctly to the ExecutionRequest of the {@link ExecutionRequestVisitorEnvironment} * (method {@link ExecutionRequest#getProjectAndAggregate()}) that is specified in the constructor. * * <p> * The result of this visitor is a pair containing the resulting column or value and a boolean indicating if the result * column is an array column (happens when projecting on repeated fields, [*] syntax). * * @author Bastian Gloeckle */ public class AnyValueVisitor extends DiqlBaseVisitor<Pair<ColumnOrValue, Boolean>> { private ExecutionRequestVisitorEnvironment env; private RepeatedColumnNameGenerator repeatedColName; private FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory; public AnyValueVisitor(ExecutionRequestVisitorEnvironment env, RepeatedColumnNameGenerator repeatedColName, FunctionBasedColumnNameBuilderFactory functionBasedColumnNameBuilderFactory) { this.env = env; this.repeatedColName = repeatedColName; this.functionBasedColumnNameBuilderFactory = functionBasedColumnNameBuilderFactory; } @Override public Pair<ColumnOrValue, Boolean> visitFunction(FunctionContext ctx) { // The output column of the function needs a name. We use a builder to build that. FunctionBasedColumnNameBuilder colNameBuilder = functionBasedColumnNameBuilderFactory.create(); FunctionRequest functionRequest = new FunctionRequest(); // parse function name if (ctx.getChild(0) instanceof ProjectionFunctionNameContext) { ProjectionFunctionNameContext projCtx = (ProjectionFunctionNameContext) ctx.getChild(0); String functionName = projCtx.getText().toLowerCase(); if (functionName.endsWith("(")) functionName = functionName.substring(0, functionName.length() - 1); functionRequest.setFunctionName(functionName); functionRequest.setType(Type.PROJECTION); colNameBuilder.withFunctionName(functionName); } else if (ctx.getChild(0) instanceof AggregationFunctionNameContext) { AggregationFunctionNameContext projCtx = (AggregationFunctionNameContext) ctx.getChild(0); String functionName = projCtx.getText().toLowerCase(); if (functionName.endsWith("(")) functionName = functionName.substring(0, functionName.length() - 1); functionRequest.setFunctionName(functionName); functionRequest.setType(Type.AGGREGATION_ROW); colNameBuilder.withFunctionName(functionName); } else throw new ParseException("Could not parse function name."); env.getExecutionRequest().getProjectAndAggregate().add(functionRequest); boolean isArrayInput = false; int anyValueChildCnt = 0; AnyValueContext anyValueCtx; // parse function parameters while ((anyValueCtx = ctx.getChild(AnyValueContext.class, anyValueChildCnt++)) != null) { Pair<ColumnOrValue, Boolean> childResult = anyValueCtx.accept(this); functionRequest.getInputParameters().add(childResult.getLeft()); if (childResult.getRight()) isArrayInput = true; // add the childs parameter to the name of the column that will be created by executing this function. if (childResult.getLeft().getType().equals(ColumnOrValue.Type.COLUMN)) { String childColName = childResult.getLeft().getColumnName(); colNameBuilder.addParameterColumnName(childColName); } else { Object childValue = childResult.getLeft().getValue(); if (childValue instanceof String) colNameBuilder.addParameterLiteralString((String) childValue); else if (childValue instanceof Long) colNameBuilder.addParameterLiteralLong((Long) childValue); else if (childValue instanceof Double) colNameBuilder.addParameterLiteralDouble((Double) childValue); else throw new ParseException("Function parameter did not provide valid literal value."); } } functionRequest.setOutputColumn(colNameBuilder.build()); boolean isArrayOutput = false; if (isArrayInput) { if (functionRequest.getType().equals(Type.PROJECTION)) { // projecting on an array input value will result in an array output value, we're projecting over a repeated // field. functionRequest.setType(Type.REPEATED_PROJECTION); isArrayOutput = true; // make clear that we're outputting an array value. functionRequest .setOutputColumn(functionRequest.getOutputColumn() + repeatedColName.allEntriesIdentifyingSubstr()); } else { // aggregation function. We're aggregating over an array, result will be a single value, but we are aggregating // over column values. functionRequest.setType(Type.AGGREGATION_COL); } } return new Pair<>(new ColumnOrValue(ColumnOrValue.Type.COLUMN, functionRequest.getOutputColumn()), isArrayOutput); } @Override public Pair<ColumnOrValue, Boolean> visitAnyValue(AnyValueContext anyValueCtx) { if (anyValueCtx.getChild(0) instanceof LiteralValueContext) { LiteralValueContext literalCtx = anyValueCtx.getChild(LiteralValueContext.class, 0); String valueText = literalCtx.getText(); Object value; if (literalCtx.getChild(0) instanceof DecimalLiteralValueContext) value = Long.parseLong(valueText); else if (literalCtx.getChild(0) instanceof StringLiteralValueContext) value = parseStringValue(valueText); else if (literalCtx.getChild(0) instanceof DoubleLiteralValueContext) value = Double.parseDouble(valueText); else throw new ParseException("Could not parse literal value at " + literalCtx.toString()); return new Pair<>(new ColumnOrValue(ColumnOrValue.Type.LITERAL, value), false); } else if (anyValueCtx.getChild(0) instanceof ColumnNameContext) { String colName = anyValueCtx.getChild(ColumnNameContext.class, 0).getText(); boolean isArray = colName.contains(repeatedColName.allEntriesIdentifyingSubstr()); env.getExecutionRequest().getAdditionalInfo().getColumnNamesRequired().add(colName); return new Pair<>(new ColumnOrValue(ColumnOrValue.Type.COLUMN, colName), isArray); } else if (anyValueCtx.getChild(0) instanceof FunctionContext) { return visitFunction(anyValueCtx.getChild(FunctionContext.class, 0)); } throw new ParseException("Could not parse AnyValueContext as there were no alternatives left"); } private String parseStringValue(String diql) { // each string starts end ends with a single ' String work = diql.substring(1, diql.length() - 1); // un-escape \' work = work.replaceAll("\\\\'", "'"); return work; } }