blob: 1615827e1db431b5ab835ff692bb44f1370f4f71 [file] [log] [blame]
/*
* 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.drill.exec.physical.impl.scan.convert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.types.TypeProtos.DataMode;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.exec.memory.BufferAllocator;
import org.apache.drill.exec.physical.impl.scan.convert.StandardConversions.ConversionDefn;
import org.apache.drill.exec.physical.impl.scan.convert.StandardConversions.ConversionType;
import org.apache.drill.exec.physical.impl.scan.v3.FixedReceiver;
import org.apache.drill.exec.physical.rowSet.RowSet;
import org.apache.drill.exec.physical.rowSet.RowSet.SingleRowSet;
import org.apache.drill.exec.physical.rowSet.RowSetBuilder;
import org.apache.drill.exec.physical.rowSet.RowSetTestUtils;
import org.apache.drill.exec.physical.rowSet.RowSetWriter;
import org.apache.drill.exec.record.metadata.ColumnMetadata;
import org.apache.drill.exec.record.metadata.SchemaBuilder;
import org.apache.drill.exec.record.metadata.TupleMetadata;
import org.apache.drill.exec.record.metadata.TupleNameSpace;
import org.apache.drill.exec.vector.accessor.InvalidConversionError;
import org.apache.drill.exec.vector.accessor.ScalarWriter;
import org.apache.drill.exec.vector.accessor.ValueWriter;
import org.apache.drill.test.SubOperatorTest;
import org.apache.drill.test.rowSet.RowSetUtilities;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.joda.time.Period;
import org.junit.Test;
public class TestDirectConverter extends SubOperatorTest {
/**
* Mock column conversion factory that takes an input schema, matches it against
* the given writer, and inserts a standard type conversion shim.
*/
private static class ConversionTestFixture {
private final RowSetWriter rowWriter;
private StandardConversions conversions;
private final TupleNameSpace<ValueWriter> rowFormat = new TupleNameSpace<>();
public ConversionTestFixture(BufferAllocator allocator, TupleMetadata outputSchema) {
rowWriter = RowSetTestUtils.makeWriter(allocator, outputSchema);
}
public ConversionTestFixture withProperties(Map<String,String> props) {
conversions = StandardConversions.builder().withProperties(props).build();
return this;
}
private StandardConversions conversions() {
if (conversions == null) {
conversions = StandardConversions.builder().build();
}
return conversions;
}
public void createConvertersFor(TupleMetadata inputSchema) {
for (ColumnMetadata inputCol : inputSchema) {
addColumn(inputCol);
}
}
public void addColumn(ColumnMetadata source) {
ScalarWriter colWriter = rowWriter.scalar(source.name());
// Test uses simple row writer; no support for adding columns.
assertNotNull(colWriter);
ValueWriter converter = conversions().converterFor(colWriter, source);
assertNotNull(converter);
rowFormat.add(source.name(), converter);
}
public ConversionTestFixture addRow(Object...cols) {
assertTrue(cols.length <= rowFormat.count());
for (int i = 0; i < rowFormat.count(); i++) {
rowFormat.get(i).setValue(cols[i]);
}
rowWriter.save();
return this;
}
public ConversionTestFixture addSingleCol(Object col) {
rowFormat.get(0).setValue(col);
rowWriter.save();
return this;
}
public RowSet build() {
return rowWriter.done();
}
}
@Test
public void testSchemaMerge() {
TupleMetadata providedSchema = new SchemaBuilder()
.add("a", MinorType.INT)
.addNullable("b", MinorType.INT)
.add("c", MinorType.INT)
.build();
providedSchema.setBooleanProperty("foo", true);
TupleMetadata readerSchema = new SchemaBuilder()
.add("a", MinorType.VARCHAR)
.add("b", MinorType.VARCHAR)
.add("d", MinorType.VARCHAR)
.build();
TupleMetadata expected = new SchemaBuilder()
.add("a", MinorType.INT)
.addNullable("b", MinorType.INT)
.add("d", MinorType.VARCHAR)
.build();
TupleMetadata mergedSchema = FixedReceiver.Builder.mergeSchemas(providedSchema, readerSchema);
assertTrue(expected.isEquivalent(mergedSchema));
assertTrue(mergedSchema.booleanProperty("foo"));
}
/**
* Test the standard string-to-type conversion using an ad-hoc conversion
* from the input type (the type used by the row set builder) to the output
* (vector) type.
*/
@Test
public void testStringToNumberConversion() {
// Create the input and output schemas
TupleMetadata outputSchema = new SchemaBuilder()
.add("ti", MinorType.TINYINT)
.add("si", MinorType.SMALLINT)
.add("int", MinorType.INT)
.add("bi", MinorType.BIGINT)
.add("fl", MinorType.FLOAT4)
.add("db", MinorType.FLOAT8)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("ti", MinorType.VARCHAR)
.add("si", MinorType.VARCHAR)
.add("int", MinorType.VARCHAR)
.add("bi", MinorType.VARCHAR)
.add("fl", MinorType.VARCHAR)
.add("db", MinorType.VARCHAR)
.buildSchema();
// Load test data using converters
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow("11", "12", "13", "14", "15.5", "16.25")
.addRow("127", "32757", Integer.toString(Integer.MAX_VALUE),
Long.toString(Long.MAX_VALUE), "10E6", "10E200")
.build();
// Build the expected vector without a type converter.
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow(11, 12, 13, 14L, 15.5F, 16.25D)
.addRow(127, 32757, Integer.MAX_VALUE, Long.MAX_VALUE, 10E6F, 10E200D)
.build();
// Compare
RowSetUtilities.verify(expected, actual);
}
/**
* Test the standard string-to-type conversion using an ad-hoc conversion
* from the input type (the type used by the row set builder) to the output
* (vector) type.
*/
@Test
public void testNumberToStringConversion() {
// Create the schema
TupleMetadata outputSchema = new SchemaBuilder()
.add("ti", MinorType.VARCHAR)
.add("si", MinorType.VARCHAR)
.add("int", MinorType.VARCHAR)
.add("bi", MinorType.VARCHAR)
.add("fl", MinorType.VARCHAR)
.add("db", MinorType.VARCHAR)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("ti", MinorType.TINYINT)
.add("si", MinorType.SMALLINT)
.add("int", MinorType.INT)
.add("bi", MinorType.BIGINT)
.add("fl", MinorType.FLOAT4)
.add("db", MinorType.FLOAT8)
.buildSchema();
// The setObject() method won't do the Float to Double conversion,
// so values before are provided as doubles in the float case.
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow(11, 12, 13, 14L, 15.5D, 16.25D)
.addRow(127, 32757, Integer.MAX_VALUE, Long.MAX_VALUE, 10E6D, 10E200D)
.build();
// Build the expected vector without a type converter.
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow("11", "12", "13", "14", "15.5", "16.25")
.addRow("127", "32757", Integer.toString(Integer.MAX_VALUE),
Long.toString(Long.MAX_VALUE), "1.0E7", "1.0E201")
.build();
// Compare
RowSetUtilities.verify(expected, actual);
}
@Test
public void testStringToNumberConversionError() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("int", MinorType.INT)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("int", MinorType.VARCHAR)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
try {
testFixture.addRow("foo");
fail();
} catch (InvalidConversionError e) {
// Expected
} finally {
testFixture.build().clear();
}
}
public static BigDecimal dec(int value) {
return new BigDecimal(value);
}
/**
* Tests the implicit conversions provided by the column writer itself.
* No conversion mechanism is needed in this case.
*/
@Test
public void testImplicitConversion() {
TupleMetadata schema = new SchemaBuilder()
.add("ti", MinorType.TINYINT)
.add("si", MinorType.SMALLINT)
.add("int", MinorType.INT)
.add("bi", MinorType.BIGINT)
.add("fl", MinorType.FLOAT4)
.add("db", MinorType.FLOAT8)
.add("dec", MinorType.VARDECIMAL, 10, 0)
.buildSchema();
// Test allowed implicit conversions.
RowSet actual = new RowSetBuilder(fixture.allocator(), schema)
.addRow(11, 12, 13, 14, 15, 16, 17) // int
.addRow(21L, 22L, 23L, 24L, 25L, 26L, 27L) // long
.addRow(31F, 32F, 33F, 34F, 35F, 36F, 37F) // float
.addRow(41D, 42D, 43D, 44D, 45D, 46D, 47D) // double
.addRow(dec(51), dec(52), dec(53), dec(54), dec(55), dec(56), dec(57)) // decimal
.build();
final SingleRowSet expected = fixture.rowSetBuilder(schema)
.addRow(11, 12, 13, 14L, 15F, 16D, dec(17))
.addRow(21, 22, 23, 24L, 25F, 26D, dec(27))
.addRow(31, 32, 33, 34L, 35F, 36D, dec(37))
.addRow(41, 42, 43, 44L, 45F, 46D, dec(47))
.addRow(51, 52, 53, 54L, 55L, 56D, dec(57))
.build();
RowSetUtilities.verify(expected, actual);
}
/**
* The column accessors provide only int setters. For performance, the int value is
* assumed to be of the correct range for the target column. If not, truncation of
* the highest bytes occurs.
* <p>
* The assumption is, if the reader or other code expects that overflow might
* occur, that code should be implemented in the client (or in a type conversion
* shim), leaving the normal code path to optimize for the 99% of the cases where
* the value is in the proper range.
*/
@Test
public void testImplicitConversionIntTruncation() {
TupleMetadata schema = new SchemaBuilder()
.add("ti", MinorType.TINYINT)
.add("si", MinorType.SMALLINT)
.buildSchema();
// Test allowed implicit conversions.
RowSet actual = new RowSetBuilder(fixture.allocator(), schema)
.addRow(Byte.MAX_VALUE + 1, Short.MAX_VALUE + 1)
.addRow(Byte.MAX_VALUE + 2, Short.MAX_VALUE + 2)
.build();
// Build the expected vector without a type converter.
final SingleRowSet expected = fixture.rowSetBuilder(schema)
.addRow(Byte.MIN_VALUE, Short.MIN_VALUE)
.addRow(Byte.MIN_VALUE + 1, Short.MIN_VALUE + 1)
.build();
RowSetUtilities.verify(expected, actual);
}
/**
* Overflow from double-to-int is detected.
*/
@Test
public void testImplicitConversionIntOverflow() {
TupleMetadata schema = new SchemaBuilder()
.add("int", MinorType.INT)
.buildSchema();
{
RowSetBuilder builder = new RowSetBuilder(fixture.allocator(), schema);
try {
builder.addRow((long) Integer.MAX_VALUE + 1);
fail();
} catch (InvalidConversionError e) {
// Expected
} finally {
builder.build().clear();
}
}
{
RowSetBuilder builder = new RowSetBuilder(fixture.allocator(), schema);
try {
builder.addRow((double) Integer.MAX_VALUE + 1);
fail();
} catch (InvalidConversionError e) {
// Expected
} finally {
builder.build().clear();
}
}
}
/**
* Implicit conversion from double (or float) follows the Java Math.round
* rules: round to the closest long value. Readers that want other behavior
* should insert a type-conversion shim to implement the preferred rules.
*/
@Test
public void testImplicitConversionDoubleClamp() {
TupleMetadata schema = new SchemaBuilder()
.add("bi", MinorType.BIGINT)
.buildSchema();
RowSet actual = new RowSetBuilder(fixture.allocator(), schema)
.addRow(Long.MAX_VALUE * 10D)
.addRow(Double.NaN)
.addRow(Double.MAX_VALUE)
.addRow(Double.MIN_VALUE)
.addRow(Double.POSITIVE_INFINITY)
.addRow(Double.NEGATIVE_INFINITY)
.build();
final SingleRowSet expected = fixture.rowSetBuilder(schema)
.addRow(Long.MAX_VALUE)
.addRow(0)
.addRow(Long.MAX_VALUE)
.addRow(0)
.addRow(Long.MAX_VALUE)
.addRow(Long.MIN_VALUE)
.build();
RowSetUtilities.verify(expected, actual);
}
/**
* Implicit conversion from String to period using default ISO
* format.
*/
@Test
public void testStringToInterval() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("id", MinorType.INTERVALDAY)
.add("iy", MinorType.INTERVALYEAR)
.add("int", MinorType.INTERVAL)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("id", MinorType.VARCHAR)
.add("iy", MinorType.VARCHAR)
.add("int", MinorType.VARCHAR)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow("P2DT3H4M5S", "P9Y8M", "P9Y8M2DT3H4M5S")
.build();
Period p1 = Period.days(2).plusHours(3).plusMinutes(4).plusSeconds(5);
Period p2 = Period.years(9).plusMonths(8);
Period p3 = p1.plus(p2);
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow(p1, p2, p3)
.build();
RowSetUtilities.verify(expected, actual);
}
@Test
public void testIntervalToString() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("id", MinorType.VARCHAR)
.add("iy", MinorType.VARCHAR)
.add("int", MinorType.VARCHAR)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("id", MinorType.INTERVALDAY)
.add("iy", MinorType.INTERVALYEAR)
.add("int", MinorType.INTERVAL)
.buildSchema();
Period p1 = Period.days(2).plusHours(3).plusMinutes(4).plusSeconds(5);
Period p2 = Period.years(9).plusMonths(8);
Period p3 = p1.plus(p2);
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow(p1, p2, p3)
.build();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow("P2DT3H4M5S", "P9Y8M", "P9Y8M2DT3H4M5S")
.build();
RowSetUtilities.verify(expected, actual);
}
/**
* Test VARCHAR to DATE, TIME and TIMESTAMP conversion
* using default ISO formats.
*/
@Test
public void testStringToDateTimeDefault() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("date", MinorType.DATE)
.add("time", MinorType.TIME)
.add("ts", MinorType.TIMESTAMP)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("date", MinorType.VARCHAR)
.add("time", MinorType.VARCHAR)
.add("ts", MinorType.VARCHAR)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow("2019-03-28", "12:34:56", "2019-03-28T12:34:56Z")
.addRow("2019-03-28", "12:34:56", "2019-03-28T12:34:56Z")
.build();
LocalTime lt = new LocalTime(12, 34, 56);
LocalDate ld = new LocalDate(2019, 3, 28);
Instant ts = ld.toDateTime(lt, DateTimeZone.UTC).toInstant();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow(ld, lt, ts)
.addRow(ld.toDateTimeAtStartOfDay(DateTimeZone.UTC).toInstant().getMillis(),
lt.getMillisOfDay(), ts.getMillis())
.build();
RowSetUtilities.verify(expected, actual);
}
@Test
public void testStringToDateTimeCustom() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("date", MinorType.DATE)
.add("time", MinorType.TIME)
.add("ts", MinorType.TIMESTAMP)
.buildSchema();
outputSchema.metadata("date").setFormat("M/d/yyyy");
outputSchema.metadata("time").setFormat("hh:mm:ss a");
outputSchema.metadata("ts").setFormat("M/d/yyyy hh:mm:ss a Z");
TupleMetadata inputSchema = new SchemaBuilder()
.add("date", MinorType.VARCHAR)
.add("time", MinorType.VARCHAR)
.add("ts", MinorType.VARCHAR)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow("3/28/2019", "12:34:56 PM", "3/28/2019 12:34:56 PM Z")
.addRow("3/28/2019", "12:34:56 PM", "3/28/2019 12:34:56 PM Z")
.build();
LocalTime lt = new LocalTime(12, 34, 56);
LocalDate ld = new LocalDate(2019, 3, 28);
Instant ts = ld.toDateTime(lt, DateTimeZone.UTC).toInstant();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow(ld, lt, ts)
.addRow(ld.toDateTimeAtStartOfDay(DateTimeZone.UTC).toInstant().getMillis(),
lt.getMillisOfDay(), ts.getMillis())
.build();
RowSetUtilities.verify(expected, actual);
}
@Test
public void testDateTimeToString() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("date", MinorType.VARCHAR)
.add("time", MinorType.VARCHAR)
.add("ts", MinorType.VARCHAR)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("date", MinorType.DATE)
.add("time", MinorType.TIME)
.add("ts", MinorType.TIMESTAMP)
.buildSchema();
LocalTime lt = new LocalTime(12, 34, 56);
LocalDate ld = new LocalDate(2019, 3, 28);
Instant ts = ld.toDateTime(lt, DateTimeZone.UTC).toInstant();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow(ld, lt, ts)
.build();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow("2019-03-28", "12:34:56.000", "2019-03-28T12:34:56.000Z")
.build();
RowSetUtilities.verify(expected, actual);
}
/**
* Test conversion to/from Java-style Booleans.
*/
@Test
public void testBooleanToFromString() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("bool", MinorType.BIT)
.add("str", MinorType.VARCHAR)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("bool", MinorType.VARCHAR)
.add("str", MinorType.BIT)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow("true", false)
.addRow("false", true)
.addRow("TRUE", false)
.addRow("FALSE", true)
.build();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow(true, "false")
.addRow(false, "true")
.addRow(true, "false")
.addRow(false, "true")
.build();
RowSetUtilities.verify(expected, actual);
}
private static BigDecimal dec(String value) {
return new BigDecimal(value);
}
@Test
public void testDecimalFromString() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("id", MinorType.INT)
.add("dec", MinorType.VARDECIMAL, 4, 2)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("id", MinorType.INT)
.add("dec", MinorType.VARCHAR)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
RowSet actual = testFixture
.addRow(1, "0")
.addRow(2, "-0")
.addRow(3, "0.12")
.addRow(4, "1.23")
.addRow(5, "12.34")
// Rounding occurs for VARDECIMAL
.addRow(6, "23.456")
.addRow(7, "-99.99")
.build();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addRow(1, dec("0"))
.addRow(2, dec("-0"))
.addRow(3, dec("0.12"))
.addRow(4, dec("1.23"))
.addRow(5, dec("12.34"))
.addRow(6, dec("23.46"))
.addRow(7, dec("-99.99"))
.build();
RowSetUtilities.verify(expected, actual);
}
@Test
public void testDecimalOverflow() {
TupleMetadata outputSchema = new SchemaBuilder()
.add("id", MinorType.INT)
.add("dec", MinorType.VARDECIMAL, 4, 2)
.buildSchema();
TupleMetadata inputSchema = new SchemaBuilder()
.add("id", MinorType.INT)
.add("dec", MinorType.VARCHAR)
.buildSchema();
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.createConvertersFor(inputSchema);
try {
testFixture.addRow(1, "1234567.89");
fail();
} catch (UserException e) {
// Expected
}
testFixture.build().clear();
}
private static void expect(ConversionType type, ConversionDefn defn) {
assertEquals(type, defn.type);
}
/**
* Test the conversion type for a subset of type pairs.
*/
@Test
public void testBasicConversionType() {
StandardConversions conversions = StandardConversions.builder().build();
TupleMetadata schema = new SchemaBuilder()
.add("ti", MinorType.TINYINT)
.add("si", MinorType.SMALLINT)
.add("int", MinorType.INT)
.add("bi", MinorType.BIGINT)
.add("fl", MinorType.FLOAT4)
.add("db", MinorType.FLOAT8)
.add("dec", MinorType.VARDECIMAL, 10, 0)
.add("str", MinorType.VARCHAR)
.buildSchema();
ColumnMetadata tinyIntCol = schema.metadata("ti");
ColumnMetadata smallIntCol = schema.metadata("si");
ColumnMetadata intCol = schema.metadata("int");
ColumnMetadata bigIntCol = schema.metadata("bi");
ColumnMetadata float4Col = schema.metadata("fl");
ColumnMetadata float8Col = schema.metadata("db");
ColumnMetadata decimalCol = schema.metadata("dec");
ColumnMetadata stringCol = schema.metadata("str");
// TinyInt --> x
expect(ConversionType.NONE, conversions.analyze(tinyIntCol, tinyIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(tinyIntCol, smallIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(tinyIntCol, intCol));
expect(ConversionType.IMPLICIT, conversions.analyze(tinyIntCol, bigIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(tinyIntCol, float4Col));
expect(ConversionType.IMPLICIT, conversions.analyze(tinyIntCol, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(tinyIntCol, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(tinyIntCol, stringCol));
// SmallInt --> x
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(smallIntCol, tinyIntCol));
expect(ConversionType.NONE, conversions.analyze(smallIntCol, smallIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(smallIntCol, intCol));
expect(ConversionType.IMPLICIT, conversions.analyze(smallIntCol, bigIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(smallIntCol, float4Col));
expect(ConversionType.IMPLICIT, conversions.analyze(smallIntCol, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(smallIntCol, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(smallIntCol, stringCol));
// Int --> x
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(intCol, tinyIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(intCol, smallIntCol));
expect(ConversionType.NONE, conversions.analyze(intCol, intCol));
expect(ConversionType.IMPLICIT, conversions.analyze(intCol, bigIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(intCol, float4Col));
expect(ConversionType.IMPLICIT, conversions.analyze(intCol, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(intCol, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(intCol, stringCol));
// BigInt --> x
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(bigIntCol, tinyIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(bigIntCol, smallIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(bigIntCol, intCol));
expect(ConversionType.NONE, conversions.analyze(bigIntCol, bigIntCol));
expect(ConversionType.IMPLICIT, conversions.analyze(bigIntCol, float4Col));
expect(ConversionType.IMPLICIT, conversions.analyze(bigIntCol, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(bigIntCol, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(bigIntCol, stringCol));
// Float4 --> x
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float4Col, tinyIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float4Col, smallIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float4Col, intCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float4Col, bigIntCol));
expect(ConversionType.NONE, conversions.analyze(float4Col, float4Col));
expect(ConversionType.IMPLICIT, conversions.analyze(float4Col, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(float4Col, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(float4Col, stringCol));
// Float8 --> x
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float8Col, tinyIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float8Col, smallIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float8Col, intCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float8Col, bigIntCol));
expect(ConversionType.IMPLICIT_UNSAFE, conversions.analyze(float8Col, float4Col));
expect(ConversionType.NONE, conversions.analyze(float8Col, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(float8Col, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(float8Col, stringCol));
// Decimal --> x
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, tinyIntCol));
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, smallIntCol));
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, intCol));
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, bigIntCol));
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, float4Col));
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, float8Col));
expect(ConversionType.NONE, conversions.analyze(decimalCol, decimalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(decimalCol, stringCol));
// VarChar --> x
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, tinyIntCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, smallIntCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, intCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, bigIntCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, float4Col));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, float8Col));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, decimalCol));
expect(ConversionType.NONE, conversions.analyze(stringCol, stringCol));
}
/**
* Test the specialized types: conversation to/from string.
*/
@Test
public void testSpecialConversionType() {
StandardConversions conversions = StandardConversions.builder().build();
TupleMetadata schema = new SchemaBuilder()
.add("time", MinorType.TIME)
.add("date", MinorType.DATE)
.add("ts", MinorType.TIMESTAMP)
.add("interval", MinorType.INTERVAL)
.add("year", MinorType.INTERVALYEAR)
.add("day", MinorType.INTERVALDAY)
.add("int", MinorType.INT)
.add("bi", MinorType.BIGINT)
.add("str", MinorType.VARCHAR)
.buildSchema();
ColumnMetadata timeCol = schema.metadata("time");
ColumnMetadata dateCol = schema.metadata("date");
ColumnMetadata tsCol = schema.metadata("ts");
ColumnMetadata intervalCol = schema.metadata("interval");
ColumnMetadata yearCol = schema.metadata("year");
ColumnMetadata dayCol = schema.metadata("day");
ColumnMetadata intCol = schema.metadata("int");
ColumnMetadata bigIntCol = schema.metadata("bi");
ColumnMetadata stringCol = schema.metadata("str");
// TIME
expect(ConversionType.NONE, conversions.analyze(timeCol, timeCol));
expect(ConversionType.EXPLICIT, conversions.analyze(timeCol, stringCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, timeCol));
expect(ConversionType.IMPLICIT, conversions.analyze(intCol, timeCol));
expect(ConversionType.IMPLICIT, conversions.analyze(timeCol, intCol));
// DATE
expect(ConversionType.NONE, conversions.analyze(dateCol, dateCol));
expect(ConversionType.EXPLICIT, conversions.analyze(dateCol, stringCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, dateCol));
expect(ConversionType.IMPLICIT, conversions.analyze(bigIntCol, dateCol));
expect(ConversionType.IMPLICIT, conversions.analyze(dateCol, bigIntCol));
// TIMESTAMP
expect(ConversionType.NONE, conversions.analyze(tsCol, tsCol));
expect(ConversionType.EXPLICIT, conversions.analyze(tsCol, stringCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, tsCol));
expect(ConversionType.IMPLICIT, conversions.analyze(bigIntCol, tsCol));
expect(ConversionType.IMPLICIT, conversions.analyze(tsCol, bigIntCol));
// INTERVAL
expect(ConversionType.NONE, conversions.analyze(intervalCol, intervalCol));
expect(ConversionType.EXPLICIT, conversions.analyze(intervalCol, stringCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, intervalCol));
// INTERVALYEAR
expect(ConversionType.NONE, conversions.analyze(yearCol, yearCol));
expect(ConversionType.EXPLICIT, conversions.analyze(yearCol, stringCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, yearCol));
// INTERVALDAY
expect(ConversionType.NONE, conversions.analyze(dayCol, dayCol));
expect(ConversionType.EXPLICIT, conversions.analyze(dayCol, stringCol));
expect(ConversionType.EXPLICIT, conversions.analyze(stringCol, dayCol));
}
/**
* Test the properties for how to handle blanks on string-to-number
* conversions.
*/
@Test
public void testBlankOptions() {
// Nullable
try {
doTestBlanks(DataMode.OPTIONAL, null, null, null);
} catch (InvalidConversionError e) {
// Expected
}
doTestBlanks(DataMode.OPTIONAL, ColumnMetadata.BLANK_AS_NULL,
null, null);
doTestBlanks(DataMode.OPTIONAL, ColumnMetadata.BLANK_AS_ZERO,
null, 0);
// Non-nullable
doTestBlanks(DataMode.REQUIRED, null, null, 20);
doTestBlanks(DataMode.REQUIRED, ColumnMetadata.BLANK_AS_NULL,
null, 20);
doTestBlanks(DataMode.REQUIRED, ColumnMetadata.BLANK_AS_ZERO,
null, 0);
// Property on column
doTestBlanks(DataMode.REQUIRED, null,
ColumnMetadata.BLANK_AS_NULL, 20);
doTestBlanks(DataMode.REQUIRED, null,
ColumnMetadata.BLANK_AS_ZERO, 0);
// Properties on both: column takes precedence
doTestBlanks(DataMode.REQUIRED, ColumnMetadata.BLANK_AS_ZERO,
ColumnMetadata.BLANK_AS_NULL, 20);
doTestBlanks(DataMode.REQUIRED, ColumnMetadata.BLANK_AS_NULL,
ColumnMetadata.BLANK_AS_ZERO, 0);
}
private void doTestBlanks(DataMode mode, String frameworkOption, String colOption, Integer value) {
TupleMetadata outputSchema = new SchemaBuilder()
.add("col", MinorType.INT, mode)
.buildSchema();
ColumnMetadata colSchema = outputSchema.metadata("col");
colSchema.setProperty(ColumnMetadata.DEFAULT_VALUE_PROP, "20");
TupleMetadata inputSchema = new SchemaBuilder()
.addNullable("col", MinorType.VARCHAR)
.buildSchema();
if (colOption != null) {
colSchema = inputSchema.metadata("col");
colSchema.setProperty(ColumnMetadata.BLANK_AS_PROP, colOption);
}
Map<String, String> props = null;
if (frameworkOption != null) {
props = new HashMap<>();
props.put(ColumnMetadata.BLANK_AS_PROP, frameworkOption);
}
ConversionTestFixture testFixture = new ConversionTestFixture(fixture.allocator(), outputSchema);
testFixture.withProperties(props);
testFixture.createConvertersFor(inputSchema);
try {
testFixture
.addSingleCol("")
.addSingleCol(" ")
.addSingleCol("10")
.addSingleCol(" 11 ");
}
catch (Exception e) {
testFixture.build().clear();
throw e;
}
RowSet actual = testFixture.build();
final SingleRowSet expected = fixture.rowSetBuilder(outputSchema)
.addSingleCol(value)
.addSingleCol(value)
.addSingleCol(10)
.addSingleCol(11)
.build();
RowSetUtilities.verify(expected, actual);
}
}