From 8796fd1e13f07ac84df1c466dce83456dfa22950 Mon Sep 17 00:00:00 2001 From: dbarclay Date: Sun, 22 Mar 2015 13:05:38 -0700 Subject: [PATCH] DRILL-2461: Fix: INTERVAL in view makes INFORMATION_SCHEMA.COLUMN fail. - Created test. - Handled INTERVAL data types in View: - Added data to FieldType. - Switch calling of createSqlType to createSqlIntervalType for interval types --- .../org/apache/drill/exec/dotdrill/View.java | 160 ++++++++++++++---- ...ll2461IntervalsBreakInfoSchemaBugTest.java | 91 ++++++++++ 2 files changed, 222 insertions(+), 29 deletions(-) create mode 100644 exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java b/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java index a7b496be148..c5c62d85704 100644 --- a/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java +++ b/exec/java-exec/src/main/java/org/apache/drill/exec/dotdrill/View.java @@ -22,9 +22,13 @@ import org.apache.drill.exec.planner.StarColumnHelper; import org.apache.drill.exec.planner.types.RelDataTypeDrillImpl; import org.apache.drill.exec.planner.types.RelDataTypeHolder; + import org.eigenbase.reltype.RelDataType; import org.eigenbase.reltype.RelDataTypeFactory; import org.eigenbase.reltype.RelDataTypeField; +import org.eigenbase.sql.SqlIntervalQualifier; +import org.eigenbase.sql.parser.SqlParserPos; +import org.eigenbase.sql.type.SqlTypeFamily; import org.eigenbase.sql.type.SqlTypeName; import com.fasterxml.jackson.annotation.JsonCreator; @@ -48,36 +52,50 @@ public class View { @JsonInclude(Include.NON_NULL) public static class FieldType { - public final String name; - public final SqlTypeName type; - public final Integer precision; - public final Integer scale; - public final Boolean isNullable; + + private final String name; + private final SqlTypeName type; + private final Integer precision; + private final Integer scale; + private SqlIntervalQualifier intervalQualifier; + private final Boolean isNullable; + @JsonCreator public FieldType( - @JsonProperty("name") String name, - @JsonProperty("type") SqlTypeName type, - @JsonProperty("precision") Integer precision, - @JsonProperty("scale") Integer scale, - @JsonProperty("isNullable") Boolean isNullable){ + @JsonProperty("name") String name, + @JsonProperty("type") SqlTypeName type, + @JsonProperty("precision") Integer precision, + @JsonProperty("scale") Integer scale, + @JsonProperty("startUnit") SqlIntervalQualifier.TimeUnit startUnit, + @JsonProperty("endUnit") SqlIntervalQualifier.TimeUnit endUnit, + @JsonProperty("fractionalSecondPrecision") Integer fractionalSecondPrecision, + @JsonProperty("isNullable") Boolean isNullable) { this.name = name; this.type = type; this.precision = precision; this.scale = scale; + this.intervalQualifier = + null == startUnit + ? null + : new SqlIntervalQualifier( + startUnit, precision, endUnit, fractionalSecondPrecision, SqlParserPos.ZERO ); - // Property "isNullable" is not part of the initial view definition and added in DRILL-2342. If the - // default value is null, consider it as "true". It is safe to default to "nullable" than "required" type. - this.isNullable = (isNullable == null) ? true : isNullable; + // Property "isNullable" is not part of the initial view definition and + // was added in DRILL-2342. If the default value is null, consider it as + // "true". It is safe to default to "nullable" than "required" type. + this.isNullable = isNullable == null ? true : isNullable; } - public FieldType(String name, RelDataType dataType){ + public FieldType(String name, RelDataType dataType) { this.name = name; this.type = dataType.getSqlTypeName(); + Integer p = null; Integer s = null; + Integer fractionalSecondPrecision = null; - switch(dataType.getSqlTypeName()){ + switch (dataType.getSqlTypeName()) { case CHAR: case BINARY: case VARBINARY: @@ -88,19 +106,100 @@ public FieldType(String name, RelDataType dataType){ p = dataType.getPrecision(); s = dataType.getScale(); break; + case INTERVAL_YEAR_MONTH: + case INTERVAL_DAY_TIME: + p = dataType.getIntervalQualifier().getStartPrecision(); + default: + break; } this.precision = p; this.scale = s; + this.intervalQualifier = dataType.getIntervalQualifier(); this.isNullable = dataType.isNullable(); } + + /** + * Gets the name of this field. + */ + public String getName() { + return name; + } + + /** + * Gets the data type of this field. + * (Data type only; not full datatype descriptor.) + */ + public SqlTypeName getType() { + return type; + } + + /** + * Gets the precision of the data type descriptor of this field. + * The precision is the precision for a numeric type, the length for a + * string type, or the start unit precision for an interval type. + * */ + public Integer getPrecision() { + return precision; + } + + /** + * Gets the numeric scale of the data type descriptor of this field, + * for numeric types. + */ + public Integer getScale() { + return scale; + } + + /** + * Gets the interval type qualifier of the interval data type descriptor of + * this field (iff interval type). */ + @JsonIgnore + public SqlIntervalQualifier getIntervalQualifier() { + return intervalQualifier; + } + + /** + * Gets the time range start unit of the type qualifier of the interval data + * type descriptor of this field (iff interval type). + */ + public SqlIntervalQualifier.TimeUnit getStartUnit() { + return null == intervalQualifier ? null : intervalQualifier.getStartUnit(); + } + + /** + * Gets the time range end unit of the type qualifier of the interval data + * type descriptor of this field (iff interval type). + */ + public SqlIntervalQualifier.TimeUnit getEndUnit() { + return null == intervalQualifier ? null : intervalQualifier.getEndUnit(); + } + + /** + * Gets the fractional second precision of the type qualifier of the interval + * data type descriptor of this field (iff interval type). + * Gets the interval type descriptor's fractional second precision + * (iff interval type). + */ + public Integer getFractionalSecondPrecision() { + return null == intervalQualifier ? null : intervalQualifier.getFractionalSecondPrecision(); + } + + /** + * Gets the nullability of the data type desription of this field. + */ + public Boolean getIsNullable() { + return isNullable; + } + } - public View(String name, String sql, RelDataType rowType, List workspaceSchemaPath){ + + public View(String name, String sql, RelDataType rowType, List workspaceSchemaPath) { this.name = name; this.sql = sql; fields = Lists.newArrayList(); - for(RelDataTypeField f : rowType.getFieldList()){ + for (RelDataTypeField f : rowType.getFieldList()) { fields.add(new FieldType(f.getName(), f.getType())); } this.workspaceSchemaPath = @@ -119,28 +218,31 @@ public View(@JsonProperty("name") String name, workspaceSchemaPath == null ? ImmutableList.of() : ImmutableList.copyOf(workspaceSchemaPath); } - public RelDataType getRowType(RelDataTypeFactory factory){ + public RelDataType getRowType(RelDataTypeFactory factory) { // if there are no fields defined, this is a dynamic view. - if(isDynamic()){ + if (isDynamic()) { return new RelDataTypeDrillImpl(new RelDataTypeHolder(), factory); } List types = Lists.newArrayList(); List names = Lists.newArrayList(); - for(FieldType field : fields){ - names.add(field.name); + for (FieldType field : fields) { + names.add(field.getName()); RelDataType type; - if(field.precision == null && field.scale == null){ - type = factory.createSqlType(field.type); - }else if(field.precision != null && field.scale == null){ - type = factory.createSqlType(field.type, field.precision); - }else{ - type = factory.createSqlType(field.type, field.precision, field.scale); + if ( SqlTypeFamily.INTERVAL_YEAR_MONTH == field.getType().getFamily() + || SqlTypeFamily.INTERVAL_DAY_TIME == field.getType().getFamily() ) { + type = factory.createSqlIntervalType( field.getIntervalQualifier() ); + } else if (field.getPrecision() == null && field.getScale() == null) { + type = factory.createSqlType(field.getType()); + } else if (field.getPrecision() != null && field.getScale() == null) { + type = factory.createSqlType(field.getType(), field.getPrecision()); + } else { + type = factory.createSqlType(field.getType(), field.getPrecision(), field.getScale()); } - if (field.isNullable) { + if (field.getIsNullable()) { types.add(factory.createTypeWithNullability(type, true)); } else { types.add(type); @@ -157,7 +259,7 @@ public boolean isDynamic(){ @JsonIgnore public boolean hasStar() { for (FieldType field : fields) { - if (StarColumnHelper.isNonPrefixedStarColumn(field.name)) { + if (StarColumnHelper.isNonPrefixedStarColumn(field.getName())) { return true; } } diff --git a/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java new file mode 100644 index 00000000000..fce7923be96 --- /dev/null +++ b/exec/jdbc/src/test/java/org/apache/drill/jdbc/test/Drill2461IntervalsBreakInfoSchemaBugTest.java @@ -0,0 +1,91 @@ +/** + * 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.jdbc.test; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.*; + +import org.apache.drill.common.util.TestTools; +import org.apache.drill.jdbc.Driver; +import org.apache.drill.jdbc.JdbcTest; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.SQLException; + + +public class Drill2461IntervalsBreakInfoSchemaBugTest extends JdbcTest { + + private static final String VIEW_NAME = + Drill2461IntervalsBreakInfoSchemaBugTest.class.getSimpleName() + "_View"; + + private static Connection connection; + + + @BeforeClass + public static void setUpConnection() throws Exception { + connection = connect( "jdbc:drill:zk=local" ); + } + + @AfterClass + public static void tearDownConnection() throws Exception { + connection.close(); + } + + + @Test + public void testIntervalInViewDoesntCrashInfoSchema() throws Exception { + final Statement stmt = connection.createStatement(); + ResultSet util; + + // Create a view using an INTERVAL type: + util = stmt.executeQuery( "USE dfs.tmp" ); + assert util.next(); + assert util.getBoolean( 1 ) + : "Error setting schema to dfs.tmp: " + util.getString( 2 ); + util = stmt.executeQuery( + "CREATE OR REPLACE VIEW " + VIEW_NAME + " AS " + + "\n SELECT CAST( NULL AS INTERVAL HOUR(4) TO MINUTE ) AS optINTERVAL_HM " + + "\n FROM INFORMATION_SCHEMA.CATALOGS " + + "\n LIMIT 1 " ); + assert util.next(); + assert util.getBoolean( 1 ) + : "Error creating temporary test-columns view " + VIEW_NAME + ": " + + util.getString( 2 ); + + // Test whether query INFORMATION_SCHEMA.COLUMNS works (doesn't crash): + util = stmt.executeQuery( "SELECT * FROM INFORMATION_SCHEMA.COLUMNS" ); + assert util.next(); + + // Clean up the test view: + util = connection.createStatement().executeQuery( "DROP VIEW " + VIEW_NAME ); + assert util.next(); + assert util.getBoolean( 1 ) + : "Error dropping temporary test-columns view " + VIEW_NAME + ": " + + util.getString( 2 ); + } + +}