http://git-wip-us.apache.org/repos/asf/impala/blob/27577dd6/fe/src/test/java/org/apache/impala/analysis/NumericLiteralTest.java ---------------------------------------------------------------------- diff --git a/fe/src/test/java/org/apache/impala/analysis/NumericLiteralTest.java b/fe/src/test/java/org/apache/impala/analysis/NumericLiteralTest.java new file mode 100644 index 0000000..23f8d88 --- /dev/null +++ b/fe/src/test/java/org/apache/impala/analysis/NumericLiteralTest.java @@ -0,0 +1,655 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +package org.apache.impala.analysis; + +import static org.junit.Assert.*; + +import java.math.BigDecimal; + +import org.apache.impala.catalog.ScalarType; +import org.apache.impala.catalog.Type; +import org.apache.impala.common.AnalysisException; +import org.apache.impala.common.InvalidValueException; +import org.apache.impala.common.SqlCastException; +import org.junit.Test; + +/** + * Tests the numeric literal which is complex because of its ability to hold + * values across many types. The value and type must be compatible at all times. + * + * Note that a comprehensive set of tests exist on the C++ site in expr-test.cc. + * Those tests call into Java, then verify the results. Very complete, but hard + * to debug from the Java side. + */ +public class NumericLiteralTest { + + // Approximate maximum DECIMAL scale for a BIGINT + // 9,223,372,036,854,775,807 + private static final int MAX_BIGINT_PRECISION = 19; + + // "One above" and "one below" values for testing type ranges + private static final BigDecimal ABOVE_TINYINT = + NumericLiteral.MAX_TINYINT.add(BigDecimal.ONE); + private static final BigDecimal BELOW_TINYINT = + NumericLiteral.MIN_TINYINT.subtract(BigDecimal.ONE); + private static final BigDecimal ABOVE_SMALLINT = + NumericLiteral.MAX_SMALLINT.add(BigDecimal.ONE); + private static final BigDecimal BELOW_SMALLINT = + NumericLiteral.MIN_SMALLINT.subtract(BigDecimal.ONE); + private static final BigDecimal ABOVE_INT = + NumericLiteral.MAX_INT.add(BigDecimal.ONE); + private static final BigDecimal BELOW_INT = + NumericLiteral.MIN_INT.subtract(BigDecimal.ONE); + private static final BigDecimal ABOVE_BIGINT = + NumericLiteral.MAX_BIGINT.add(BigDecimal.ONE); + private static final BigDecimal BELOW_BIGINT = + NumericLiteral.MIN_BIGINT.subtract(BigDecimal.ONE); + + private static String repeat(String str, int n) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < n; i++) buf.append(str); + return buf.toString(); + } + + private static String genDecimal(int precision, int scale) { + if (scale == 0) { + return repeat("9", precision); + } else { + return repeat("9", precision - scale) + "." + + repeat("4", scale); + } + } + + @Test + public void testBasics() throws AnalysisException { + NumericLiteral n = NumericLiteral.create(0); + // Starts with the smallest possible type + assertEquals(Type.TINYINT, n.getType()); + // Literals start analyzed + assertTrue(n.isAnalyzed()); + // With their costs set + assertEquals(LiteralExpr.LITERAL_COST, n.getCost(), 0.01); + assertEquals(-1, n.getSelectivity(), 0.01); + // Sanity check of the string representation + assertEquals("0", n.getStringValue()); + assertEquals("0:TINYINT", n.toString()); + // Sanity check of casting + n.castTo(Type.SMALLINT); + assertEquals(Type.SMALLINT, n.getType()); + assertEquals("0:SMALLINT", n.toString()); + } + + /** + * Sanity test: we rely on the constants to be accurate the other tests. + * This will, hopefully, catch any accidental changes to them. + */ + @Test + public void testConstants() throws InvalidValueException { + assertEquals(BigDecimal.valueOf(Byte.MIN_VALUE), NumericLiteral.MIN_TINYINT); + assertEquals(BigDecimal.valueOf(Byte.MAX_VALUE), NumericLiteral.MAX_TINYINT); + assertEquals(BigDecimal.valueOf(Short.MIN_VALUE), NumericLiteral.MIN_SMALLINT); + assertEquals(BigDecimal.valueOf(Short.MAX_VALUE), NumericLiteral.MAX_SMALLINT); + assertEquals(BigDecimal.valueOf(Integer.MIN_VALUE), NumericLiteral.MIN_INT); + assertEquals(BigDecimal.valueOf(Integer.MAX_VALUE), NumericLiteral.MAX_INT); + assertEquals(BigDecimal.valueOf(Long.MIN_VALUE), NumericLiteral.MIN_BIGINT); + assertEquals(BigDecimal.valueOf(Long.MAX_VALUE), NumericLiteral.MAX_BIGINT); + assertEquals(BigDecimal.valueOf(-Float.MAX_VALUE), NumericLiteral.MIN_FLOAT); + assertEquals(BigDecimal.valueOf(Float.MAX_VALUE), NumericLiteral.MAX_FLOAT); + assertEquals(BigDecimal.valueOf(-Double.MAX_VALUE), NumericLiteral.MIN_DOUBLE); + assertEquals(BigDecimal.valueOf(Double.MAX_VALUE), NumericLiteral.MAX_DOUBLE); + } + + /** + * Detailed test of the mechanism to infer the "natural type" (smallest + * type) for a value. + */ + @Test + public void testInferType() throws SqlCastException { + assertEquals(Type.TINYINT, NumericLiteral.inferType(BigDecimal.ZERO)); + assertEquals(Type.TINYINT, NumericLiteral.inferType(NumericLiteral.MIN_TINYINT)); + assertEquals(Type.TINYINT, NumericLiteral.inferType(NumericLiteral.MAX_TINYINT)); + + assertEquals(Type.SMALLINT, NumericLiteral.inferType(ABOVE_TINYINT)); + assertEquals(Type.SMALLINT, NumericLiteral.inferType(BELOW_TINYINT)); + assertEquals(Type.SMALLINT, NumericLiteral.inferType(NumericLiteral.MIN_SMALLINT)); + assertEquals(Type.SMALLINT, NumericLiteral.inferType(NumericLiteral.MAX_SMALLINT)); + + assertEquals(Type.INT, NumericLiteral.inferType(ABOVE_SMALLINT)); + assertEquals(Type.INT, NumericLiteral.inferType(BELOW_SMALLINT)); + assertEquals(Type.INT, NumericLiteral.inferType(NumericLiteral.MIN_INT)); + assertEquals(Type.INT, NumericLiteral.inferType(NumericLiteral.MAX_INT)); + + assertEquals(Type.BIGINT, NumericLiteral.inferType(ABOVE_INT)); + assertEquals(Type.BIGINT, NumericLiteral.inferType(BELOW_INT)); + assertEquals(Type.BIGINT, NumericLiteral.inferType(NumericLiteral.MIN_BIGINT)); + assertEquals(Type.BIGINT, NumericLiteral.inferType(NumericLiteral.MAX_BIGINT)); + + // 9.9. Is DECIMAL + assertEquals(ScalarType.createDecimalType(2, 1), + NumericLiteral.inferType( + new BigDecimal(genDecimal(2, 1)))); + assertEquals(ScalarType.createDecimalType(2, 1), + NumericLiteral.inferType( + new BigDecimal(genDecimal(2, 1)).negate())); + + // One bigger or smaller than BIGINT + assertEquals(ScalarType.createDecimalType(MAX_BIGINT_PRECISION, 0), + NumericLiteral.inferType(ABOVE_BIGINT)); + assertEquals(ScalarType.createDecimalType(MAX_BIGINT_PRECISION, 0), + NumericLiteral.inferType(BELOW_BIGINT)); + + // All 9s, just bigger than BIGINT + assertEquals(ScalarType.createDecimalType(MAX_BIGINT_PRECISION, 0), + NumericLiteral.inferType( + new BigDecimal(genDecimal(MAX_BIGINT_PRECISION, 0)))); + assertEquals(ScalarType.createDecimalType(MAX_BIGINT_PRECISION, 0), + NumericLiteral.inferType( + new BigDecimal(genDecimal(MAX_BIGINT_PRECISION, 0)).negate())); + + // All 9s, at limits of DECIMAL precision + assertEquals(ScalarType.createDecimalType(ScalarType.MAX_SCALE, 0), + NumericLiteral.inferType( + new BigDecimal(genDecimal(ScalarType.MAX_SCALE, 0)))); + assertEquals(ScalarType.createDecimalType(ScalarType.MAX_SCALE, 0), + NumericLiteral.inferType( + new BigDecimal(genDecimal(ScalarType.MAX_SCALE, 0)).negate())); + + // Too large for DECIMAL, flips to DOUBLE + assertEquals(Type.DOUBLE, + NumericLiteral.inferType( + new BigDecimal(genDecimal(ScalarType.MAX_SCALE + 1, 0)))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType( + new BigDecimal(genDecimal(ScalarType.MAX_SCALE + 1, 0)).negate())); + + // Too large for Decimal, small enough for FLOAT + // DECIMAL range is e38 as is FLOAT. So, there is a small range + // in which we could flip from DECIMAL to FLOAT, but we choose + // to use DOUBLE even in this range. + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(NumericLiteral.MIN_FLOAT)); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(NumericLiteral.MAX_FLOAT)); + // Float.MIN_VALUE means smallest positive value, confusingly + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(Float.MIN_VALUE))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(-Float.MIN_VALUE))); + + // Too large for Decimal, exponent too large for FLOAT + String value = "12345" + repeat("0", 40); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value).negate())); + + value = repeat("9", 10) + repeat("0", 40); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value).negate())); + + // Too many digits for DOUBLE, but exponent fits + value = repeat("9", 30) + repeat("0", 50); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value).negate())); + value = genDecimal(100, 10); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(value).negate())); + + // Limit of DOUBLE range + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(NumericLiteral.MIN_DOUBLE)); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(NumericLiteral.MAX_DOUBLE)); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(Double.MIN_VALUE))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType(new BigDecimal(-Double.MIN_VALUE))); + + // Too big for DOUBLE or DECIMAL + try { + NumericLiteral.inferType(new BigDecimal(genDecimal(309, 0))); + fail(); + } catch (SqlCastException e) { + // Expected + } + + // Overflows DOUBLE, but low digits are truncated, so is DOUBLE + assertEquals(Type.DOUBLE, + NumericLiteral.inferType( + NumericLiteral.MAX_DOUBLE.add(BigDecimal.ONE))); + assertEquals(Type.DOUBLE, + NumericLiteral.inferType( + NumericLiteral.MAX_DOUBLE.add(BigDecimal.ONE).negate())); + + // Another power of 10, actual overflow + try { + NumericLiteral.inferType( + NumericLiteral.MAX_DOUBLE.multiply(BigDecimal.TEN)); + fail(); + } catch (SqlCastException e) { + // Expected + } + try { + NumericLiteral.inferType( + NumericLiteral.MAX_DOUBLE.multiply(BigDecimal.TEN).negate()); + fail(); + } catch (SqlCastException e) { + // Expected + } + + // Too small for DOUBLE, too many digits for DECIMAL + // BUG: Should round to zero per SQL standard. + value = "." + repeat("0", 325) + "1"; + try { + NumericLiteral.inferType(new BigDecimal(value)); + fail(); + } catch (SqlCastException e) { + // Expected + } + } + + @Test + public void testIsOverflow() throws InvalidValueException { + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.TINYINT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MIN_TINYINT, Type.TINYINT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MAX_TINYINT, Type.TINYINT)); + assertTrue(NumericLiteral.isOverflow(ABOVE_TINYINT, Type.TINYINT)); + assertTrue(NumericLiteral.isOverflow(BELOW_TINYINT, Type.TINYINT)); + + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.SMALLINT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MIN_SMALLINT, Type.SMALLINT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MAX_SMALLINT, Type.SMALLINT)); + assertTrue(NumericLiteral.isOverflow(ABOVE_SMALLINT, Type.SMALLINT)); + assertTrue(NumericLiteral.isOverflow(BELOW_SMALLINT, Type.SMALLINT)); + + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.INT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MIN_INT, Type.INT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MAX_INT, Type.INT)); + assertTrue(NumericLiteral.isOverflow(ABOVE_INT, Type.INT)); + assertTrue(NumericLiteral.isOverflow(BELOW_INT, Type.INT)); + + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.BIGINT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MIN_BIGINT, Type.BIGINT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MAX_BIGINT, Type.BIGINT)); + assertTrue(NumericLiteral.isOverflow(ABOVE_BIGINT, Type.BIGINT)); + assertTrue(NumericLiteral.isOverflow(BELOW_BIGINT, Type.BIGINT)); + + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.FLOAT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MIN_FLOAT, Type.FLOAT)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MAX_FLOAT, Type.FLOAT)); + assertFalse(NumericLiteral.isOverflow( + BigDecimal.valueOf(Float.MIN_VALUE), Type.FLOAT)); + assertTrue(NumericLiteral.isOverflow( + NumericLiteral.MAX_FLOAT.add(BigDecimal.ONE), Type.FLOAT)); + assertTrue(NumericLiteral.isOverflow( + NumericLiteral.MAX_FLOAT.multiply(BigDecimal.TEN), Type.FLOAT)); + // Underflow is not overflow + assertFalse(NumericLiteral.isOverflow(BigDecimal.valueOf( + Float.MIN_VALUE).divide(BigDecimal.TEN), Type.FLOAT)); + + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.DOUBLE)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MIN_DOUBLE, Type.DOUBLE)); + assertFalse(NumericLiteral.isOverflow(NumericLiteral.MAX_DOUBLE, Type.DOUBLE)); + assertFalse(NumericLiteral.isOverflow( + BigDecimal.valueOf(Double.MIN_VALUE), Type.DOUBLE)); + // Have to add quite a bit (enough to hit a significant digit in the + // double), else BigDecimal truncates the bits when converting to double. + assertTrue(NumericLiteral.isOverflow( + NumericLiteral.MAX_DOUBLE.add(new BigDecimal("1e300")), Type.DOUBLE)); + assertTrue(NumericLiteral.isOverflow( + NumericLiteral.MAX_DOUBLE.multiply(BigDecimal.TEN), Type.DOUBLE)); + // Underflow is not overflow + assertFalse(NumericLiteral.isOverflow(BigDecimal.valueOf( + Double.MIN_VALUE).divide(BigDecimal.TEN), Type.DOUBLE)); + + assertFalse(NumericLiteral.isOverflow(BigDecimal.ZERO, Type.DECIMAL)); + assertFalse(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(10,5)), Type.DECIMAL)); + assertFalse(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(10,5)), Type.DECIMAL)); + assertFalse(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(ScalarType.MAX_PRECISION, 0)), Type.DECIMAL)); + assertFalse(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(0, ScalarType.MAX_PRECISION)), Type.DECIMAL)); + assertTrue(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(ScalarType.MAX_PRECISION + 1, 0)), Type.DECIMAL)); + assertTrue(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(ScalarType.MAX_PRECISION + 1, 1)), Type.DECIMAL)); + assertTrue(NumericLiteral.isOverflow( + new BigDecimal(genDecimal(0, ScalarType.MAX_PRECISION + 1)), Type.DECIMAL)); + } + + /** + * Test the constructor that takes a BigDecimal argument and sets the + * literal type to the "natural" type (the smallest type that can hold + * the value.) + */ + @Test + public void testSimpleCtor() throws SqlCastException { + // Spot check. Assumes uses inferType() tested above. + NumericLiteral n = new NumericLiteral(BigDecimal.ZERO); + assertEquals(0, n.getLongValue()); + assertEquals(Type.TINYINT, n.getType()); + + n = new NumericLiteral(NumericLiteral.MAX_TINYINT); + assertEquals(Byte.MAX_VALUE, n.getLongValue()); + assertEquals(Type.TINYINT, n.getType()); + + n = new NumericLiteral(NumericLiteral.MAX_BIGINT); + assertEquals(Type.BIGINT, n.getType()); + assertEquals(Long.MAX_VALUE, n.getLongValue()); + + n = new NumericLiteral(NumericLiteral.MAX_DOUBLE); + assertEquals(Double.MAX_VALUE, n.getDoubleValue(), 1.0); + assertEquals(Type.DOUBLE, n.getType()); + + n = new NumericLiteral(new BigDecimal(genDecimal(35, 0))); + assertEquals(ScalarType.createDecimalType(35, 0), n.getType()); + + try { + new NumericLiteral(NumericLiteral.MAX_DOUBLE.multiply(BigDecimal.TEN)); + fail(); + } catch(SqlCastException e) { + // Expected + } + } + + @Test + public void testTypeCtor() throws InvalidValueException, SqlCastException { + NumericLiteral n = new NumericLiteral(BigDecimal.ZERO); + assertEquals(0, n.getLongValue()); + assertEquals(Type.TINYINT, n.getType()); + + n = new NumericLiteral(NumericLiteral.MAX_TINYINT); + assertEquals(Byte.MAX_VALUE, n.getLongValue()); + assertEquals(Type.TINYINT, n.getType()); + + n = new NumericLiteral(NumericLiteral.MAX_BIGINT); + assertEquals(Type.BIGINT, n.getType()); + assertEquals(Long.MAX_VALUE, n.getLongValue()); + + n = new NumericLiteral(NumericLiteral.MAX_DOUBLE); + assertEquals(Double.MAX_VALUE, n.getDoubleValue(), 1.0); + assertEquals(Type.DOUBLE, n.getType()); + + n = new NumericLiteral(new BigDecimal(genDecimal(35, 0))); + assertEquals(ScalarType.createDecimalType(35, 0), n.getType()); + + try { + new NumericLiteral(NumericLiteral.MAX_DOUBLE.multiply(BigDecimal.TEN)); + fail(); + } catch(SqlCastException e) { + // Expected + } + try { + new NumericLiteral(new BigDecimal("123.45"), + ScalarType.createDecimalType(3, 1)); + fail(); + } catch(SqlCastException e) { + // Expected + } + try { + new NumericLiteral(new BigDecimal(Integer.MAX_VALUE), + Type.TINYINT); + fail(); + } catch(SqlCastException e) { + // Expected + } + + n = new NumericLiteral(new BigDecimal("1.567"), ScalarType.createDecimalType(2, 1)); + assertEquals(ScalarType.createDecimalType(2, 1), n.getType()); + assertEquals("1.6", n.getValue().toString()); + } + + @Test + public void testExtremes() throws InvalidValueException, SqlCastException { + NumericLiteral n = new NumericLiteral(NumericLiteral.MAX_DOUBLE); + assertEquals(Double.MAX_VALUE, n.getDoubleValue(), 1); + n = new NumericLiteral(NumericLiteral.MIN_DOUBLE); + assertEquals(-Double.MAX_VALUE, n.getDoubleValue(), 1); + } + + @Test + public void testCastTo() throws AnalysisException { + { + // Integral types + NumericLiteral n = new NumericLiteral(BigDecimal.ZERO); + Expr result = n.uncheckedCastTo(Type.BIGINT); + assertSame(n, result); + assertEquals(Type.BIGINT, n.getType()); + result = n.uncheckedCastTo(Type.TINYINT); + assertSame(n, result); + assertEquals(Type.TINYINT, n.getType()); + result = n.uncheckedCastTo(ScalarType.createDecimalType(5, 0)); + assertSame(n, result); + assertEquals(ScalarType.createDecimalType(5, 0), n.getType()); + } + { + // Integral types, with overflow + NumericLiteral n = new NumericLiteral(ABOVE_SMALLINT); + Expr result = n.uncheckedCastTo(Type.BIGINT); + assertSame(n, result); + assertEquals(Type.BIGINT, n.getType()); + Expr result2 = n.uncheckedCastTo(Type.SMALLINT); + assertTrue(result2 instanceof CastExpr); + assertEquals(Type.SMALLINT, result2.getType()); + } + { + // Decimal types, with overflow + // Note: not safe to reuse above value after exception + NumericLiteral n = new NumericLiteral(ABOVE_SMALLINT); + Expr result = n.uncheckedCastTo(Type.BIGINT); + assertSame(n, result); + assertEquals(Type.BIGINT, n.getType()); + Expr result2 = n.uncheckedCastTo(ScalarType.createDecimalType(2, 0)); + assertTrue(result2 instanceof CastExpr); + assertEquals(ScalarType.createDecimalType(2, 0), result2.getType()); + } + { + // Decimal types + NumericLiteral n = new NumericLiteral(new BigDecimal("123.45")); + assertEquals(ScalarType.createDecimalType(5, 2), n.getType()); + Expr result = n.uncheckedCastTo(ScalarType.createDecimalType(6, 3)); + assertSame(n, result); + assertEquals(ScalarType.createDecimalType(6, 3), n.getType()); + result = n.uncheckedCastTo(ScalarType.createDecimalType(5, 2)); + assertSame(n, result); + assertEquals(ScalarType.createDecimalType(5, 2), n.getType()); + result = n.uncheckedCastTo(ScalarType.createDecimalType(4, 1)); + assertNotSame(n, result); + assertEquals(ScalarType.createDecimalType(4, 1), result.getType()); + assertEquals("123.5", ((NumericLiteral) result).toSql()); + } + } + + /** + * Test the swap() sign method used by the parser which recognizes + * numbers as: + * + * 12345 --> number + * - number --> swap sign + * + * Note that swap sign can be applied multiple times: + * + * - -1234 --> number, swap sign, swap sign + * + * Swapping sign is not as simple as it might seem. For integers, + * the positive range is smaller than the negative range, so: + * + * 256 --> SMALLINT + * -256 --> TINYINT + * + * This means that 256 starts as a SMALLINT, but drops one + * size to TINYINT when it becomes negative. And, if negated again, it jumps + * up one size to again become SMALLINT. + */ + @Test + public void testSwapSign() { + { + // TINYINT size promotion + int absValue = -(int) Byte.MIN_VALUE; + NumericLiteral n = NumericLiteral.create(absValue); + assertEquals(Type.SMALLINT, n.getType()); + assertEquals(Type.SMALLINT, n.getExplicitType()); + assertEquals(absValue, n.getIntValue()); + n.swapSign(); + assertEquals(Type.TINYINT, n.getType()); + assertEquals(Type.TINYINT, n.getExplicitType()); + assertEquals(-absValue, n.getIntValue()); + n.swapSign(); + assertEquals(Type.SMALLINT, n.getType()); + assertEquals(Type.SMALLINT, n.getExplicitType()); + assertEquals(absValue, n.getIntValue()); + } + { + // Max BIGINT promotion: can't become another, larger + // integer, so must become a DECIMAL. + BigDecimal absValue = NumericLiteral.MIN_BIGINT.negate(); + NumericLiteral n = NumericLiteral.create(absValue); + Type posType = ScalarType.createDecimalType(19); + assertEquals(posType, n.getType()); + assertEquals(posType, n.getExplicitType()); + assertTrue(absValue.compareTo(n.getValue()) == 0); + n.swapSign(); + assertEquals(Type.BIGINT, n.getType()); + assertEquals(Type.BIGINT, n.getExplicitType()); + assertEquals(Long.MIN_VALUE, n.getLongValue()); + n.swapSign(); + assertEquals(posType, n.getType()); + assertEquals(posType, n.getExplicitType()); + assertTrue(absValue.compareTo(n.getValue()) == 0); + } + } + + /** + * Test of the major cases for convertValue(). Details of overflow + * detection are tested above. + */ + @Test + public void testConvertValue() throws SqlCastException { + BigDecimal result = NumericLiteral.convertValue(BigDecimal.ZERO, Type.TINYINT); + assertSame(result, BigDecimal.ZERO); + result = NumericLiteral.convertValue(BigDecimal.ZERO, Type.DOUBLE); + assertSame(result, BigDecimal.ZERO); + result = NumericLiteral.convertValue(BigDecimal.ZERO, + ScalarType.createDecimalType(2, 2)); + assertSame(result, BigDecimal.ZERO); + + // Overflow case + try { + NumericLiteral.convertValue(ABOVE_TINYINT, Type.TINYINT); + fail(); + } catch(SqlCastException e) { + // Expected + } + + // Round to integer + result = NumericLiteral.convertValue( + new BigDecimal("1234.56"), Type.INT); + assertEquals("1235", result.toString()); + + // Round to decimal precision + BigDecimal input = new BigDecimal("1234.56789"); + result = NumericLiteral.convertValue( + input, ScalarType.createDecimalType(7, 3)); + assertEquals("1234.568", result.toString()); + result = NumericLiteral.convertValue( + input, ScalarType.createDecimalType(4, 0)); + assertEquals("1235", result.toString()); + + // Decimal overflow + try { + NumericLiteral.convertValue( + new BigDecimal("1234.56789"), ScalarType.createDecimalType(3, 2)); + fail(); + } catch(SqlCastException e) { + // Expected + } + + // Reuse value as decimal + input = new BigDecimal("1235.56"); + result = NumericLiteral.convertValue(input, + ScalarType.createDecimalType(6, 2)); + assertSame(input, result); + input = new BigDecimal("0.01"); + result = NumericLiteral.convertValue(input, + ScalarType.createDecimalType(2, 2)); + assertSame(input, result); + } + + @Test + public void testCast() throws SqlCastException { + NumericLiteral n = NumericLiteral.create(1000); + assertEquals(Type.SMALLINT, n.getType()); + Expr result = n.uncheckedCastTo(Type.TINYINT); + assertTrue(result instanceof CastExpr); + assertEquals(Type.TINYINT, result.getType()); + + result = n.uncheckedCastTo(Type.INT); + assertSame(n, result); + assertEquals(Type.INT, n.getType()); + + n = new NumericLiteral(new BigDecimal("123.45")); + assertEquals(ScalarType.createDecimalType(5, 2), n.getType()); + assertSame(n, n.uncheckedCastTo(ScalarType.createDecimalType(6, 3))); + assertEquals(ScalarType.createDecimalType(6, 3), n.getType()); + + n = new NumericLiteral(new BigDecimal("123.45")); + Type newType = ScalarType.createDecimalType(4, 1); + result = n.uncheckedCastTo(newType); + assertNotSame(result, n); + assertTrue(result instanceof NumericLiteral); + assertEquals(newType, result.getType()); + NumericLiteral n2 = (NumericLiteral) result; + assertEquals("123.5", n2.getValue().toString()); + + Expr result2 = n2.uncheckedCastTo(Type.SMALLINT); + assertNotSame(result2, result); + assertEquals(Type.SMALLINT, result2.getType()); + assertEquals("124", ((NumericLiteral)result2).getValue().toString()); + } + + @Test + public void testEquality() throws SqlCastException { + NumericLiteral n1 = NumericLiteral.create(10); + NumericLiteral n2 = NumericLiteral.create(10); + NumericLiteral n3 = NumericLiteral.create(10, Type.INT); + NumericLiteral n4 = NumericLiteral.create(11); + NumericLiteral n5 = NumericLiteral.create( + new BigDecimal("10.000"), Type.TINYINT); + + // Same object + assertTrue(n1.localEquals(n1)); + // Types and values match + assertTrue(n1.localEquals(n2)); + // Types differ, values same + assertFalse(n1.localEquals(n3)); + // Types same, values differ + assertFalse(n1.localEquals(n4)); + // Types same, values are considered the same + // (Though BigDecimal.equals() considers the values different + // due to different precisions.) + assertTrue(n1.localEquals(n5)); + } +}
http://git-wip-us.apache.org/repos/asf/impala/blob/27577dd6/fe/src/test/java/org/apache/impala/analysis/ParserTest.java ---------------------------------------------------------------------- diff --git a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java index ad5d111..62c74c5 100644 --- a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java +++ b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java @@ -25,7 +25,6 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import org.apache.impala.analysis.Parser.ParseException; import org.apache.impala.analysis.TimestampArithmeticExpr.TimeUnit; import org.apache.impala.common.AnalysisException; import org.apache.impala.common.FrontendTestBase; @@ -69,7 +68,7 @@ public class ParserTest extends FrontendTestBase { StatementBase result = null; // Save this object to make debugging easier try { result = Parser.parse(stmt); - } catch (ParseException e) { + } catch (AnalysisException e) { if (expectedErrorString != null) { String errorString = e.getMessage(); StringBuilder message = new StringBuilder(); @@ -1062,8 +1061,8 @@ public class ParserTest extends FrontendTestBase { ParsesOk(String.format("select -%s", Double.toString(Double.MAX_VALUE))); // Test overflow and underflow - ParsesOk(String.format("select %s1", Double.toString(Double.MIN_VALUE))); - ParsesOk(String.format("select %s1", Double.toString(Double.MAX_VALUE))); + ParserError(String.format("select %s1", Double.toString(Double.MIN_VALUE))); + ParserError(String.format("select %s1", Double.toString(Double.MAX_VALUE))); } @Test http://git-wip-us.apache.org/repos/asf/impala/blob/27577dd6/fe/src/test/java/org/apache/impala/catalog/CatalogObjectToFromThriftTest.java ---------------------------------------------------------------------- diff --git a/fe/src/test/java/org/apache/impala/catalog/CatalogObjectToFromThriftTest.java b/fe/src/test/java/org/apache/impala/catalog/CatalogObjectToFromThriftTest.java index bf182d6..114e289 100644 --- a/fe/src/test/java/org/apache/impala/catalog/CatalogObjectToFromThriftTest.java +++ b/fe/src/test/java/org/apache/impala/catalog/CatalogObjectToFromThriftTest.java @@ -25,6 +25,7 @@ import java.util.Map; import org.apache.impala.analysis.LiteralExpr; import org.apache.impala.common.AnalysisException; import org.apache.impala.common.ImpalaException; +import org.apache.impala.common.SqlCastException; import org.apache.impala.testutil.CatalogServiceTestCatalog; import org.apache.impala.thrift.CatalogObjectsConstants; import org.apache.impala.thrift.TAccessLevel; @@ -224,14 +225,15 @@ public class CatalogObjectToFromThriftTest { Assert.assertNotNull(part);; // Create a dummy partition with an invalid decimal type. try { - HdfsPartition dummyPart = new HdfsPartition(hdfsTable, part.toHmsPartition(), - Lists.newArrayList(LiteralExpr.create("1.1", ScalarType.createDecimalType(1, 0)), - LiteralExpr.create("1.1", ScalarType.createDecimalType(1, 0))), + new HdfsPartition(hdfsTable, part.toHmsPartition(), + Lists.newArrayList(LiteralExpr.create("11.1", ScalarType.createDecimalType(1, 0)), + LiteralExpr.create("11.1", ScalarType.createDecimalType(1, 0))), null, Lists.<HdfsPartition.FileDescriptor>newArrayList(), TAccessLevel.READ_WRITE); fail("Expected metadata to be malformed."); - } catch (AnalysisException e) { - Assert.assertTrue(e.getMessage().contains("invalid DECIMAL(1,0) value: 1.1")); + } catch (SqlCastException e) { + Assert.assertTrue(e.getMessage().contains( + "Value 11.1 cannot be cast to type DECIMAL(1,0)")); } } http://git-wip-us.apache.org/repos/asf/impala/blob/27577dd6/fe/src/test/java/org/apache/impala/catalog/HdfsPartitionTest.java ---------------------------------------------------------------------- diff --git a/fe/src/test/java/org/apache/impala/catalog/HdfsPartitionTest.java b/fe/src/test/java/org/apache/impala/catalog/HdfsPartitionTest.java index 2e093c1..f40897d 100644 --- a/fe/src/test/java/org/apache/impala/catalog/HdfsPartitionTest.java +++ b/fe/src/test/java/org/apache/impala/catalog/HdfsPartitionTest.java @@ -20,7 +20,6 @@ package org.apache.impala.catalog; import static org.apache.impala.catalog.HdfsPartition.comparePartitionKeyValues; import static org.junit.Assert.*; -import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -60,18 +59,18 @@ public class HdfsPartitionTest { public HdfsPartitionTest() { valuesNull_.add(NullLiteral.create(Type.BIGINT)); - valuesDecimal_.add(new NumericLiteral(BigDecimal.valueOf(1))); - valuesDecimal1_.add(new NumericLiteral(BigDecimal.valueOf(3))); - valuesDecimal2_.add(new NumericLiteral(BigDecimal.valueOf(5))); + valuesDecimal_.add(NumericLiteral.create(1)); + valuesDecimal1_.add(NumericLiteral.create(3)); + valuesDecimal2_.add(NumericLiteral.create(5)); - valuesMixed_.add(new NumericLiteral(BigDecimal.valueOf(3))); + valuesMixed_.add(NumericLiteral.create(3)); valuesMixed_.add(NullLiteral.create(Type.BIGINT)); - valuesMixed1_.add(new NumericLiteral(BigDecimal.valueOf(1))); + valuesMixed1_.add(NumericLiteral.create(1)); valuesMixed1_.add(NullLiteral.create(Type.STRING)); valuesMixed1_.add(new BoolLiteral(true)); - valuesMixed2_.add(new NumericLiteral(BigDecimal.valueOf(1))); + valuesMixed2_.add(NumericLiteral.create(1)); valuesMixed2_.add(new StringLiteral("Large")); valuesMixed2_.add(new BoolLiteral(false)); } @@ -98,7 +97,7 @@ public class HdfsPartitionTest { } List<LiteralExpr> valuesTest = Lists.newArrayList(); - valuesTest.add(new NumericLiteral(BigDecimal.valueOf(3))); + valuesTest.add(NumericLiteral.create(3)); verifyAntiSymmetric(valuesDecimal1_, valuesTest, valuesNull_); valuesTest.add(NullLiteral.create(Type.BIGINT)); verifyAntiSymmetric(valuesMixed_, valuesTest, valuesDecimal_); http://git-wip-us.apache.org/repos/asf/impala/blob/27577dd6/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java ---------------------------------------------------------------------- diff --git a/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java b/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java index c3d5350..de4266b 100644 --- a/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java +++ b/fe/src/test/java/org/apache/impala/common/FrontendTestBase.java @@ -20,7 +20,6 @@ package org.apache.impala.common; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; -import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -35,10 +34,7 @@ import org.apache.impala.analysis.FunctionName; import org.apache.impala.analysis.InsertStmt; import org.apache.impala.analysis.ParseNode; import org.apache.impala.analysis.Parser; -import org.apache.impala.analysis.Parser.ParseException; import org.apache.impala.analysis.QueryStmt; -import org.apache.impala.analysis.SqlParser; -import org.apache.impala.analysis.SqlScanner; import org.apache.impala.analysis.StatementBase; import org.apache.impala.analysis.StmtMetadataLoader; import org.apache.impala.analysis.StmtMetadataLoader.StmtTableCache; @@ -280,7 +276,7 @@ public class FrontendTestBase { StatementBase node = Parser.parse(stmt); assertNotNull(node); return node; - } catch (ParseException e) { + } catch (AnalysisException e) { fail("\nParser error:\n" + e.getMessage()); throw new IllegalStateException(); // Keep compiler happy }