/** * 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.mcompat.mfuncs; import com.foundationdb.server.error.InvalidArgumentTypeException; import com.foundationdb.server.error.InvalidOperationException; import com.foundationdb.server.explain.*; import com.foundationdb.server.types.LazyList; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TCustomOverloadResult; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TInstanceGenerator; import com.foundationdb.server.types.TInstanceNormalizer; import com.foundationdb.server.types.TInstanceNormalizers; import com.foundationdb.server.types.TScalar; import com.foundationdb.server.types.TOverloadResult; import com.foundationdb.server.types.TPreptimeContext; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.aksql.aktypes.AkInterval; import com.foundationdb.server.types.common.BigDecimalWrapper; import com.foundationdb.server.types.common.funcs.TArithmetic; import com.foundationdb.server.types.common.types.DecimalAttribute; import com.foundationdb.server.types.common.types.TBigDecimal; import com.foundationdb.server.types.mcompat.mtypes.MApproximateNumber; import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime; import com.foundationdb.server.types.mcompat.mtypes.MNumeric; import com.foundationdb.server.types.mcompat.mtypes.MString; 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.TPreparedExpression; import com.foundationdb.server.types.texpressions.TScalarBase; import com.google.common.primitives.Doubles; import java.util.ArrayList; import java.util.List; public abstract class MArithmetic extends TArithmetic { private static final int DEC_INDEX = 0; private final String infix; private final boolean associative; private MArithmetic(String overloadName, String infix, boolean associative, TClass operand0, TClass operand1, TClass resultType, int... attrs) { super(overloadName, operand0, operand1, resultType, attrs); this.infix = infix; this.associative = associative; } private MArithmetic(String overloadName, String infix, boolean associative, TClass operand, TClass resultType, int... attrs) { this(overloadName, infix, associative, operand, operand, resultType, attrs); } @Override protected String toStringName() { return "Arith"; } @Override protected String toStringArgsPrefix() { return infix + " -> "; } @Override public CompoundExplainer getExplainer(ExplainContext context, List<? extends TPreparedExpression> inputs, TInstance resultType) { CompoundExplainer ex = super.getExplainer(context, inputs, resultType); if (infix != null) ex.addAttribute(Label.INFIX_REPRESENTATION, PrimitiveExplainer.getInstance(infix)); if (associative) ex.addAttribute(Label.ASSOCIATIVE, PrimitiveExplainer.getInstance(associative)); return ex; } // Add functions public static final TScalar ADD_TINYINT = new MArithmetic("plus", "+", true, MNumeric.TINYINT, MNumeric.MEDIUMINT, 5) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt8(); int a1 = inputs.get(1).getInt8(); output.putInt32(a0 + a1); } }; public static final TScalar ADD_SMALLINT = new MArithmetic("plus", "+", true, MNumeric.SMALLINT, MNumeric.MEDIUMINT, 7) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt16(); int a1 = inputs.get(1).getInt16(); output.putInt32(a0 + a1); } }; public static final TScalar ADD_MEDIUMINT = new MArithmetic("plus", "+", true, MNumeric.MEDIUMINT, MNumeric.INT, 9) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt32(); int a1 = inputs.get(1).getInt32(); output.putInt32(a0 + a1); } }; public static final TScalar ADD_INT = new MArithmetic("plus", "+", true, MNumeric.INT, MNumeric.BIGINT, 12) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt32(); long a1 = inputs.get(1).getInt32(); output.putInt64(a0 + a1); } }; public static final TScalar ADD_BIGINT = new MArithmetic("plus", "+", true, MNumeric.BIGINT, MNumeric.BIGINT, 21) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt64(); long a1 = inputs.get(1).getInt64(); output.putInt64(a0 + a1); } }; public static final TScalar ADD_DECIMAL = new DecimalArithmetic("plus", "+", true) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { ValueSource input0 = inputs.get(0); ValueSource input1 = inputs.get(1); output.putObject(TBigDecimal.getWrapper(context, DEC_INDEX) .set(TBigDecimal.getWrapper(input0, input0.getType())) .add(TBigDecimal.getWrapper(input1, input1.getType()))); } @Override protected long precisionAndScale(int arg0Precision, int arg0Scale, int arg1Precision, int arg1Scale) { return plusOrMinusArithmetic(arg0Precision, arg0Scale, arg1Precision, arg1Scale); } }; public static final TScalar ADD_DOUBLE = new MArithmetic("plus", "+", true, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { output.putDouble(inputs.get(0).getDouble() + inputs.get(1).getDouble()); } }; public static final TScalar ADD_DOUBLE_P2 = new MArithmetic("plus", "+", true, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { output.putDouble(inputs.get(0).getDouble() + inputs.get(1).getDouble()); } @Override public int[] getPriorities() { return new int[] { 100 }; // if everything else failed, cast both to DOUBLEs } }; // Subtract functions public static final TScalar SUBTRACT_TINYINT = new MArithmetic("minus", "-", false, MNumeric.TINYINT, MNumeric.INT, 5) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt8(); int a1 = inputs.get(1).getInt8(); output.putInt32(a0 - a1); } }; public static final TScalar SUBTRACT_SMALLINT = new MArithmetic("minus", "-", false, MNumeric.SMALLINT, MNumeric.MEDIUMINT, 7) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt16(); int a1 = inputs.get(1).getInt16(); output.putInt32(a0 - a1); } }; public static final TScalar SUBTRACT_MEDIUMINT = new MArithmetic("minus", "-", false, MNumeric.MEDIUMINT, MNumeric.INT, 9) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt32(); int a1 = inputs.get(1).getInt32(); output.putInt32(a0 - a1); } }; public static final TScalar SUBTRACT_INT = new MArithmetic("minus", "-", false, MNumeric.INT, MNumeric.BIGINT, 12) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt32(); long a1 = inputs.get(1).getInt32(); output.putInt64(a0 - a1); } }; public static final TScalar SUBTRACT_BIGINT = new MArithmetic("minus", "-", false, MNumeric.BIGINT, MNumeric.BIGINT, 21) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt64(); long a1 = inputs.get(1).getInt64(); output.putInt64(a0 - a1); } }; public static final TScalar SUBTRACT_DECIMAL = new DecimalArithmetic("minus", "-", false) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { ValueSource input0 = inputs.get(0); ValueSource input1 = inputs.get(1); output.putObject(TBigDecimal.getWrapper(context, DEC_INDEX) .set(TBigDecimal.getWrapper(input0, input0.getType())) .subtract(TBigDecimal.getWrapper(input1, input1.getType()))); } @Override protected long precisionAndScale(int arg0Precision, int arg0Scale, int arg1Precision, int arg1Scale) { return plusOrMinusArithmetic(arg0Precision, arg0Scale, arg1Precision, arg1Scale); } }; public static final TScalar SUBSTRACT_DOUBLE = new MArithmetic("minus", "-", true, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { output.putDouble(inputs.get(0).getDouble() - inputs.get(1).getDouble()); } }; public static final TScalar SUBSTRACT_DOUBLE_P2 = new MArithmetic("minus", "-", true, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { output.putDouble(inputs.get(0).getDouble() - inputs.get(1).getDouble()); } @Override public int[] getPriorities() { return new int[] {100}; } }; // (Regular) Divide functions public static final TScalar DIVIDE_TINYINT = new MArithmetic("divide", "/", false, MNumeric.TINYINT, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt8(); if (divisor == 0) output.putNull(); else output.putDouble((double)inputs.get(0).getInt8() / divisor); } }; public static final TScalar DIVIDE_SMALLINT = new MArithmetic("divide", "/", false, MNumeric.SMALLINT, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt16(); if (divisor == 0) output.putNull(); else output.putDouble((double)inputs.get(0).getInt16() / divisor); } }; public static final TScalar DIVIDE_INT = new MArithmetic("divide", "/", false, MNumeric.INT, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt32(); if (divisor == 0L) output.putNull(); else output.putDouble((double)inputs.get(0).getInt32() / divisor); } }; public static final TScalar DIVIDE_BIGINT = new MArithmetic("divide", "/", false, MNumeric.BIGINT, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long divisor = inputs.get(1).getInt64(); if (divisor == 0L) output.putNull(); else output.putDouble((double)inputs.get(0).getInt64() / divisor); } }; public static final TScalar DIVIDE_DOUBLE = new MArithmetic("divide", "/", false, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double divisor = inputs.get(1).getDouble(); if (Double.compare(divisor, 0) == 0) output.putNull(); else output.putDouble(inputs.get(0).getDouble() / divisor); } }; public static final TScalar DIVIDE_DOUBLE_P2 = new MArithmetic("divide", "/", false, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double divisor = inputs.get(1).getDouble(); if (Double.compare(divisor, 0) == 0) output.putNull(); else output.putDouble(inputs.get(0).getDouble() / divisor); } @Override public int[] getPriorities() { return new int[] {100}; } }; public static final TScalar DIVIDE_DECIMAL = new DecimalArithmetic("divide", "/", false) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { ValueSource input0 = inputs.get(0); ValueSource input1 = inputs.get(1); BigDecimalWrapper divisor = TBigDecimal.getWrapper(input1, input1.getType()); if (divisor.isZero()) { output.putNull(); } else { BigDecimalWrapper numerator = TBigDecimal.getWrapper(input0, input0.getType()); BigDecimalWrapper result = TBigDecimal.getWrapper(context, DEC_INDEX); result.set(numerator); result.divide(divisor, context.outputType().attribute(DecimalAttribute.SCALE)); output.putObject(result); } } @Override protected long precisionAndScale(int p1, int s1, int p2, int s2) { // https://dev.mysql.com/doc/refman/5.5/en/arithmetic-functions.html : // // In division performed with /, the scale of the result when using two exact-value operands is the scale of // the first operand plus the value of the div_precision_increment system variable (which is 4 by default). // // This seems to apply to precision, too. int precision = p1 + DIV_PRECISION_INCREMENT; int scale = s1 + DIV_PRECISION_INCREMENT; return packPrecisionAndScale(precision, scale); } }; // integer division public static final TScalar DIV_TINYINT = new MArithmetic("div", "div", false, MNumeric.TINYINT, MNumeric.INT, 4) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt8(); if (divisor == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt8() / divisor); } }; public static final TScalar DIV_SMALLINT = new MArithmetic("div", "div", false, MNumeric.SMALLINT, MNumeric.INT, 6) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt16(); if (divisor == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt16() / divisor); } }; public static final TScalar DIV_MEDIUMINT = new MArithmetic("div", "div", false, MNumeric.MEDIUMINT, MNumeric.INT, 9) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt32(); if (divisor == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt32() / divisor); } }; public static final TScalar DIV_INT =new MArithmetic("div", "div", false, MNumeric.INT, MNumeric.INT, 11) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int divisor = inputs.get(1).getInt32(); if (divisor == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt32() / divisor); } }; public static final TScalar DIV_BIGINT = new MArithmetic("div", "div", false, MNumeric.BIGINT, MNumeric.BIGINT, 20) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long divisor = inputs.get(1).getInt64(); if (divisor == 0L) output.putNull(); else output.putInt64(inputs.get(0).getInt64() / divisor); } }; public static final TScalar DIV_DOUBLE = new MArithmetic("div", "div", false, MApproximateNumber.DOUBLE, MNumeric.BIGINT, 22) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double divisor = inputs.get(1).getDouble(); if (Double.compare(divisor, 0) == 0) output.putNull(); else output.putInt64((long)(inputs.get(0).getDouble() / divisor)); } }; public static final TScalar DIV_DOUBLE_P2 = new MArithmetic("div", "div", false, MApproximateNumber.DOUBLE, MNumeric.BIGINT, 22) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double divisor = inputs.get(1).getDouble(); if (Double.compare(divisor, 0) == 0) output.putNull(); else output.putInt64((long)(inputs.get(0).getDouble() / divisor)); } @Override public int[] getPriorities() { return new int[] {100}; } }; //(String overloadName, String infix, boolean associative, TClass inputType, TInstance resultType) public static final TScalar DIV_DECIMAL = new MArithmetic("div", "div", false, MNumeric.DECIMAL, null) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { ValueSource input0 = inputs.get(0); ValueSource input1 = inputs.get(1); BigDecimalWrapper wrapper = TBigDecimal.getWrapper(context, DEC_INDEX); BigDecimalWrapper numerator = TBigDecimal.getWrapper(input0, input0.getType()); BigDecimalWrapper divisor = TBigDecimal.getWrapper(input1, input1.getType()); long rounded = wrapper.set(numerator).divide(divisor).round(0).asBigDecimal().longValue(); output.putInt64(rounded); } @Override public TOverloadResult resultType() { return TOverloadResult.custom(new TInstanceGenerator(MNumeric.BIGINT), new TCustomOverloadResult() { @Override public TInstance resultInstance(List<TPreptimeValue> inputs, TPreptimeContext context) { TInstance numeratorType = inputs.get(0).type(); int precision = numeratorType.attribute(DecimalAttribute.PRECISION); int scale = numeratorType.attribute(DecimalAttribute.SCALE); // These seem to be MySQL's wonky rules. For instance: // DECIMAL(11, 0) -> BIGINT(12) // DECIMAL(11, 1) -> BIGINT(12) // DECIMAL(11, 2) -> BIGINT(11) // DECIMAL(11, 3) -> BIGINT(10) etc ++precision; scale = (scale == 0) ? 0 : scale - 1; TClass tClass = (scale > 11) ? MNumeric.BIGINT : MNumeric.INT; return tClass.instance(precision - scale, anyContaminatingNulls(inputs)); } }); } }; // Multiply functions public static final TScalar MULTIPLY_TINYINT = new MArithmetic("times", "*", true, MNumeric.TINYINT, MNumeric.INT, 7) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt8(); int a1 = inputs.get(1).getInt8(); output.putInt32(a0 * a1); } }; public static final TScalar MULTIPLY_SMALLINT = new MArithmetic("times", "*", true, MNumeric.SMALLINT, MNumeric.INT, 11) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int a0 = inputs.get(0).getInt16(); int a1 = inputs.get(1).getInt16(); output.putInt32(a0 * a1); } }; public static final TScalar MULTIPLY_MEDIUMINT = new MArithmetic("times", "*", true, MNumeric.MEDIUMINT, MNumeric.BIGINT, 15) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt32(); long a1 = inputs.get(1).getInt32(); output.putInt64(a0 * a1); } }; public static final TScalar MULTIPLY_INT = new MArithmetic("times", "*", true, MNumeric.INT, MNumeric.BIGINT, 21) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt32(); long a1 = inputs.get(1).getInt32(); output.putInt64(a0 * a1); } }; public static final TScalar MULTIPLY_BIGINT = new MArithmetic("times", "*", true, MNumeric.BIGINT, MNumeric.BIGINT, 39) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long a0 = inputs.get(0).getInt64(); long a1 = inputs.get(1).getInt64(); output.putInt64(a0 * a1); } }; public static final TScalar MULTIPLY_DOUBLE = new MArithmetic("times", "*", true, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double result = inputs.get(0).getDouble() * inputs.get(1).getDouble(); if (!Doubles.isFinite(result)) output.putNull(); else output.putDouble(result); } @Override public int[] getPriorities() { return new int[] { 1, 2 }; } }; public static final TScalar MULTIPLY_DECIMAL = new DecimalArithmetic("times", "*", true) { @Override protected long precisionAndScale(int arg0Precision, int arg0Scale, int arg1Precision, int arg1Scale) { //TODO: // for now, just sum up the precisions and scales return packPrecisionAndScale(arg0Precision + arg1Precision, arg0Scale + arg1Scale); } @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { ValueSource input0 = inputs.get(0); ValueSource input1 = inputs.get(1); BigDecimalWrapper wrapper = TBigDecimal.getWrapper(context, DEC_INDEX); BigDecimalWrapper arg0 = TBigDecimal.getWrapper(input0, input0.getType()); BigDecimalWrapper arg1 = TBigDecimal.getWrapper(input1, input1.getType()); output.putObject(wrapper.set(arg0).multiply(arg1)); } }; // mod function public static final TScalar MOD_TINYTINT = new MArithmetic("mod", "mod", false, MNumeric.TINYINT, MNumeric.INT, 4) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int right = inputs.get(1).getInt8(); if (right == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt8() % right); } }; public static final TScalar MOD_SMALLINT = new MArithmetic("mod", "mod", false, MNumeric.SMALLINT, MNumeric.INT, 6) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int right = inputs.get(1).getInt16(); if (right == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt16() % right); } }; public static final TScalar MOD_MEDIUMINT = new MArithmetic("mod", "mod", false, MNumeric.MEDIUMINT, MNumeric.INT, 9) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int right = inputs.get(1).getInt32(); if (right == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt32() % right); } }; public static final TScalar MOD_INT = new MArithmetic("mod", "mod", false, MNumeric.INT, MNumeric.INT, 11) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { int right = inputs.get(1).getInt32(); if (right == 0) output.putNull(); else output.putInt32(inputs.get(0).getInt32() % right); } }; public static final TScalar MOD_BIGINT = new MArithmetic("mod", "mod", false, MNumeric.BIGINT, MNumeric.BIGINT, 20) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { long right = inputs.get(1).getInt64(); if (right == 0L) output.putNull(); else output.putInt64(inputs.get(0).getInt64() % right); } }; public static final TScalar MOD_DOUBLE = new MArithmetic("mod", "mod", false, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double right = inputs.get(1).getDouble(); if (Double.compare(right, 0) == 0) output.putNull(); else output.putDouble(inputs.get(0).getDouble() % right); } }; public static final TScalar MOD_DOUBLE_P2 = new MArithmetic("mod", "mod", false, MApproximateNumber.DOUBLE, MApproximateNumber.DOUBLE) { @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { double right = inputs.get(1).getDouble(); if (Double.compare(right, 0) == 0) output.putNull(); else output.putDouble(inputs.get(0).getDouble() % right); } @Override public int[] getPriorities() { return new int[] {100}; } }; public static final TScalar MOD_DECIMAL = new DecimalArithmetic("mod", "mod", false) { @Override protected long precisionAndScale(int arg0Precision, int arg0Scale, int arg1Precision, int arg1Scale) { return packPrecisionAndScale(Math.max(arg0Precision, arg1Precision), Math.max(arg0Scale, arg1Scale)); } @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { ValueSource input0 = inputs.get(0); ValueSource input1 = inputs.get(1); BigDecimalWrapper divisor = TBigDecimal.getWrapper(input1, input1.getType()); if (divisor.isZero()) output.putNull(); else output.putObject(TBigDecimal.getWrapper(context, DEC_INDEX) .set(TBigDecimal.getWrapper(input0, input0.getType())) .mod(divisor)); } }; // TODO this should extend some base class that MArithmetic also extends, rather than extending MArithmetic // but ignoring its TInstance field private abstract static class DecimalArithmetic extends MArithmetic { @Override public TOverloadResult resultType() { return TOverloadResult.custom(new TCustomOverloadResult() { @Override public TInstance resultInstance(List<TPreptimeValue> inputs, TPreptimeContext context) { TInstance arg0 = inputs.get(0).type(); TInstance arg1 = inputs.get(1).type(); int arg0Precision = arg0.attribute(DecimalAttribute.PRECISION); int arg0Scale = arg0.attribute(DecimalAttribute.SCALE); int arg1Precision = arg1.attribute(DecimalAttribute.PRECISION); int arg1Scale = arg1.attribute(DecimalAttribute.SCALE); long resultPrecisionAndScale = precisionAndScale(arg0Precision, arg0Scale, arg1Precision, arg1Scale); int resultPrecision = (int)(resultPrecisionAndScale >> 32); int resultScale = (int)resultPrecisionAndScale; return MNumeric.DECIMAL.instance(resultPrecision, resultScale, anyContaminatingNulls(inputs)); } }); } protected abstract long precisionAndScale(int arg0Precision, int arg0Scale, int arg1Precision, int arg1Scale); static long packPrecisionAndScale(int precision, int scale) { long result = precision; result <<= 32; result |= scale; return result; } @Override protected TInstanceNormalizer inputSetInstanceNormalizer() { return TInstanceNormalizers.ALL_UNTOUCHED; } static long plusOrMinusArithmetic(int arg0Precision, int arg0Scale, int arg1Precision, int arg1Scale){ int maxScale = Math.max(arg0Scale, arg1Precision); int maxPrecision = Math.max(arg0Precision, arg1Precision); return packPrecisionAndScale(maxPrecision + maxScale, maxScale); } protected DecimalArithmetic(String overloadName, String infix, boolean associative) { super(overloadName, infix, associative, MNumeric.DECIMAL, null); } } /** * Implementation for arithmetic which always results in a NULL VARCHAR(29). Odd as it may seem, such things do * seem to exist. For instance, {@code <time> + INTERVAL N MONTH}. */ static class AlwaysNull extends MArithmetic { private final InvalidOperationException warningErr; AlwaysNull(String overloadName, String infix, boolean associative, TClass operand0, TClass operand1) { super(overloadName, infix, associative, operand0, operand1, MString.VARCHAR, 29); warningErr = null; } AlwaysNull(String overloadName, String infix, boolean associative, TClass operand0, TClass operand1, InvalidOperationException err) { super(overloadName, infix, associative, operand0, operand1, MString.VARCHAR, 29); this.warningErr = err; } @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { if (warningErr != null) context.warnClient(warningErr); output.putNull(); } @Override protected Constantness constness(TPreptimeContext context, int inputIndex, LazyList<? extends TPreptimeValue> values) { return Constantness.CONST; } @Override protected boolean nullContaminates(int inputIndex) { // We return false here so that TScalarBase never tries to look at the inputs as part of evaluating // a const, to see if they're null. If it did, a non-const input would cause an exception during const // evaluation. Returning false here means we'll always get to doEvaluate, which will then putNull. return false; } } private static List<TScalar> generateReflexive(List<TScalar> ret, String name, String infix, boolean asso, TClass types[][]) { for (TClass pair[] : types) { ret.add(new InvalidTypes(name, infix, asso, pair[0], pair[1])); ret.add(new InvalidTypes(name, infix, asso, pair[1], pair[0])); } return ret; } private static List<TScalar> getDateTimeErrorCases() { String names[][] = new String[][]{ {"times", "*"}, {"divide", "/"}, {"div", "div"}}; boolean assos[] = new boolean []{true, false, false}; assert assos.length == names.length : "names and assos differ in length!"; TClass types[][] = new TClass [][] {{ MDateAndTime.DATE, AkInterval.MONTHS}, { MDateAndTime.DATETIME, AkInterval.MONTHS}, { MDateAndTime.TIME, AkInterval.MONTHS}, { MDateAndTime.TIMESTAMP, AkInterval.MONTHS}, { MDateAndTime.YEAR, AkInterval.MONTHS}, { MDateAndTime.DATE, AkInterval.SECONDS}, { MDateAndTime.DATETIME, AkInterval.SECONDS}, { MDateAndTime.TIME, AkInterval.SECONDS}, { MDateAndTime.TIMESTAMP, AkInterval.SECONDS}, { MDateAndTime.YEAR, AkInterval.SECONDS} }; List<TScalar> ret = new ArrayList<>(types.length); for (int n = 0, limit = assos.length; n < limit; ++n) generateReflexive(ret, names[n][0], names[n][1], assos[n],types); // <INTERVAL> minus <DATE/TIME> for (TClass pair[] : types) ret.add(new InvalidTypes("minus", "-", false, pair[1], pair[0])); ret.add(new InvalidTypes("minus", "-", false, AkInterval.MONTHS, MString.VARCHAR)); ret.add(new InvalidTypes("minus", "-", false, AkInterval.SECONDS, MString.VARCHAR)); return ret; } public static final List<TScalar> ERRORS = getDateTimeErrorCases(); //---------- multiplications public static final TScalar MULT_MONTH_DOUBLE = new IntervalArith(AkInterval.MONTHS, 0, MApproximateNumber.DOUBLE, 1, IntervalOp.MULT); public static final TScalar MULT_DOUBLE_MONTH = new IntervalArith(AkInterval.MONTHS, 1, MApproximateNumber.DOUBLE, 0, IntervalOp.MULT); public static final TScalar MULT_SECS_DOUBLE = new IntervalArith(AkInterval.SECONDS, 0, MApproximateNumber.DOUBLE, 1, IntervalOp.MULT); public static final TScalar MULT_DOUBLE_SECS = new IntervalArith(AkInterval.SECONDS, 1, MApproximateNumber.DOUBLE, 0, IntervalOp.MULT); // divisions public static final TScalar DIVIDE_MONTHS_DOUBLE = new IntervalArith(AkInterval.MONTHS, 0, MApproximateNumber.DOUBLE, 1, IntervalOp.DIVIDE); public static final TScalar DIVIDE_SECS_DOUBLE = new IntervalArith(AkInterval.SECONDS, 0, MApproximateNumber.DOUBLE, 1, IntervalOp.DIVIDE); // additions public static final TScalar ADD_MONTH = new IntervalArith(AkInterval.MONTHS, 0, AkInterval.MONTHS, 1, IntervalOp.ADD); public static final TScalar ADD_DAY = new IntervalArith(AkInterval.SECONDS, 0, AkInterval.SECONDS, 1, IntervalOp.ADD); // substractions public static final TScalar SUBSTRACT_MONTH = new IntervalArith(AkInterval.MONTHS, 0, AkInterval.MONTHS, 1, IntervalOp.MINUS); public static final TScalar SUBSTRACT_DAY = new IntervalArith(AkInterval.SECONDS, 0, AkInterval.SECONDS, 1, IntervalOp.MINUS); // div (integer division) public static final TScalar DIV_MONTH = new IntervalArith(AkInterval.MONTHS, 0, MApproximateNumber.DOUBLE, 1, IntervalOp.DIV); public static final TScalar DIV_SECS = new IntervalArith(AkInterval.SECONDS, 0, MApproximateNumber.DOUBLE, 1, IntervalOp.DIV); static class InvalidTypes extends AlwaysNull { private final InvalidArgumentTypeException error; InvalidTypes(String overloadName, String infix, boolean associative, TClass operand0, TClass operand1) { super(overloadName, infix, associative, operand0, operand1); error = new InvalidArgumentTypeException(String.format("Invald Argument Types: %s(<%s>, <%s>)", overloadName, operand0, operand1)); } @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { throw error; } } private static enum IntervalOp { DIV("div") // integer division { @Override long doMath(LazyList<? extends ValueSource> inputs, int pos0, int pos1) { assert pos0 == 0 && pos1 == 1 : "ONLY <INTERVAL> / <NUMERIC> is supported"; return (long)(inputs.get(0).getInt64() / inputs.get(1).getDouble()); } }, MULT("times") { @Override long doMath(LazyList<? extends ValueSource> inputs, int pos0, int pos1) { return Math.round(inputs.get(pos0).getInt64() * inputs.get(pos1).getDouble()); } }, DIVIDE("divide") { @Override long doMath(LazyList<? extends ValueSource> inputs, int pos0, int pos1) { assert pos0 == 0 && pos1 == 1 : "ONLY <INTERVAL> / <NUMERIC> is supported"; return Math.round(inputs.get(0).getInt64() / inputs.get(1).getDouble()); } }, ADD("plus") { @Override long doMath(LazyList<? extends ValueSource> inputs, int pos0, int pos1) { return inputs.get(0).getInt64() + inputs.get(1).getInt64(); } }, MINUS("minus") { @Override long doMath(LazyList<? extends ValueSource> inputs, int pos0, int pos1) { return inputs.get(0).getInt64() - inputs.get(1).getInt64(); } } ; private IntervalOp(String n) { name = n; } final String name; abstract long doMath(LazyList<? extends ValueSource> inputs, int pos0, int pos1); } static class IntervalArith extends TScalarBase { private final TClass left, right; protected final int pos0, pos1; private final IntervalOp op; IntervalArith(TClass left,int pos0, TClass right, int pos1, IntervalOp op) { if (!(pos0 == 0 && pos1 == 1 || pos0 == 1 && pos1 == 0)) throw new IllegalArgumentException("pos0 and pos1 must be {0, 1}"); this.left = left; this.right = right; this.pos0 = pos0; this.pos1 = pos1; this.op = op; } @Override protected void buildInputSets(TInputSetBuilder builder) { builder.covers(left, pos0).covers(right, pos1); } @Override protected void doEvaluate(TExecutionContext context, LazyList<? extends ValueSource> inputs, ValueTarget output) { output.putInt64(op.doMath(inputs, pos0, pos1)); } @Override public String displayName() { return op.name; } @Override public TOverloadResult resultType() { return TOverloadResult.custom(new TCustomOverloadResult() { @Override public TInstance resultInstance(List<TPreptimeValue> inputs, TPreptimeContext context) { return inputs.get(pos0).type(); } }); } } private static final int DIV_PRECISION_INCREMENT = 4; }