/**
* 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.name;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import org.diqube.data.column.ColumnType;
import org.diqube.function.FunctionFactory;
import com.google.common.collect.Iterables;
/**
* Calculates the name of the result column when executing a function on some input data.
*
* <p>
* Each function (aggregation or projection) that is executed on some set of data (constants or columns) creates a
* column which will hold the result values of applying that function to the input data. This class can calculate the
* name of this output column in a way that it is unique for the executed operation within the execution of one query.
*
* @author Bastian Gloeckle
*/
public class FunctionBasedColumnNameBuilder {
private List<String> parameterNames = new ArrayList<>();
private String functionName;
private RepeatedColumnNameGenerator repeatedCols;
private FunctionFactory functionFactory;
private ConcurrentMap<String, List<List<Integer>>> exchangeableIndicesCache;
/* package */ FunctionBasedColumnNameBuilder(RepeatedColumnNameGenerator repeatedCols,
FunctionFactory functionFactory, ConcurrentMap<String, List<List<Integer>>> exchangeableIndicesCache) {
this.repeatedCols = repeatedCols;
this.functionFactory = functionFactory;
this.exchangeableIndicesCache = exchangeableIndicesCache;
}
/**
* Transforms all [*] occurrences in the string with [a], therefore the resulting column name represents the finally
* built column and not the pattern that is used in a query.
*/
public FunctionBasedColumnNameBuilder addParameterColumnName(String parameterColumnName) {
if (parameterColumnName.contains(repeatedCols.allEntriesIdentifyingSubstr()))
parameterNames.add("col%" + parameterColumnName.replace(repeatedCols.allEntriesIdentifyingSubstr(),
repeatedCols.allEntriesManifestedSubstr()));
else
parameterNames.add("col%" + parameterColumnName);
return this;
}
public FunctionBasedColumnNameBuilder addParameterLiteralString(String parameterLiteralString) {
parameterNames.add("lits%" + parameterLiteralString);
return this;
}
public FunctionBasedColumnNameBuilder addParameterLiteralLong(long parameterLiteralLong) {
parameterNames.add("litl%" + parameterLiteralLong);
return this;
}
public FunctionBasedColumnNameBuilder addParameterLiteralDouble(double parameterLiteralDouble) {
parameterNames.add("litd%" + parameterLiteralDouble);
return this;
}
public FunctionBasedColumnNameBuilder withFunctionName(String functionName) {
this.functionName = functionName;
return this;
}
public String build() {
// Try to exchange parameters in a deterministic way. This is only done for projection functions.
// We do this in order to not calculate the same result twice.
//
// For example: add(1, a) and add(a, 1) are two distinct columns which though have the exactly same values -> It
// would be nice to calculate that result only once. We can achieve this by giving those two function requests the
// same output column (see FunctionColumnInfoBuilder).
//
// In addition to calculating the same results only once, we facilitate caching of those columns - even if later
// queries might specify the params of the function in a different order.
if (parameterNames.size() > 1) {
if (!exchangeableIndicesCache.containsKey(functionName)) {
Collection<ColumnType> inputColTypes =
functionFactory.getPossibleInputDataTypesForProjectionFunction(functionName);
if (inputColTypes != null) {
List<List<Set<Integer>>> exchangeableParamIdxDistinctList =
inputColTypes.stream().map(inColType -> functionFactory.createProjectionFunction(functionName, inColType)
.exchangeableParameterIndices()).distinct().collect(Collectors.toList());
if (exchangeableParamIdxDistinctList.size() == 1) {
// all functions with the same functionName have the same exchangeable parameter indices. So we can exchange
// them! Put them in the cache and process below.
List<List<Integer>> exchangeableSortedIndices = Iterables.getOnlyElement(exchangeableParamIdxDistinctList)
.stream().map(set -> set.stream().sorted().collect(Collectors.toList())).collect(Collectors.toList());
exchangeableIndicesCache.put(functionName, exchangeableSortedIndices);
} else
exchangeableIndicesCache.put(functionName, new ArrayList<>()); // no exchangeable indices.
} else
exchangeableIndicesCache.put(functionName, new ArrayList<>()); // no exchangeable indices.
}
if (exchangeableIndicesCache.containsKey(functionName)) {
List<List<Integer>> exchangeIdxList = exchangeableIndicesCache.get(functionName);
for (List<Integer> exchangeIdxSortedList : exchangeIdxList) {
List<String> paramValues = new ArrayList<>(exchangeIdxSortedList.size());
exchangeIdxSortedList.forEach(idx -> paramValues.add(parameterNames.get(idx)));
paramValues.sort(Comparator.naturalOrder());
Iterator<Integer> idxIt = exchangeIdxSortedList.iterator();
for (int i = 0; i < paramValues.size(); i++)
parameterNames.set(idxIt.next(), paramValues.get(i));
}
}
}
StringBuilder sb = new StringBuilder();
sb.append(functionName);
sb.append("{");
for (String paramName : parameterNames) {
sb.append(paramName);
sb.append(",");
}
sb.append("}");
return sb.toString();
}
}