/*
* Licensed to Crate under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership. Crate licenses this file
* to you under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial
* agreement.
*/
package io.crate.analyze.expressions;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
import io.crate.action.sql.Option;
import io.crate.action.sql.SessionContext;
import io.crate.analyze.DataTypeAnalyzer;
import io.crate.analyze.NegativeLiteralVisitor;
import io.crate.analyze.SubscriptContext;
import io.crate.analyze.SubscriptValidator;
import io.crate.analyze.relations.AnalyzedRelation;
import io.crate.analyze.relations.FieldProvider;
import io.crate.analyze.symbol.*;
import io.crate.analyze.symbol.Literal;
import io.crate.analyze.symbol.format.SymbolFormatter;
import io.crate.exceptions.ColumnUnknownException;
import io.crate.exceptions.ConversionException;
import io.crate.exceptions.UnsupportedFeatureException;
import io.crate.metadata.*;
import io.crate.metadata.table.Operation;
import io.crate.operation.aggregation.impl.CollectSetAggregation;
import io.crate.operation.operator.*;
import io.crate.operation.operator.any.AnyEqOperator;
import io.crate.operation.operator.any.AnyLikeOperator;
import io.crate.operation.operator.any.AnyNotLikeOperator;
import io.crate.operation.operator.any.AnyOperator;
import io.crate.operation.predicate.NotPredicate;
import io.crate.operation.scalar.ExtractFunctions;
import io.crate.operation.scalar.SubscriptFunction;
import io.crate.operation.scalar.SubscriptObjectFunction;
import io.crate.operation.scalar.arithmetic.ArrayFunction;
import io.crate.operation.scalar.arithmetic.MapFunction;
import io.crate.operation.scalar.cast.CastFunctionResolver;
import io.crate.operation.scalar.conditional.IfFunction;
import io.crate.operation.scalar.timestamp.CurrentTimestampFunction;
import io.crate.sql.ExpressionFormatter;
import io.crate.sql.parser.SqlParser;
import io.crate.sql.tree.*;
import io.crate.sql.tree.MatchPredicate;
import io.crate.types.*;
import javax.annotation.Nullable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>This Analyzer can be used to convert Expression from the SQL AST into symbols.</p>
* <p>
* <p>
* In order to resolve QualifiedName or SubscriptExpressions it will use the fieldResolver given in the constructor and
* generate a relationOutput for the matching Relation.
* </p>
*/
public class ExpressionAnalyzer {
private final static Map<ComparisonExpression.Type, ComparisonExpression.Type> SWAP_OPERATOR_TABLE =
ImmutableMap.<ComparisonExpression.Type, ComparisonExpression.Type>builder()
.put(ComparisonExpression.Type.GREATER_THAN, ComparisonExpression.Type.LESS_THAN)
.put(ComparisonExpression.Type.LESS_THAN, ComparisonExpression.Type.GREATER_THAN)
.put(ComparisonExpression.Type.GREATER_THAN_OR_EQUAL, ComparisonExpression.Type.LESS_THAN_OR_EQUAL)
.put(ComparisonExpression.Type.LESS_THAN_OR_EQUAL, ComparisonExpression.Type.GREATER_THAN_OR_EQUAL)
.build();
private final static NegativeLiteralVisitor NEGATIVE_LITERAL_VISITOR = new NegativeLiteralVisitor();
private final SessionContext sessionContext;
private final java.util.function.Function<ParameterExpression, Symbol> convertParamFunction;
private final FieldProvider<?> fieldProvider;
@Nullable
private final SubqueryAnalyzer subQueryAnalyzer;
private final Functions functions;
private final InnerExpressionAnalyzer innerAnalyzer;
private Operation operation = Operation.READ;
private static final Pattern SUBSCRIPT_SPLIT_PATTERN = Pattern.compile("^([^\\.\\[]+)(\\.*)([^\\[]*)(\\['.*'\\])");
public ExpressionAnalyzer(Functions functions,
SessionContext sessionContext,
java.util.function.Function<ParameterExpression, Symbol> convertParamFunction,
FieldProvider fieldProvider,
@Nullable SubqueryAnalyzer subQueryAnalyzer) {
this.functions = functions;
this.sessionContext = sessionContext;
this.convertParamFunction = convertParamFunction;
this.fieldProvider = fieldProvider;
this.subQueryAnalyzer = subQueryAnalyzer;
this.innerAnalyzer = new InnerExpressionAnalyzer();
}
/**
* <h2>Converts an expression into a symbol.</h2>
* <p>
* <p>
* Expressions like QualifiedName that reference a column are resolved using the fieldResolver that were passed
* to the constructor.
* </p>
* <p>
* <p>
* Some information (like resolved function symbols) are written onto the given expressionAnalysisContext
* </p>
*/
public Symbol convert(Expression expression, ExpressionAnalysisContext expressionAnalysisContext) {
return innerAnalyzer.process(expression, expressionAnalysisContext);
}
public Symbol generateQuerySymbol(Optional<Expression> whereExpression, ExpressionAnalysisContext context) {
if (whereExpression.isPresent()) {
return convert(whereExpression.get(), context);
} else {
return Literal.BOOLEAN_TRUE;
}
}
private FunctionInfo getBuiltinFunctionInfo(String name, List<DataType> argumentTypes) {
FunctionImplementation impl = functions.getBuiltin(name, argumentTypes);
if (impl == null) {
throw Functions.createUnknownFunctionException(name, argumentTypes);
}
return impl.info();
}
private FunctionInfo getBuiltinOrUdfFunctionInfo(@Nullable String schema, String name, List<DataType> argumentTypes) {
FunctionImplementation impl;
if (schema == null) {
impl = functions.getBuiltin(name, argumentTypes);
if (impl == null) {
impl = functions.getUserDefined(sessionContext.defaultSchema(), name, argumentTypes);
}
} else {
impl = functions.getUserDefined(schema, name, argumentTypes);
}
return impl.info();
}
protected Symbol convertFunctionCall(FunctionCall node, ExpressionAnalysisContext context) {
List<Symbol> arguments = new ArrayList<>(node.getArguments().size());
List<DataType> argumentTypes = new ArrayList<>(node.getArguments().size());
for (Expression expression : node.getArguments()) {
Symbol argSymbol = expression.accept(innerAnalyzer, context);
argumentTypes.add(argSymbol.valueType());
arguments.add(argSymbol);
}
List<String> parts = node.getName().getParts();
String schema = null;
String name;
if (parts.size() == 1) {
name = parts.get(0);
} else {
schema = parts.get(0);
name = parts.get(1);
}
FunctionInfo functionInfo;
if (node.isDistinct()) {
if (argumentTypes.size() > 1) {
throw new UnsupportedOperationException(String.format(Locale.ENGLISH,
"%s(DISTINCT x) does not accept more than one argument", node.getName()));
}
// define the inner function. use the arguments/argumentTypes from above
Symbol innerFunction = context.allocateFunction(
getBuiltinOrUdfFunctionInfo(schema, CollectSetAggregation.NAME, argumentTypes),
arguments
);
// define the outer function which contains the inner function as argument.
String nodeName = "collection_" + name;
List<Symbol> outerArguments = Arrays.asList(innerFunction); // needs to be mutable
List<DataType> outerArgumentTypes = ImmutableList.of(new SetType(argumentTypes.get(0))); // can be immutable
try {
functionInfo = getBuiltinFunctionInfo(nodeName, outerArgumentTypes);
} catch (UnsupportedOperationException ex) {
throw new UnsupportedOperationException(String.format(Locale.ENGLISH,
"unknown function %s(DISTINCT %s)", name, argumentTypes.get(0)), ex);
}
arguments = outerArguments;
} else {
functionInfo = getBuiltinOrUdfFunctionInfo(schema, name, argumentTypes);
}
return context.allocateFunction(functionInfo, arguments);
}
public void setResolveFieldsOperation(Operation operation) {
this.operation = operation;
}
public static Symbol castIfNeededOrFail(Symbol symbolToCast, DataType targetType) {
DataType sourceType = symbolToCast.valueType();
if (sourceType.equals(targetType)) {
return symbolToCast;
}
if (sourceType.isConvertableTo(targetType)) {
// cast further below doesn't always fail because it might be lazy as it wraps functions/references inside a cast function.
// -> Need to check isConvertableTo to fail eagerly if the cast won't work.
return cast(symbolToCast, targetType, false);
}
throw new ConversionException(symbolToCast, targetType);
}
private static Symbol cast(Symbol sourceSymbol, DataType targetType, boolean tryCast) {
if (sourceSymbol.symbolType().isValueSymbol()) {
return Literal.convert(sourceSymbol, targetType);
}
return CastFunctionResolver.generateCastFunction(sourceSymbol, targetType, tryCast);
}
@Nullable
protected static String getQuotedSubscriptLiteral(String nodeName) {
Matcher matcher = SUBSCRIPT_SPLIT_PATTERN.matcher(nodeName);
if (matcher.matches()) {
StringBuilder quoted = new StringBuilder();
String group1 = matcher.group(1);
if (!group1.isEmpty()) {
quoted.append("\"").append(group1).append("\"");
} else {
quoted.append(group1);
}
String group2 = matcher.group(2);
String group3 = matcher.group(3);
if (!group2.isEmpty() && !group3.isEmpty()) {
quoted.append(matcher.group(2));
quoted.append("\"").append(group3).append("\"");
} else if (!group2.isEmpty() && group3.isEmpty()) {
return null;
}
quoted.append(matcher.group(4));
return quoted.toString();
} else {
return null;
}
}
private class InnerExpressionAnalyzer extends AstVisitor<Symbol, ExpressionAnalysisContext> {
@Override
protected Symbol visitNode(Node node, ExpressionAnalysisContext context) {
throw new UnsupportedOperationException(String.format(Locale.ENGLISH,
"Unsupported node %s", node));
}
@Override
protected Symbol visitExpression(Expression node, ExpressionAnalysisContext context) {
throw new UnsupportedOperationException(String.format(Locale.ENGLISH,
"Unsupported expression %s", ExpressionFormatter.formatExpression(node)));
}
@Override
protected Symbol visitCurrentTime(CurrentTime node, ExpressionAnalysisContext context) {
if (!node.getType().equals(CurrentTime.Type.TIMESTAMP)) {
visitExpression(node, context);
}
List<Symbol> args = Lists.newArrayList(
Literal.of(node.getPrecision().orElse(CurrentTimestampFunction.DEFAULT_PRECISION))
);
return context.allocateFunction(CurrentTimestampFunction.INFO, args);
}
@Override
protected Symbol visitIfExpression(IfExpression node, ExpressionAnalysisContext context) {
// check for global operand
Optional<Expression> defaultExpression = node.getFalseValue();
List<Symbol> arguments = new ArrayList<>(defaultExpression.isPresent() ? 3 : 2);
arguments.add(node.getCondition().accept(innerAnalyzer, context));
arguments.add(node.getTrueValue().accept(innerAnalyzer, context));
if (defaultExpression.isPresent()) {
arguments.add(defaultExpression.get().accept(innerAnalyzer, context));
}
return IfFunction.createFunction(arguments);
}
@Override
protected Symbol visitFunctionCall(FunctionCall node, ExpressionAnalysisContext context) {
// If it's subscript function then use the special handling
// and validation that is used for the subscript operator `[]`
if (node.getName().toString().equalsIgnoreCase(SubscriptFunction.NAME)) {
assert node.getArguments().size() == 2 : "Number of arguments for subscript function must be 2";
return visitSubscriptExpression(
new SubscriptExpression(node.getArguments().get(0), node.getArguments().get(1)), context);
}
return convertFunctionCall(node, context);
}
@Override
protected Symbol visitSimpleCaseExpression(SimpleCaseExpression node, ExpressionAnalysisContext context) {
return convertCaseExpressionToIfFunctions(node.getWhenClauses(), node.getOperand(),
node.getDefaultValue(), context);
}
@Override
protected Symbol visitSearchedCaseExpression(SearchedCaseExpression node, ExpressionAnalysisContext context) {
return convertCaseExpressionToIfFunctions(node.getWhenClauses(), null, node.getDefaultValue(), context);
}
private Symbol convertCaseExpressionToIfFunctions(List<WhenClause> whenClauseExpressions,
@Nullable Expression operandExpression,
@Nullable Expression defaultValue,
ExpressionAnalysisContext context) {
List<Symbol> operands = new ArrayList<>(whenClauseExpressions.size());
List<Symbol> results = new ArrayList<>(whenClauseExpressions.size());
Set<DataType> resultsTypes = new HashSet<>(whenClauseExpressions.size());
// check for global operand
Symbol operandLeftSymbol = null;
if (operandExpression != null) {
operandLeftSymbol = operandExpression.accept(innerAnalyzer, context);
}
for (WhenClause whenClause : whenClauseExpressions) {
Symbol operandRightSymbol = whenClause.getOperand().accept(innerAnalyzer, context);
Symbol operandSymbol = operandRightSymbol;
if (operandLeftSymbol != null) {
operandSymbol = EqOperator.createFunction(
operandLeftSymbol, castIfNeededOrFail(operandRightSymbol, operandLeftSymbol.valueType()));
}
operands.add(operandSymbol);
Symbol resultSymbol = whenClause.getResult().accept(innerAnalyzer, context);
results.add(resultSymbol);
resultsTypes.add(resultSymbol.valueType());
}
if (resultsTypes.size() == 2 && !resultsTypes.contains(DataTypes.UNDEFINED)
|| resultsTypes.size() > 2) {
throw new UnsupportedOperationException(String.format(Locale.ENGLISH,
"Data types of all result expressions of a CASE statement must be equal, found: %s",
resultsTypes));
}
Symbol defaultValueSymbol = null;
if (defaultValue != null) {
defaultValueSymbol = defaultValue.accept(innerAnalyzer, context);
}
return IfFunction.createChain(operands, results, defaultValueSymbol);
}
@Override
protected Symbol visitCast(Cast node, ExpressionAnalysisContext context) {
DataType returnType = DataTypeAnalyzer.convert(node.getType());
return cast(process(node.getExpression(), context), returnType, false);
}
@Override
protected Symbol visitTryCast(TryCast node, ExpressionAnalysisContext context) {
DataType returnType = DataTypeAnalyzer.convert(node.getType());
if (CastFunctionResolver.supportsExplicitConversion(returnType)) {
try {
return cast(process(node.getExpression(), context), returnType, true);
} catch (ConversionException e) {
return Literal.NULL;
}
}
throw new IllegalArgumentException(
String.format(Locale.ENGLISH, "No cast function found for return type %s", returnType.getName()));
}
@Override
protected Symbol visitExtract(Extract node, ExpressionAnalysisContext context) {
Symbol expression = process(node.getExpression(), context);
expression = castIfNeededOrFail(expression, DataTypes.TIMESTAMP);
Symbol field = castIfNeededOrFail(process(node.getField(), context), DataTypes.STRING);
return context.allocateFunction(
ExtractFunctions.GENERIC_INFO, Arrays.asList(field, expression));
}
@Override
protected Symbol visitInPredicate(InPredicate node, ExpressionAnalysisContext context) {
/*
* convert where x IN (values)
*
* where values = a list of expressions
*
* into
*
* x = ANY(array(1, 2, 3, ...))
*/
Symbol left = process(node.getValue(), context);
DataType targetType = left.valueType();
Expression valueList = node.getValueList();
if (!(valueList instanceof InListExpression)) {
throw new UnsupportedOperationException(String.format(Locale.ENGLISH,
"Expression %s is not supported in IN", ExpressionFormatter.formatExpression(valueList)));
}
List<Expression> expressions = ((InListExpression) valueList).getValues();
List<Symbol> symbols = new ArrayList<>(expressions.size());
for (Expression expression : expressions) {
Symbol symbol = process(expression, context);
if (targetType == DataTypes.UNDEFINED) {
targetType = symbol.valueType();
left = castIfNeededOrFail(left, targetType);
symbols.add(symbol);
} else {
symbols.add(castIfNeededOrFail(symbol, targetType));
}
}
return context.allocateFunction(
AnyEqOperator.createInfo(targetType),
Arrays.asList(
left, context.allocateFunction(ArrayFunction.createInfo(Symbols.extractTypes(symbols)), symbols))
);
}
@Override
protected Symbol visitIsNotNullPredicate(IsNotNullPredicate node, ExpressionAnalysisContext context) {
Symbol argument = process(node.getValue(), context);
FunctionInfo isNullInfo =
getBuiltinFunctionInfo(io.crate.operation.predicate.IsNullPredicate.NAME, ImmutableList.of(argument.valueType()));
return context.allocateFunction(
NotPredicate.INFO,
Arrays.asList(context.allocateFunction(isNullInfo, Arrays.asList(argument))));
}
@Override
protected Symbol visitSubscriptExpression(SubscriptExpression node, ExpressionAnalysisContext context) {
SubscriptContext subscriptContext = new SubscriptContext();
SubscriptValidator.validate(node, subscriptContext);
return resolveSubscriptSymbol(subscriptContext, context);
}
Symbol resolveSubscriptSymbol(SubscriptContext subscriptContext, ExpressionAnalysisContext context) {
// TODO: support nested subscripts as soon as DataTypes.OBJECT elements can be typed
Symbol subscriptSymbol;
Expression subscriptExpression = subscriptContext.expression();
if (subscriptContext.qName() != null && subscriptExpression == null) {
subscriptSymbol = fieldProvider.resolveField(subscriptContext.qName(), subscriptContext.parts(), operation);
} else if (subscriptExpression != null) {
subscriptSymbol = subscriptExpression.accept(this, context);
} else {
throw new UnsupportedOperationException("Only references, function calls or array literals " +
"are valid subscript symbols");
}
assert subscriptSymbol != null : "subscriptSymbol must not be null";
Expression index = subscriptContext.index();
List<String> parts = subscriptContext.parts();
if (index != null) {
Symbol indexSymbol = index.accept(this, context);
// rewrite array access to subscript scalar
return context.allocateFunction(
getBuiltinFunctionInfo(
SubscriptFunction.NAME,
ImmutableList.of(subscriptSymbol.valueType(), indexSymbol.valueType())),
Arrays.asList(subscriptSymbol, indexSymbol)
);
} else if (parts != null && subscriptExpression != null) {
FunctionInfo info = getBuiltinFunctionInfo( SubscriptObjectFunction.NAME,
ImmutableList.of(subscriptSymbol.valueType(), DataTypes.STRING));
Symbol function = context.allocateFunction(info, Arrays.asList(subscriptSymbol, Literal.of(parts.get(0))));
for (int i = 1; i < parts.size(); i++) {
function = context.allocateFunction(info, Arrays.asList(function, Literal.of(parts.get(i))));
}
return function;
}
return subscriptSymbol;
}
@Override
protected Symbol visitLogicalBinaryExpression(LogicalBinaryExpression node, ExpressionAnalysisContext context) {
FunctionInfo functionInfo;
switch (node.getType()) {
case AND:
functionInfo = AndOperator.INFO;
break;
case OR:
functionInfo = OrOperator.INFO;
break;
default:
throw new UnsupportedOperationException(
"Unsupported logical binary expression " + node.getType().name());
}
List<Symbol> arguments = new ArrayList<>(2);
arguments.add(process(node.getLeft(), context));
arguments.add(process(node.getRight(), context));
return context.allocateFunction(functionInfo, arguments);
}
@Override
protected Symbol visitNotExpression(NotExpression node, ExpressionAnalysisContext context) {
Symbol argument = process(node.getValue(), context);
return context.allocateFunction(
getBuiltinFunctionInfo(NotPredicate.NAME, Arrays.asList(argument.valueType())), Arrays.asList(argument));
}
@Override
protected Symbol visitComparisonExpression(ComparisonExpression node, ExpressionAnalysisContext context) {
Symbol left = process(node.getLeft(), context);
Symbol right = process(node.getRight(), context);
Comparison comparison = new Comparison(node.getType(), left, right);
comparison.normalize(context);
FunctionIdent ident = comparison.toFunctionIdent();
return context.allocateFunction(getBuiltinFunctionInfo(ident.name(), ident.argumentTypes()), comparison.arguments());
}
@Override
public Symbol visitArrayComparisonExpression(ArrayComparisonExpression node, ExpressionAnalysisContext context) {
if (node.quantifier().equals(ArrayComparisonExpression.Quantifier.ALL)) {
throw new UnsupportedFeatureException("ALL is not supported");
}
Symbol arraySymbol = process(node.getRight(), context);
Symbol leftSymbol = process(node.getLeft(), context);
DataType rightType = arraySymbol.valueType();
if (!DataTypes.isCollectionType(rightType)) {
throw new IllegalArgumentException(
SymbolFormatter.format("invalid array expression: '%s'", arraySymbol));
}
DataType rightInnerType = ((CollectionType) rightType).innerType();
if (rightInnerType.equals(DataTypes.OBJECT)) {
throw new IllegalArgumentException("ANY on object arrays is not supported");
}
// There is no proper type-precedence logic in place yet,
// but in this case the side which has a column instead of functions/literals should dictate the type.
// x = ANY([null]) -> must not result in to-null casts
// int_col = ANY([1, 2, 3]) -> must cast to int instead of long (otherwise lucene query would be slow)
// null = ANY([1, 2]) -> must not cast to null
if (SymbolVisitors.any(symbol -> symbol instanceof Field, leftSymbol) || rightInnerType == DataTypes.UNDEFINED) {
arraySymbol = castIfNeededOrFail(arraySymbol, new ArrayType(leftSymbol.valueType()));
} else {
leftSymbol = castIfNeededOrFail(leftSymbol, rightInnerType);
}
ComparisonExpression.Type operationType = node.getType();
String operatorName;
operatorName = AnyOperator.OPERATOR_PREFIX + operationType.getValue();
return context.allocateFunction(
getBuiltinFunctionInfo(operatorName, Arrays.asList(leftSymbol.valueType(), arraySymbol.valueType())),
Arrays.asList(leftSymbol, arraySymbol));
}
@Override
public Symbol visitArrayLikePredicate(ArrayLikePredicate node, ExpressionAnalysisContext context) {
if (node.getEscape() != null) {
throw new UnsupportedOperationException("ESCAPE is not supported.");
}
Symbol rightSymbol = process(node.getValue(), context);
Symbol leftSymbol = process(node.getPattern(), context);
DataType rightType = rightSymbol.valueType();
if (!DataTypes.isCollectionType(rightType)) {
throw new IllegalArgumentException(
SymbolFormatter.format("invalid array expression: '%s'", rightSymbol));
}
rightSymbol = castIfNeededOrFail(rightSymbol, new ArrayType(DataTypes.STRING));
String operatorName = node.inverse() ? AnyNotLikeOperator.NAME : AnyLikeOperator.NAME;
return context.allocateFunction(
getBuiltinFunctionInfo(operatorName, Arrays.asList(leftSymbol.valueType(), rightSymbol.valueType())),
Arrays.asList(leftSymbol, rightSymbol));
}
@Override
protected Symbol visitLikePredicate(LikePredicate node, ExpressionAnalysisContext context) {
if (node.getEscape() != null) {
throw new UnsupportedOperationException("ESCAPE is not supported.");
}
Symbol expression = process(node.getValue(), context);
expression = castIfNeededOrFail(expression, DataTypes.STRING);
Symbol pattern = castIfNeededOrFail(process(node.getPattern(), context), DataTypes.STRING);
return context.allocateFunction(
getBuiltinFunctionInfo(LikeOperator.NAME, Arrays.asList(expression.valueType(), pattern.valueType())),
Arrays.asList(expression, pattern));
}
@Override
protected Symbol visitIsNullPredicate(IsNullPredicate node, ExpressionAnalysisContext context) {
Symbol value = process(node.getValue(), context);
return context.allocateFunction(
getBuiltinFunctionInfo(io.crate.operation.predicate.IsNullPredicate.NAME, ImmutableList.of(value.valueType())),
Arrays.asList(value));
}
@Override
protected Symbol visitNegativeExpression(NegativeExpression node, ExpressionAnalysisContext context) {
// in statements like "where x = -1" the positive (expression)IntegerLiteral (1)
// is just wrapped inside a negativeExpression
// the visitor here swaps it to getBuiltin -1 in a (symbol)LiteralInteger
return NEGATIVE_LITERAL_VISITOR.process(process(node.getValue(), context), null);
}
@Override
protected Symbol visitArithmeticExpression(ArithmeticExpression node, ExpressionAnalysisContext context) {
Symbol left = process(node.getLeft(), context);
Symbol right = process(node.getRight(), context);
return context.allocateFunction(
getBuiltinFunctionInfo(
node.getType().name().toLowerCase(Locale.ENGLISH),
Arrays.asList(left.valueType(), right.valueType())),
Arrays.asList(left, right));
}
@Override
protected Symbol visitQualifiedNameReference(QualifiedNameReference node, ExpressionAnalysisContext context) {
try {
return fieldProvider.resolveField(node.getName(), null, operation);
} catch (ColumnUnknownException exception) {
if (sessionContext.options().contains(Option.ALLOW_QUOTED_SUBSCRIPT)) {
String quotedSubscriptLiteral = getQuotedSubscriptLiteral(node.getName().toString());
if (quotedSubscriptLiteral != null) {
return process(SqlParser.createExpression(quotedSubscriptLiteral), context);
} else {
throw exception;
}
} else {
throw exception;
}
}
}
@Override
protected Symbol visitBooleanLiteral(BooleanLiteral node, ExpressionAnalysisContext context) {
return Literal.of(node.getValue());
}
@Override
protected Symbol visitStringLiteral(StringLiteral node, ExpressionAnalysisContext context) {
return Literal.of(node.getValue());
}
@Override
protected Symbol visitDoubleLiteral(DoubleLiteral node, ExpressionAnalysisContext context) {
return Literal.of(node.getValue());
}
@Override
protected Symbol visitLongLiteral(LongLiteral node, ExpressionAnalysisContext context) {
return Literal.of(node.getValue());
}
@Override
protected Symbol visitNullLiteral(NullLiteral node, ExpressionAnalysisContext context) {
return Literal.of(UndefinedType.INSTANCE, null);
}
@Override
public Symbol visitArrayLiteral(ArrayLiteral node, ExpressionAnalysisContext context) {
List<Expression> values = node.values();
if (values.isEmpty()) {
return Literal.of(new ArrayType(UndefinedType.INSTANCE), new Object[0]);
} else {
List<Symbol> arguments = new ArrayList<>(values.size());
for (Expression value : values) {
arguments.add(process(value, context));
}
return context
.allocateFunction(getBuiltinFunctionInfo(ArrayFunction.NAME, Symbols.extractTypes(arguments)), arguments);
}
}
@Override
public Symbol visitObjectLiteral(ObjectLiteral node, ExpressionAnalysisContext context) {
Multimap<String, Expression> values = node.values();
if (values.isEmpty()) {
return Literal.EMPTY_OBJECT;
}
List<Symbol> arguments = new ArrayList<>(values.size() * 2);
for (Map.Entry<String, Expression> entry : values.entries()) {
arguments.add(Literal.of(entry.getKey()));
arguments.add(process(entry.getValue(), context));
}
return context
.allocateFunction(getBuiltinFunctionInfo(MapFunction.NAME, Symbols.extractTypes(arguments)), arguments);
}
@Override
public Symbol visitParameterExpression(ParameterExpression node, ExpressionAnalysisContext context) {
return convertParamFunction.apply(node);
}
@Override
protected Symbol visitBetweenPredicate(BetweenPredicate node, ExpressionAnalysisContext context) {
// <value> between <min> and <max>
// -> <value> >= <min> and <value> <= max
Symbol value = process(node.getValue(), context);
Symbol min = process(node.getMin(), context);
Symbol max = process(node.getMax(), context);
Comparison gte = new Comparison(ComparisonExpression.Type.GREATER_THAN_OR_EQUAL, value, min);
FunctionIdent gteIdent = gte.normalize(context).toFunctionIdent();
Function gteFunc = context.allocateFunction(
getBuiltinFunctionInfo(gteIdent.name(), gteIdent.argumentTypes()), gte.arguments());
Comparison lte = new Comparison(ComparisonExpression.Type.LESS_THAN_OR_EQUAL, value, max);
FunctionIdent lteIdent = lte.normalize(context).toFunctionIdent();
Function lteFunc = context.allocateFunction(
getBuiltinFunctionInfo(lteIdent.name(), lteIdent.argumentTypes()), lte.arguments());
return AndOperator.of(gteFunc, lteFunc);
}
@Override
public Symbol visitMatchPredicate(MatchPredicate node, ExpressionAnalysisContext context) {
Map<Field, Symbol> identBoostMap = new HashMap<>(node.idents().size());
DataType columnType = null;
for (MatchPredicateColumnIdent ident : node.idents()) {
Symbol column = process(ident.columnIdent(), context);
if (columnType == null) {
columnType = column.valueType();
}
Preconditions.checkArgument(
column instanceof Field,
SymbolFormatter.format("can only MATCH on columns, not on %s", column));
Symbol boost = process(ident.boost(), context);
identBoostMap.put(((Field) column), boost);
}
assert columnType != null : "columnType must not be null";
verifyTypesForMatch(identBoostMap.keySet(), columnType);
Symbol queryTerm = castIfNeededOrFail(process(node.value(), context), columnType);
String matchType = io.crate.operation.predicate.MatchPredicate.getMatchType(node.matchType(), columnType);
List<Symbol> mapArgs = new ArrayList<>(node.properties().size() * 2);
for (Map.Entry<String, Expression> e : node.properties().properties().entrySet()) {
mapArgs.add(Literal.of(e.getKey()));
mapArgs.add(process(e.getValue(), context));
}
Function options = context.allocateFunction(MapFunction.createInfo(Symbols.extractTypes(mapArgs)), mapArgs);
return new io.crate.analyze.symbol.MatchPredicate(identBoostMap, queryTerm, matchType, options);
}
@Override
protected Symbol visitSubqueryExpression(SubqueryExpression node, ExpressionAnalysisContext context) {
if (subQueryAnalyzer == null) {
throw new UnsupportedOperationException("Subquery not supported in this statement");
}
/* note: This does not support analysis columns in the subquery which belong to the parent relation
* this would require {@link StatementAnalysisContext#startRelation} to somehow inherit the parent context
*/
AnalyzedRelation relation = subQueryAnalyzer.analyze(node.getQuery());
List<Field> fields = relation.fields();
if (fields.size() > 1) {
throw new UnsupportedOperationException("Subqueries with more than 1 column are not supported.");
}
/*
* The SelectSymbol should actually have a RowType as it is a row-expression.
*
* But there are no other row-expressions yet. In addition the cast functions and operators don't work with
* row types (yet).
*
* Since we only support 1 column and only single-row subselects it is okay to use the inner type directly.
*/
return new SelectSymbol(relation, fields.get(0).valueType());
}
}
private static void verifyTypesForMatch(Iterable<? extends Symbol> columns, DataType columnType) {
Preconditions.checkArgument(
io.crate.operation.predicate.MatchPredicate.SUPPORTED_TYPES.contains(columnType),
String.format(Locale.ENGLISH, "Can only use MATCH on columns of type STRING or GEO_SHAPE, not on '%s'", columnType));
for (Symbol column : columns) {
if (!column.valueType().equals(columnType)) {
throw new IllegalArgumentException(String.format(
Locale.ENGLISH,
"All columns within a match predicate must be of the same type. Found %s and %s",
columnType, column.valueType()));
}
}
}
private static class Comparison {
private static final Set<ComparisonExpression.Type> NEGATING_TYPES = ImmutableSet.of(
ComparisonExpression.Type.REGEX_NO_MATCH,
ComparisonExpression.Type.REGEX_NO_MATCH_CI,
ComparisonExpression.Type.NOT_EQUAL);
private ComparisonExpression.Type comparisonExpressionType;
private Symbol left;
private Symbol right;
private DataType leftType;
private DataType rightType;
private String operatorName;
private FunctionIdent functionIdent = null;
private Comparison(ComparisonExpression.Type comparisonExpressionType,
Symbol left,
Symbol right) {
this.operatorName = "op_" + comparisonExpressionType.getValue();
this.comparisonExpressionType = comparisonExpressionType;
this.left = left;
this.right = right;
this.leftType = left.valueType();
this.rightType = right.valueType();
}
Comparison normalize(ExpressionAnalysisContext context) {
swapIfNecessary();
castTypes();
rewriteNegatingOperators(context);
return this;
}
/**
* swaps the comparison so that references and fields are on the left side.
* e.g.:
* eq(2, name) becomes eq(name, 2)
*/
private void swapIfNecessary() {
if ((!(right instanceof Reference || right instanceof Field)
|| left instanceof Reference || left instanceof Field)
&& leftType.id() != DataTypes.UNDEFINED.id()) {
return;
}
ComparisonExpression.Type type = SWAP_OPERATOR_TABLE.get(comparisonExpressionType);
if (type != null) {
comparisonExpressionType = type;
operatorName = "op_" + type.getValue();
}
Symbol tmp = left;
DataType tmpType = leftType;
left = right;
leftType = rightType;
right = tmp;
rightType = tmpType;
}
private void castTypes() {
right = castIfNeededOrFail(right, leftType);
rightType = leftType;
}
/**
* rewrite exp1 != exp2 to not(eq(exp1, exp2))
* and exp1 !~ exp2 to not(~(exp1, exp2))
* does nothing if operator != not equals
*/
private void rewriteNegatingOperators(ExpressionAnalysisContext context) {
if (!NEGATING_TYPES.contains(comparisonExpressionType)) {
return;
}
String opName = null;
DataType opType = null;
switch (comparisonExpressionType) {
case NOT_EQUAL:
opName = EqOperator.NAME;
opType = EqOperator.RETURN_TYPE;
break;
case REGEX_NO_MATCH:
opName = RegexpMatchOperator.NAME;
opType = RegexpMatchOperator.RETURN_TYPE;
break;
case REGEX_NO_MATCH_CI:
opName = RegexpMatchCaseInsensitiveOperator.NAME;
opType = RegexpMatchCaseInsensitiveOperator.RETURN_TYPE;
break;
}
FunctionIdent ident = new FunctionIdent(opName, Arrays.asList(leftType, rightType));
left = context.allocateFunction(
new FunctionInfo(ident, opType),
Arrays.asList(left, right)
);
right = null;
rightType = null;
functionIdent = NotPredicate.INFO.ident();
leftType = opType;
operatorName = NotPredicate.NAME;
}
FunctionIdent toFunctionIdent() {
if (functionIdent == null) {
return new FunctionIdent(operatorName, Arrays.asList(leftType, rightType));
}
return functionIdent;
}
List<Symbol> arguments() {
if (right == null) {
// this is the case if the comparison has been rewritten to not(eq(exp1, exp2))
return Arrays.asList(left);
}
return Arrays.asList(left, right);
}
}
}