/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program 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 com.foundationdb.server.types.service; import com.foundationdb.server.error.ArgumentTypeRequiredException; import com.foundationdb.server.error.NoCommonTypeException; import com.foundationdb.server.error.NoSuchCastException; import com.foundationdb.server.error.NoSuchFunctionOverloadException; import com.foundationdb.server.error.NoSuchFunctionException; import com.foundationdb.server.error.WrongExpressionArityException; import com.foundationdb.server.types.InputSetFlags; import com.foundationdb.server.types.TCast; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TInputSet; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TInstanceAdjuster; import com.foundationdb.server.types.TInstanceBuilder; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.common.types.StringFactory; import com.foundationdb.server.types.common.types.TString; import com.foundationdb.server.types.texpressions.TValidatedOverload; import com.foundationdb.server.types.mcompat.mtypes.MString; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; public final class OverloadResolver<V extends TValidatedOverload> { public static class OverloadResult<V extends TValidatedOverload> { private V overload; /** Could be have null values, or be null itself */ private TInstance[] instances; /** * null if there is no pickingSet in the overload, or if one of `instances` is null. */ private TInstance pickedInstance; private class AdjusterImpl implements TInstanceAdjuster { private TInstanceBuilder[] builders; void setInputSet(TInputSet inputSet) { this.inputSet = inputSet; } void copyBack() { if (builders != null) { for (int i = 0; i < builders.length; ++i) { TInstanceBuilder builder = builders[i]; if (builder != null) instances[i] = builder.get(); } } } @Override public TInstance get(int i) { check(i); if (builders != null && builders[i] != null) instances[i] = builders[i].get(); // update the TInstance if needed return instances[i]; } @Override public TInstanceBuilder adjust(int i) { check(i); if (builders == null) builders = new TInstanceBuilder[nInputs]; TInstanceBuilder result = builders[i]; if (result == null) { result = new TInstanceBuilder(instances[i]); builders[i] = result; instances[i] = null; } return result; } @Override public void replace(int i, TInstance type) { if (builders != null && builders[i] != null) builders[i].copyFrom(type); else instances[i] = type; } void check(int i) { assert overload.inputSetAt(i) == inputSet : "input set at " + i + " is " + overload.inputSetAt(i) + ", expected " + inputSet; } private AdjusterImpl(TValidatedOverload overload, int nInputs) { this.overload = overload; this.nInputs = nInputs; } private final int nInputs; private final TValidatedOverload overload; private TInputSet inputSet; } private OverloadResult(V overload, List<? extends TPreptimeValue> inputs, TCastResolver resolver) { this.overload = overload; List<TInputSet> inputSets = overload.inputSets(); int nInputs = inputs.size(); AdjusterImpl adjuster = null; for (TInputSet inputSet : inputSets) { TClass targetTClass = inputSet.targetType(); if (targetTClass == null) { TInstance type = findCommon(overload, inputSet, inputs, resolver); fillInstances(overload, inputSet, type, nInputs); } else { if (adjuster == null) adjuster = new AdjusterImpl(overload, nInputs); adjuster.setInputSet(inputSet); findInstance(overload, inputSet, inputs, resolver); inputSet.instanceAdjuster().apply(adjuster, overload, inputSet, nInputs); adjuster.copyBack(); } } pickedInstance = findPickedInstance(overload, inputs); } private TInstance findPickedInstance(V overload, List<? extends TPreptimeValue> inputs) { TInputSet pickingSet = overload.pickingInputSet(); TInstance result = null; if (pickingSet != null) { for (int i = overload.firstInput(pickingSet), max = inputs.size(); i >= 0; i = overload.nextInput(pickingSet, i+1, max)) { TInstance inputInstance = instances[i]; // if we need to pickInstance, we'll do it on the previous result's TClass. That covers the case // of a picking ANY, in which case findCommon would have found a TClass. result = (result == null) ? inputInstance : result.typeClass().pickInstance(result, inputInstance); } } return result; } private void fillInstances(V func, TInputSet inputSet, TInstance type, int inputsLen) { if (instances == null) instances = new TInstance[inputsLen]; else assert inputsLen == instances.length : inputsLen + "not size of " + Arrays.toString(instances); for (int i = func.firstInput(inputSet); i >= 0; i = func.nextInput(inputSet, i+1, inputsLen)) { instances[i] = type; } } private void findInstance(V overload, TInputSet inputSet, List<? extends TPreptimeValue> inputs, TCastResolver resolver) { final TClass targetTClass = inputSet.targetType(); assert targetTClass != null; int lastPositionalInput = overload.positionalInputs(); boolean notVararg = ! overload.isVararg(); for (int i = 0, size = inputs.size(); i < size; ++i) { if (overload.inputSetAt(i) != inputSet) continue; if (notVararg && (i >= lastPositionalInput)) break; TPreptimeValue inputTpv = inputs.get(i); TInstance inputInstance = inputTpv.type(); TInstance resultInstance; if (inputInstance != null) { TClass inputTClass = inputInstance.typeClass(); if (inputTClass == targetTClass) { resultInstance = inputInstance; } else { TCast requiredCast = resolver.cast(inputTClass, targetTClass); if (requiredCast == null) throw new NoSuchCastException(inputInstance.typeClass(), targetTClass); inputInstance = requiredCast.preferredTarget(inputTpv); resultInstance = inputInstance; } } // no inputInstance = no type attributes else { assert inputTpv.isNullable() : inputTpv; // TODO: Generalize to e.g. instance(nullable) -> unknownInstance(nullable) ? if(targetTClass instanceof TString) { resultInstance = targetTClass.instance(Integer.MAX_VALUE, // no length would be preferable StringFactory.DEFAULT_CHARSET_ID, StringFactory.NULL_COLLATION_ID, true); } else { resultInstance = targetTClass.instance(true); } } if (instances == null) instances = new TInstance[size]; instances[i] = resultInstance; } } /** * Never returns null. * Note: If !inputSet.isPicking() and no common tclass can be found, then VARCHAR(0) will be returned. */ private TInstance findCommon(V overload, TInputSet inputSet, List<? extends TPreptimeValue> inputs, TCastResolver resolver) { assert inputSet.targetType() == null : inputSet; // so we have to look at inputs TClass common = null; TInstance commonInst = null; int lastPositionalInput = overload.positionalInputs(); boolean notVararg = ! overload.isVararg(); boolean nullable = false; for (int i = 0, size = inputs.size(); i < size; ++i) { if (overload.inputSetAt(i) != inputSet) continue; if (notVararg && (i >= lastPositionalInput)) break; TPreptimeValue inputTpv = inputs.get(i); nullable |= inputTpv.isNullable(); TInstance inputInstance = inputTpv.type(); if (inputInstance == null) { // unknown type, like a NULL literal or parameter continue; } TClass inputClass = inputInstance.typeClass(); if (common == null) { // First input we've seen, so just use it. common = inputClass; commonInst = inputInstance; } else if (inputClass == common) { // saw the same TClass as before, so pick it commonInst = (commonInst == null) ? inputInstance : common.pickInstance(commonInst, inputInstance); } else { // Saw a different TCLass as before, so need to cast one of them. We'll get the new common type, // at which point we have exactly one of three possibilities: // 1) newCommon == [old] common, in which case we'll keep the old TInstance // 2) newCommon == inputClass, in which case we'll use the inputInstance // 3) newCommon is neither, in which case we'll generate a new TInstance // We know that we can't have both #1 and #2, because that would imply [old] common == inputClass, // which has already been handled. TClass newCommon = resolver.commonTClass(common, inputClass); if (newCommon == null) throw new NoCommonTypeException(overload.displayName(), typeNameList(inputs)); if (newCommon == inputClass) { // case #2 common = newCommon; commonInst = inputInstance; } else if (newCommon != common) { // case #3 common = newCommon; commonInst = null; } // else if (newCommon = common), we don't need this because there's nothing to do in this case } } if (common == null) { if (!inputSet.isPicking()) return MString.VARCHAR.instance(0, nullable); // Unknown type and callee doesn't care. else throw new ArgumentTypeRequiredException(overload.displayName(), inputSet); } return (commonInst == null) ? common.instance(nullable) : commonInst; } public V getOverload() { return overload; } public TInstance getPickedInstance() { if (pickedInstance == null) throw new IllegalStateException("no picked instance"); return pickedInstance; } public TInstance getTypeClass(int inputIndex) { return instances[inputIndex]; } } private final ResolvablesRegistry<V> overloadsRegistry; private final TCastResolver castsResolver; public OverloadResolver(ResolvablesRegistry<V> overloadsRegistry, TCastResolver castsResolver) { this.overloadsRegistry = overloadsRegistry; this.castsResolver = castsResolver; } public boolean isDefined(String name) { return overloadsRegistry.containsKey(name); } public OverloadResult<V> get(String name, List<? extends TPreptimeValue> inputs) { Iterable<? extends ScalarsGroup<V>> scalarsGroup = overloadsRegistry.get(name); if (scalarsGroup == null) { throw new NoSuchFunctionException(name); } return inputBasedResolution(name, inputs, scalarsGroup); } private OverloadResult<V> inputBasedResolution( String name, List<? extends TPreptimeValue> inputs, Iterable<? extends ScalarsGroup<V>> scalarGroupsByPriority) { V mostSpecific = null; boolean sawRightArity = false; int aritySeen = -1; Iterator<? extends ScalarsGroup<V>> iter = scalarGroupsByPriority.iterator(); ScalarsGroup<V> scalarsGroup; while (iter.hasNext()) { scalarsGroup = iter.next(); Collection<? extends V> namedOverloads = scalarsGroup.getOverloads(); List<V> candidates = new ArrayList<>(namedOverloads.size()); for (V overload : namedOverloads) { if (!overload.coversNInputs(inputs.size())) { aritySeen = overload.positionalInputs(); continue; } sawRightArity = true; if (isCandidate(overload, inputs, scalarsGroup, iter.hasNext())) { candidates.add(overload); } } if (candidates.isEmpty()) continue; // try next priority group of namedOverloads if (candidates.size() == 1) { mostSpecific = candidates.get(0); break; // found one! } else { List<List<V>> groups = reduceToMinimalCastGroups(candidates); if (groups.size() == 1 && groups.get(0).size() == 1) { mostSpecific = groups.get(0).get(0); break; // found one! } else { // Too many candidates in priority group. // An error that is normally caught by function registry. throw new IllegalStateException(name + " has too many candidates for " + typeNameList(inputs)); } } } if (mostSpecific == null) { // no priority group had any candidates; this is an error if (sawRightArity) throw new NoSuchFunctionOverloadException(name, typeNameList(inputs)); throw new WrongExpressionArityException(aritySeen, inputs.size()); } return buildResult(mostSpecific, inputs); } ResolvablesRegistry<V> getRegistry() { return overloadsRegistry; } private static String typeNameList(List<? extends TPreptimeValue> inputs) { StringBuilder sb = new StringBuilder(); for(TPreptimeValue tpv : inputs) { if(sb.length() > 0) { sb.append(","); } if(tpv == null || tpv.type() == null) { sb.append("?"); } else { sb.append(tpv.type().typeClass().name().unqualifiedName()); } } return sb.toString(); } private boolean isCandidate(V overload, List<? extends TPreptimeValue> inputs, ScalarsGroup<V> scalarGroups, boolean hasNext) { if (!overload.coversNInputs(inputs.size())) return false; InputSetFlags exactInputs = overload.exactInputs(); TClass[] pickSameType = null; for (int i = 0, inputsSize = inputs.size(); i < inputsSize; i++) { // allow this input if // all overloads of this name have the same at this position boolean requireExact = exactInputs.get(i); if ( (!requireExact) && scalarGroups.hasSameTypeAt(i)) { continue; } TPreptimeValue inputTpv = inputs.get(i); TInstance inputInstance = (inputTpv == null) ? null : inputTpv.type(); // allow this input if... // ... input set takes ANY, and it isn't marked as an exact. If it's marked as an exact, we'll figure it // out later TInputSet inputSet = overload.inputSetAt(i); if ((!requireExact) && inputSet.targetType() == null) { continue; } // ... input can be strongly cast to input set TClass inputTypeClass; if (requireExact) { inputTypeClass = (inputInstance == null) ? null : inputInstance.typeClass(); } else if (inputInstance == null) { // If input type is unknown (NULL literal or parameter), assume common type at this position among // all overloads in this group. inputTypeClass = scalarGroups.commonTypeAt(i); if (inputTypeClass == null) { // We couldn't resolve it in this group if (hasNext) // , but we might find a match in the subsequent ones return false; else throw new ArgumentTypeRequiredException(overload.displayName(), i); } } else { inputTypeClass = inputInstance.typeClass(); } if (requireExact) { if (inputSet.targetType() == null) { // We're in an ANY-exact input set. The semantics are: // - unknown types are always allowed // - the first known type defines the type of the input set // - subsequent known types must equal this known type if (inputTypeClass == null) { continue; } if (pickSameType == null) pickSameType = new TClass[overload.inputSetIndexCount()]; int inputSetIndex = overload.inputSetIndexAtPos(i); TClass definedType = pickSameType[inputSetIndex]; if (definedType == null) { pickSameType[inputSetIndex] = inputTypeClass; continue; } else if (definedType == inputTypeClass) { continue; } } else if (inputTypeClass == null && scalarGroups.hasSameTypeAt(i)) { continue; } else if (inputTypeClass == inputSet.targetType()) { continue; } } else { if (castsResolver.strongCastExists(inputTypeClass, inputSet.targetType())) continue; } // This input precludes the use of the overload return false; } // no inputs have precluded this overload return true; } private OverloadResult<V> buildResult(V overload, List<? extends TPreptimeValue> inputs) { return new OverloadResult<>(overload, inputs, castsResolver); } /* * Two overloads have SIMILAR INPUT SETS if they * 1) have the same number of input sets * 2) each input set from one overload covers the same columns as an input set from the other function * * For any two overloads A and B, if A and B have SIMILAR INPUT SETS, and the target type of each input * set Ai can be strongly cast to the target type of Bi, then A is said to be MORE SPECIFIC than A, and B * is discarded as a possible overload. */ private List<List<V>> reduceToMinimalCastGroups(List<V> candidates) { // NOTE: // This method shares some concepts with #commonTClass. See that method for a note about possible refactoring // opportunities (tl;dr is the two methods don't share code right now, but they might be able to.) List<List<V>> castGroups = new ArrayList<>(); for(V B : candidates) { final int nInputSets = B.inputSets().size(); // Find the OVERLOAD CAST GROUP List<V> castGroup = null; for(List<V> group : castGroups) { // Groups are not empty, can always get first V cur = group.get(0); if(cur.inputSets().size() == nInputSets) { boolean matches = true; for(int i = 0; i < nInputSets && matches; ++i) { matches = (cur.inputSetAt(i).positionsLength() == B.inputSetAt(i).positionsLength()); } if(matches) { castGroup = group; break; } } } if(castGroup != null) { // Found group, check for more specific Iterator<V> it = castGroup.iterator(); while(it.hasNext()) { V A = it.next(); boolean AtoB = true; boolean BtoA = true; for(int i = 0; i < nInputSets; ++i) { TInputSet Ai = A.inputSetAt(i); TInputSet Bi = B.inputSetAt(i); AtoB &= castsResolver.strongCastExists(Ai.targetType(), Bi.targetType()); BtoA &= castsResolver.strongCastExists(Bi.targetType(), Ai.targetType()); } if(AtoB) { // current more specific // set B to null so that we know not to add it to castGroup B = null; break; } else if(BtoA) { // new more specific it.remove(); } } if(B != null) { // No more specific existed or B was most specific castGroup.add(B); } } else { // No matching group, must be in a new group castGroup = new ArrayList<>(1); castGroup.add(B); castGroups.add(castGroup); } } return castGroups; } }