/*
* Licensed to CRATE Technology GmbH ("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;
import io.crate.analyze.relations.FieldResolver;
import io.crate.analyze.symbol.*;
import io.crate.analyze.symbol.format.SymbolFormatter;
import io.crate.metadata.*;
import io.crate.data.Input;
import io.crate.operation.reference.ReferenceResolver;
import io.crate.operation.scalar.arithmetic.MapFunction;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.logging.Loggers;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* the normalizer does symbol normalization and reference resolving if possible
* <p>
* E.g.:
* The query
* </p>
* <p>
* and(true, eq(column_ref, 'someliteral'))
* </p>
* <p>
* will be changed to
* </p>
* <p>
* eq(column_ref, 'someliteral')
* </p>
*/
public class EvaluatingNormalizer {
private static final Logger logger = Loggers.getLogger(EvaluatingNormalizer.class);
private final Functions functions;
private final RowGranularity granularity;
private final ReferenceResolver<? extends Input<?>> referenceResolver;
private final FieldResolver fieldResolver;
private final BaseVisitor visitor;
public static EvaluatingNormalizer functionOnlyNormalizer(Functions functions, ReplaceMode replaceMode) {
return new EvaluatingNormalizer(functions, RowGranularity.CLUSTER, replaceMode, null, null);
}
/**
* @param functions function resolver
* @param granularity the maximum row granularity the normalizer should try to normalize
* @param replaceMode defines if symbols like functions can be mutated or if they have to be copied
* @param referenceResolver reference resolver which is used to resolve paths
* @param fieldResolver optional field resolver to resolve fields
*/
public EvaluatingNormalizer(Functions functions,
RowGranularity granularity,
ReplaceMode replaceMode,
@Nullable ReferenceResolver<? extends Input<?>> referenceResolver,
@Nullable FieldResolver fieldResolver) {
this.functions = functions;
this.granularity = granularity;
this.referenceResolver = referenceResolver;
this.fieldResolver = fieldResolver;
if (replaceMode == ReplaceMode.MUTATE) {
this.visitor = new InPlaceVisitor();
} else {
this.visitor = new CopyingVisitor();
}
}
private static class Context {
@Nullable
private final TransactionContext transactionContext;
public Context(@Nullable TransactionContext transactionContext) {
this.transactionContext = transactionContext;
}
}
private abstract class BaseVisitor extends SymbolVisitor<Context, Symbol> {
@Override
public Symbol visitField(Field field, Context context) {
if (fieldResolver != null) {
Symbol resolved = fieldResolver.resolveField(field);
if (resolved != null) {
return resolved;
}
}
return field;
}
@Override
public Symbol visitMatchPredicate(MatchPredicate matchPredicate, Context context) {
if (fieldResolver != null) {
// Once the fields can be resolved, rewrite matchPredicate to function
Map<Field, Symbol> fieldBoostMap = matchPredicate.identBoostMap();
List<Symbol> columnBoostMapArgs = new ArrayList<>(fieldBoostMap.size() * 2);
for (Map.Entry<Field, Symbol> entry : fieldBoostMap.entrySet()) {
Symbol resolved = process(entry.getKey(), null);
if (resolved instanceof Reference) {
columnBoostMapArgs.add(Literal.of(((Reference) resolved).ident().columnIdent().fqn()));
columnBoostMapArgs.add(entry.getValue());
} else {
return matchPredicate;
}
}
Function function = new Function(
io.crate.operation.predicate.MatchPredicate.INFO,
Arrays.asList(
new Function(MapFunction.createInfo(Symbols.extractTypes(columnBoostMapArgs)), columnBoostMapArgs),
matchPredicate.queryTerm(),
Literal.of(matchPredicate.matchType()),
matchPredicate.options()
));
return process(function, context);
}
return matchPredicate;
}
@SuppressWarnings("unchecked")
Symbol normalizeFunctionSymbol(Function function, Context context) {
FunctionIdent ident = function.info().ident();
FunctionImplementation impl = functions.getQualified(ident);
return impl.normalizeSymbol(function, context.transactionContext);
}
@Override
public Symbol visitReference(Reference symbol, Context context) {
if (referenceResolver == null || symbol.granularity().ordinal() > granularity.ordinal()) {
return symbol;
}
Input input = referenceResolver.getImplementation(symbol);
if (input != null) {
return Literal.of(symbol.valueType(), input.value());
}
if (logger.isTraceEnabled()) {
logger.trace(SymbolFormatter.format("Can't resolve reference %s", symbol));
}
return symbol;
}
@Override
protected Symbol visitSymbol(Symbol symbol, Context context) {
return symbol;
}
}
private class CopyingVisitor extends BaseVisitor {
@Override
public Symbol visitFunction(Function function, Context context) {
List<Symbol> newArgs = normalize(function.arguments(), context);
if (newArgs != function.arguments()) {
function = new Function(function.info(), newArgs);
}
return normalizeFunctionSymbol(function, context);
}
}
private class InPlaceVisitor extends BaseVisitor {
@Override
public Symbol visitFunction(Function function, Context context) {
normalizeInplace(function.arguments(), context);
return normalizeFunctionSymbol(function, context);
}
}
/**
* Normalizes all symbols of a List. Does not return a new list if no changes occur.
*
* @param symbols the list to be normalized
* @return a list with normalized symbols
*/
public List<Symbol> normalize(List<Symbol> symbols, TransactionContext transactionContext) {
Context context = new Context(transactionContext);
return normalize(symbols, context);
}
private List<Symbol> normalize(List<Symbol> symbols, Context context) {
if (symbols.size() > 0) {
boolean changed = false;
Symbol[] newArgs = new Symbol[symbols.size()];
int i = 0;
for (Symbol symbol : symbols) {
Symbol newArg = normalize(symbol, context);
changed = changed || newArg != symbol;
newArgs[i++] = newArg;
}
if (changed) {
return Arrays.asList(newArgs);
}
}
return symbols;
}
/**
* Normalizes all symbols of a List in place
*
* @param symbols the list to be normalized
*/
public void normalizeInplace(@Nullable List<Symbol> symbols, @Nullable TransactionContext transactionContext) {
Context context = new Context(transactionContext);
normalizeInplace(symbols, context);
}
private void normalizeInplace(@Nullable List<Symbol> symbols, Context context) {
if (symbols != null) {
for (int i = 0; i < symbols.size(); i++) {
symbols.set(i, normalize(symbols.get(i), context));
}
}
}
public Symbol normalize(@Nullable Symbol symbol, @Nullable TransactionContext transactionContext) {
return normalize(symbol, new Context(transactionContext));
}
private Symbol normalize(@Nullable Symbol symbol, Context context) {
if (symbol == null) {
return null;
}
return visitor.process(symbol, context);
}
}