/*
* 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.planner.projection.builder;
import com.google.common.base.MoreObjects;
import io.crate.analyze.symbol.*;
import io.crate.metadata.FunctionInfo;
import io.crate.types.DataType;
import org.elasticsearch.common.inject.Singleton;
import java.util.*;
@Singleton
public final class InputColumns extends DefaultTraversalSymbolVisitor<InputColumns.Context, Symbol> {
private static final InputColumns INSTANCE = new InputColumns();
public static class Context {
final HashMap<Symbol, InputColumn> inputs;
final IdentityHashMap<Symbol, InputColumn> nonDeterministicFunctions;
public Context(Collection<? extends Symbol> inputs) {
this.inputs = new HashMap<>(inputs.size());
// non deterministic functions would override each other in a normal hashmap
// as they compare equal but shouldn't be treated that way here.
// we want them to have their own Input each
this.nonDeterministicFunctions = new IdentityHashMap<>(inputs.size());
int i = 0;
for (Symbol input : inputs) {
// only non-literals should be replaced with input columns.
// otherwise {@link io.crate.metadata.Scalar#compile} won't do anything which
// results in poor performance of some scalar implementations
if (!input.symbolType().isValueSymbol()) {
DataType valueType = input.valueType();
if (input.symbolType() == SymbolType.FUNCTION
&& !((Function) input).info().features().contains(FunctionInfo.Feature.DETERMINISTIC)) {
nonDeterministicFunctions.put(input, new InputColumn(i, valueType));
} else {
this.inputs.put(input, new InputColumn(i, valueType));
}
}
i++;
}
}
}
/**
* Return a symbol where each element from {@code symbolTree} that occurs in {@code inputSymbols}
* is replaced with a {@link InputColumn} pointing to the position in {@code inputSymbols}
*
* <p>
* The returned instance may be the same if no elements of {@code symbolTree} where part of {@code inputSymbols}
* </p>
*/
public static Symbol create(Symbol symbolTree, Collection<? extends Symbol> inputSymbols) {
return create(symbolTree, new Context(inputSymbols));
}
/**
* Same as {@link #create(Symbol, Collection)} but allows to re-use a context.
*/
public static Symbol create(Symbol symbolTree, Context context) {
return INSTANCE.process(symbolTree, context);
}
/**
* Same as {@link #create(Symbol, Collection)}, but works for multiple symbols and allows re-using
* a context class.
* <p>
* If {@code symbols} and the inputSymbols of the Context class are the same,
* it's better to use {@link InputColumn#fromSymbols(Collection)} to create a 1:1 InputColumn mapping.
* </p>
*/
public static List<Symbol> create(Collection<? extends Symbol> symbols, Context context) {
List<Symbol> result = new ArrayList<>(symbols.size());
for (Symbol symbol : symbols) {
result.add(INSTANCE.process(symbol, context));
}
return result;
}
@Override
public Symbol visitFunction(Function symbol, final Context context) {
Symbol replacement;
if (symbol.info().features().contains(FunctionInfo.Feature.DETERMINISTIC)) {
replacement = context.inputs.get(symbol);
} else {
replacement = context.nonDeterministicFunctions.get(symbol);
}
if (replacement != null) {
return replacement;
}
ArrayList<Symbol> args = new ArrayList<>(symbol.arguments().size());
for (Symbol arg : symbol.arguments()) {
args.add(process(arg, context));
}
return new Function(symbol.info(), args);
}
@Override
protected Symbol visitSymbol(Symbol symbol, Context context) {
return MoreObjects.firstNonNull(context.inputs.get(symbol), symbol);
}
@Override
public Symbol visitFetchReference(FetchReference fetchReference, Context context) {
return fetchReference;
}
@Override
public Symbol visitAggregation(Aggregation symbol, Context context) {
throw new AssertionError("Aggregation Symbols must not be visited with " +
getClass().getCanonicalName());
}
}