/**
* 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.NoSuchCastException;
import com.foundationdb.server.error.NoSuchFunctionOverloadException;
import com.foundationdb.server.error.NoSuchFunctionException;
import com.foundationdb.server.error.WrongExpressionArityException;
import com.foundationdb.server.types.LazyList;
import com.foundationdb.server.types.TBundleID;
import com.foundationdb.server.types.TCast;
import com.foundationdb.server.types.TCastBase;
import com.foundationdb.server.types.TCastIdentifier;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TExecutionContext;
import com.foundationdb.server.types.TScalar;
import com.foundationdb.server.types.TOverloadResult;
import com.foundationdb.server.types.TPreptimeValue;
import com.foundationdb.server.types.TStrongCasts;
import com.foundationdb.server.types.common.types.NoAttrTClass;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.server.types.texpressions.Constantness;
import com.foundationdb.server.types.texpressions.TInputSetBuilder;
import com.foundationdb.server.types.texpressions.TScalarBase;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
public class OverloadResolverTest {
private static class TestClassBase extends NoAttrTClass {
private static final TBundleID TEST_BUNDLE_ID = new TBundleID("test", new UUID(0,0));
public TestClassBase(String name, UnderlyingType underlyingType) {
super(TEST_BUNDLE_ID, name, null, null, 1, 1, 1, underlyingType, null, 64, null);
}
@Override
public TCast castToVarchar() {
return null;
}
@Override
public TCast castFromVarchar() {
return null;
}
}
private static class TestCastBase extends TCastBase {
public TestCastBase(TClass source, TClass target, boolean isStrong) {
super(source, target, Constantness.UNKNOWN);
this.isStrong = isStrong;
}
@Override
public void doEvaluate(TExecutionContext context, ValueSource source, ValueTarget target) {
throw new UnsupportedOperationException();
}
TStrongCasts strongCasts() {
return isStrong
? TStrongCasts.from(sourceClass()).to(targetClass())
: null;
}
private boolean isStrong;
}
private static final String MUL_NAME = "*";
private static class TestMulBase extends TScalarBase {
private final TClass tLeft;
private final TClass tRight;
private final TClass tTarget;
public TestMulBase(TClass tClass) {
this(tClass, tClass, tClass);
}
public TestMulBase(TClass tLeft, TClass tRight, TClass tTarget) {
this.tLeft = tLeft;
this.tRight = tRight;
this.tTarget = tTarget;
}
@Override
protected void buildInputSets(TInputSetBuilder builder) {
if (tLeft == tRight) {
builder.covers(tLeft, 0, 1);
} else {
builder.covers(tLeft, 0);
builder.covers(tRight, 1);
}
}
@Override
protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) {
throw new UnsupportedOperationException();
}
@Override
public String displayName() {
return MUL_NAME;
}
@Override
public TOverloadResult resultType() {
return TOverloadResult.fixed(tTarget);
}
}
private static class TestGetBase extends TScalarBase {
private final String name;
private final TClass tResult;
private final TInputSetBuilder builder = new TInputSetBuilder();
public TestGetBase(String name, TClass tResult) {
this.name = name;
this.tResult = tResult;
}
public TInputSetBuilder builder() {
return builder;
}
@Override
protected void buildInputSets(TInputSetBuilder builder) {
builder.reset(this.builder);
}
@Override
protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) {
throw new UnsupportedOperationException();
}
@Override
public String displayName() {
return name;
}
@Override
public TOverloadResult resultType() {
return (tResult == null) ? TOverloadResult.picking() : TOverloadResult.fixed(tResult);
}
}
private final static TClass TINT = new TestClassBase("int", UnderlyingType.INT_32);
private final static TClass TBIGINT = new TestClassBase("bigint", UnderlyingType.INT_64);
private final static TClass TDATE = new TestClassBase("date", UnderlyingType.DOUBLE);
private final static TClass TVARCHAR = new TestClassBase("varchar", UnderlyingType.BYTES);
private final static TestCastBase C_INT_BIGINT = new TestCastBase(TINT, TBIGINT, true);
private final static TestCastBase C_BIGINT_INT = new TestCastBase(TBIGINT, TINT, false);
private final static TestMulBase MUL_INTS = new TestMulBase(TINT);
private final static TestMulBase MUL_BIGINTS = new TestMulBase(TBIGINT);
private final static TestMulBase MUL_DATE_INT = new TestMulBase(TDATE, TINT, TDATE);
private TypesRegistryService registry;
private class Initializer {
public Initializer overloads(TScalar... scalars) {
finder.put(TScalar.class, scalars);
return this;
}
public Initializer types(TClass... classes) {
finder.put(TClass.class, classes);
return this;
}
public Initializer casts(TCast... casts) {
for (TCast cast : casts) {
if (!castIdentifiers.add(new TCastIdentifier(cast)))
continue;
if (cast instanceof TestCastBase) {
TStrongCasts strongCasts = ((TestCastBase) cast).strongCasts();
if (strongCasts != null)
finder.put(TStrongCasts.class, strongCasts);
}
finder.put(TCast.class, cast);
types(cast.sourceClass());
types(cast.targetClass());
}
return this;
}
private void init() {
TypesRegistryServiceImpl registryImpl = new TypesRegistryServiceImpl();
registryImpl.start(finder);
registry = registryImpl;
}
private InstanceFinderBuilder finder = new InstanceFinderBuilder();
private Set<TCastIdentifier> castIdentifiers = new HashSet<>();
}
private static TPreptimeValue prepVal(TClass tClass) {
return (tClass != null) ? new TPreptimeValue(tClass.instance(true)) : new TPreptimeValue();
}
private static List<TPreptimeValue> prepVals(TClass... tClasses) {
TPreptimeValue[] prepVals = new TPreptimeValue[tClasses.length];
for(int i = 0; i < tClasses.length; ++i) {
prepVals[i] = prepVal(tClasses[i]);
}
return Arrays.asList(prepVals);
}
private void checkResolved(String msg, TScalar expected, String overloadName, List<TPreptimeValue> prepValues) {
// result.getPickingClass() not checked, SimpleRegistry doesn't implement commonTypes()
OverloadResolver.OverloadResult result = registry.getScalarsResolver().get(overloadName, prepValues);
assertSame(msg, expected, result != null ? result.getOverload().getUnderlying() : null);
}
private void checkCommon(TClass a, TClass b, TClass common) {
TClass actualCommon;
try {
actualCommon = registry.getCastsResolver().commonTClass(a, b);
} catch (NoSuchCastException e) {
actualCommon = null;
}
assertSame("common(" + a + ", " + b +")", common, actualCommon);
}
@Test
public void findCommon_Int_Bigint() {
new Initializer().casts(C_INT_BIGINT).init();
checkCommon(TINT, TBIGINT, TBIGINT);
}
@Test
public void findCommon_BigInt_Bigint() {
new Initializer().types(TINT, TBIGINT).init();
checkCommon(TBIGINT, TBIGINT, TBIGINT);
}
@Test
public void findCommon_Int_Date() {
new Initializer().types(TINT, TBIGINT, TDATE).init();
checkCommon(TINT, TDATE, null);
}
@Test(expected=NoSuchFunctionException.class)
public void noSuchOverload() {
new Initializer().init();
registry.getScalarsResolver().get("foo", Arrays.asList(prepVal(TINT)));
}
@Test(expected=WrongExpressionArityException.class)
public void knownOverloadTooFewParams() {
new Initializer().overloads(MUL_INTS).init();
registry.getScalarsResolver().get(MUL_NAME, prepVals(TINT));
}
@Test(expected=WrongExpressionArityException.class)
public void knownOverloadTooManyParams() {
new Initializer().overloads(MUL_INTS).init();
registry.getScalarsResolver().get(MUL_NAME, prepVals(TINT, TINT, TINT));
}
// default resolution, exact match
@Test
public void mulIntWithInts() {
new Initializer().overloads(MUL_INTS).init();
checkResolved("INT*INT", MUL_INTS, MUL_NAME, prepVals(TINT, TINT));
}
// default resolution, types don't match (no registered casts, int -> bigint)
@Test
public void mulBigIntWithInts() {
new Initializer().overloads(MUL_BIGINTS).casts(C_INT_BIGINT).init();
checkResolved("INT*INT", MUL_BIGINTS, MUL_NAME, prepVals(TINT, TINT));
}
// default resolution, requires weak cast (bigint -> int)
@Test
public void mulIntWithBigInts() {
new Initializer().overloads(MUL_INTS).init();
checkResolved("INT*INT", MUL_INTS, MUL_NAME, prepVals(TINT, TINT));
}
// input resolution, no casts
@Test(expected = NoSuchFunctionOverloadException.class)
public void mulIntMulBigIntWithIntsNoCasts() {
new Initializer().types(TINT, TBIGINT, TDATE).overloads(MUL_INTS, MUL_BIGINTS).init();
checkResolved("INT*INT", null, MUL_NAME, prepVals(TDATE, TDATE));
}
// input resolution, type only casts, only one candidate
@Test
public void mulIntMulBigIntWithIntsTypeOnlyCasts() {
new Initializer()
.types(TINT, TBIGINT)
.overloads(MUL_INTS, MUL_BIGINTS).init();
checkResolved("INT*INT", MUL_INTS, MUL_NAME, prepVals(TINT, TINT));
checkResolved("BIGINT*BIGINT", MUL_BIGINTS, MUL_NAME, prepVals(TBIGINT, TBIGINT));
}
// input resolution, more casts, 2 candidates but 1 more specific
@Test
public void mulIntMulBigIntWithIntsAndIntBigintStrongAndWeakCasts() {
new Initializer()
.overloads(MUL_INTS, MUL_BIGINTS)
.casts(C_INT_BIGINT, C_BIGINT_INT).init();
// 2 candidates, 1 more specific
checkResolved("INT*INT", MUL_INTS, MUL_NAME, prepVals(TINT, TINT));
}
@Test
public void specMulExampleIntBigIntAndDateCombos() {
new Initializer()
.overloads(MUL_INTS, MUL_BIGINTS, MUL_DATE_INT)
.casts(C_INT_BIGINT, C_BIGINT_INT)
.types(TDATE).init();
// 2 survive filtering, 1 more specific
checkResolved("INT*INT", MUL_INTS, MUL_NAME, prepVals(TINT, TINT));
// 1 survives filtering (bigint can't be strongly cast to INT or DATE)
checkResolved("BIGINT*BIGINT", MUL_BIGINTS, MUL_NAME, prepVals(TBIGINT, TBIGINT));
// 1 survives filtering (INT strongly cast to BIGINT)
checkResolved("BIGINT*INT", MUL_BIGINTS, MUL_NAME, prepVals(TBIGINT, TINT));
// 1 survives filtering
checkResolved("DATE*INT", MUL_DATE_INT, MUL_NAME, prepVals(TDATE, TINT));
try {
// 3 survive filtering, 1 less specific, 2 candidates
checkResolved("?*INT", null, MUL_NAME, prepVals(null, TINT));
fail("expected OverloadException");
} catch (ArgumentTypeRequiredException e) {
// expected
}
}
@Test(expected = IllegalStateException.class)
public void conflictingOverloads() {
final String NAME = "foo";
// Overloads aren't valid and should(?) be rejected by real registry,
// but make sure resolver doesn't choke
TestGetBase posPos = new TestGetBase(NAME, TINT);
posPos.builder().covers(TINT, 0, 1);
TestGetBase posRem = new TestGetBase(NAME, TINT);
posRem.builder().covers(TINT, 0).vararg(TINT);
new Initializer().overloads(posPos, posRem).init();
checkResolved(NAME+"(INT,INT)", null, NAME, prepVals(TINT, TINT));
}
@Test
public void noArg() {
final String NAME = "foo";
TestGetBase noArg = new TestGetBase(NAME, TINT);
new Initializer().overloads(noArg).init();
checkResolved(NAME+"()", noArg, NAME, prepVals());
}
@Test
public void onePosAndRemainingWithPickingSet() {
final String NAME = "coalesce";
TestGetBase coalesce = new TestGetBase(NAME, TVARCHAR);
coalesce.builder().pickingVararg(null, 0);
new Initializer().overloads(coalesce).init();
try {
OverloadResolver.OverloadResult result = registry.getScalarsResolver().get(NAME, prepVals());
fail("WrongArity expected but got: " + result);
} catch(WrongExpressionArityException e) {
// Expected
}
checkResolved(NAME + "(INT)", coalesce, NAME, prepVals(TINT));
new Initializer().overloads(coalesce).casts(C_INT_BIGINT).types(TDATE).init();
checkResolved(NAME+"(null,INT,BIGINT)", coalesce, NAME, prepVals(null, TINT, TBIGINT));
try {
checkResolved(NAME+"(null,DATE,INT)", coalesce, NAME, prepVals(null, TDATE, TINT));
fail("expected overload exception");
} catch (NoSuchCastException e) {
// There is no common type between date and int
}
}
@Test
public void onlyPickingRemaining() {
final String NAME = "first";
TestGetBase first = new TestGetBase(NAME, null);
first.builder.pickingVararg(null, 0);
new Initializer().overloads(first).init();
checkResolved(NAME + "(INT)", first, NAME, prepVals(TINT));
new Initializer().overloads(first).casts(C_INT_BIGINT).init();
checkResolved(NAME+"(BIGINT,INT)", first, NAME, prepVals(TBIGINT,TINT));
try {
checkResolved(NAME+"()", first, NAME, prepVals());
fail("expected overload exception");
} catch (WrongExpressionArityException e) {
// can't resolve overload if nargs is wrong
}
try {
checkResolved(NAME+"(null)", first, NAME, Arrays.asList(prepVal(null)));
fail("expected overload exception");
} catch (ArgumentTypeRequiredException e) {
// can't find picking type for first if it's null
}
}
}