Repository: trinodb/trino (Java)
Date: 2019-12-07 12:31:44+00:00
Task type: Bug Fix

Issue:
SHOW CREATE TABLE for row type match reserved SQL keywords fails
Steps to reproduce: 
```sql
presto> create table memory.default.test (c1 row("from" integer));
CREATE TABLE
presto> show columns in memory.default.test;
 Column |       Type        | Extra | Comment 
--------+-------------------+-------+---------
 c1     | row(from integer) |       |         
(1 row)

presto> select * from memory.default.test;
 c1 
----
(0 rows)

presto> show create table memory.default.test;
Query 20191128_083045_00003_iysy5 failed: line 1:5: mismatched input 'from'. Expecting: <identifier>, <type>
io.prestosql.sql.parser.ParsingException: line 1:5: mismatched input 'from'. Expecting: <identifier>, <type>
        at io.prestosql.sql.parser.ErrorHandler.syntaxError(ErrorHandler.java:107)
        at org.antlr.v4.runtime.ProxyErrorListener.syntaxError(ProxyErrorListener.java:41)
        at org.antlr.v4.runtime.Parser.notifyErrorListeners(Parser.java:544)
        at org.antlr.v4.runtime.DefaultErrorStrategy.reportNoViableAlternative(DefaultErrorStrategy.java:310)
        at org.antlr.v4.runtime.DefaultErrorStrategy.reportError(DefaultErrorStrategy.java:136)
        at io.prestosql.sql.parser.SqlBaseParser.type(SqlBaseParser.java:10217)
        at io.prestosql.sql.parser.SqlBaseParser.standaloneType(SqlBaseParser.java:385)
        at io.prestosql.sql.parser.SqlParser.invokeParser(SqlParser.java:164)
        at io.prestosql.sql.parser.SqlParser.createType(SqlParser.java:114)
        at io.prestosql.sql.analyzer.TypeSignatureTranslator.toSqlType(TypeSignatureTranslator.java:62)
        at io.prestosql.sql.rewrite.ShowQueriesRewrite$Visitor.lambda$visitShowCreate$8(ShowQueriesRewrite.java:469)
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
        at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
        at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
        at io.prestosql.sql.rewrite.ShowQueriesRewrite$Visitor.visitShowCreate(ShowQueriesRewrite.java:471)
        at io.prestosql.sql.rewrite.ShowQueriesRewrite$Visitor.visitShowCreate(ShowQueriesRewrite.java:156)
        at io.prestosql.sql.tree.ShowCreate.accept(ShowCreate.java:67)
        at io.prestosql.sql.tree.AstVisitor.process(AstVisitor.java:27)
        at io.prestosql.sql.rewrite.ShowQueriesRewrite.rewrite(ShowQueriesRewrite.java:153)
        at io.prestosql.sql.rewrite.StatementRewrite.rewrite(StatementRewrite.java:57)
        at io.prestosql.sql.analyzer.Analyzer.analyze(Analyzer.java:80)
        at io.prestosql.sql.analyzer.Analyzer.analyze(Analyzer.java:75)
        at io.prestosql.execution.SqlQueryExecution.analyze(SqlQueryExecution.java:219)
        at io.prestosql.execution.SqlQueryExecution.<init>(SqlQueryExecution.java:178)
        at io.prestosql.execution.SqlQueryExecution.<init>(SqlQueryExecution.java:95)
        at io.prestosql.execution.SqlQueryExecution$SqlQueryExecutionFactory.createQueryExecution(SqlQueryExecution.java:715)
        at io.prestosql.dispatcher.LocalDispatchQueryFactory.lambda$createDispatchQuery$0(LocalDispatchQueryFactory.java:119)
        at io.prestosql.$gen.Presto_null__testversion____20191128_082928_2.call(Unknown Source)
        at com.google.common.util.concurrent.TrustedListenableFutureTask$TrustedFutureInterruptibleTask.runInterruptibly(TrustedListenableFutureTask.java:125)
        at com.google.common.util.concurrent.InterruptibleTask.run(InterruptibleTask.java:57)
        at com.google.common.util.concurrent.TrustedListenableFutureTask.run(TrustedListenableFutureTask.java:78)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.antlr.v4.runtime.NoViableAltException
        at org.antlr.v4.runtime.atn.ParserATNSimulator.noViableAlt(ParserATNSimulator.java:2028)
        at org.antlr.v4.runtime.atn.ParserATNSimulator.execATN(ParserATNSimulator.java:467)
        at org.antlr.v4.runtime.atn.ParserATNSimulator.adaptivePredict(ParserATNSimulator.java:393)
        at io.prestosql.sql.parser.SqlBaseParser.type(SqlBaseParser.java:9903)
        ... 33 more
show create table memory.default.test

```

Ref: https://github.com/prestosql/presto/issues/1962


Issue discussion:

Solution patch:
diff --git a/presto-main/pom.xml b/presto-main/pom.xml
index bfda52c0ec51..0eb7e5b831d3 100644
--- a/presto-main/pom.xml
+++ b/presto-main/pom.xml
@@ -318,6 +318,7 @@
         <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
+            <version>3.13.2</version>
             <scope>provided</scope>
         </dependency>
 
diff --git a/presto-main/src/main/java/io/prestosql/sql/analyzer/TypeSignatureTranslator.java b/presto-main/src/main/java/io/prestosql/sql/analyzer/TypeSignatureTranslator.java
index c6ca13888740..ce9f822085b5 100644
--- a/presto-main/src/main/java/io/prestosql/sql/analyzer/TypeSignatureTranslator.java
+++ b/presto-main/src/main/java/io/prestosql/sql/analyzer/TypeSignatureTranslator.java
@@ -21,7 +21,6 @@
 import io.prestosql.spi.type.Type;
 import io.prestosql.spi.type.TypeSignature;
 import io.prestosql.spi.type.TypeSignatureParameter;
-import io.prestosql.sql.parser.SqlParser;
 import io.prestosql.sql.tree.DataType;
 import io.prestosql.sql.tree.DataTypeParameter;
 import io.prestosql.sql.tree.DateTimeDataType;
@@ -31,13 +30,17 @@
 import io.prestosql.sql.tree.NumericParameter;
 import io.prestosql.sql.tree.RowDataType;
 import io.prestosql.sql.tree.TypeParameter;
+import org.assertj.core.util.VisibleForTesting;
 
 import java.util.List;
 import java.util.Locale;
+import java.util.Optional;
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static io.prestosql.spi.StandardErrorCode.NOT_SUPPORTED;
 import static io.prestosql.spi.StandardErrorCode.TYPE_MISMATCH;
+import static io.prestosql.spi.type.StandardTypes.INTERVAL_DAY_TO_SECOND;
+import static io.prestosql.spi.type.StandardTypes.INTERVAL_YEAR_TO_MONTH;
 import static io.prestosql.spi.type.TimeType.TIME;
 import static io.prestosql.spi.type.TimeWithTimeZoneType.TIME_WITH_TIME_ZONE;
 import static io.prestosql.spi.type.TimestampType.TIMESTAMP;
@@ -45,6 +48,7 @@
 import static io.prestosql.spi.type.TypeSignatureParameter.namedTypeParameter;
 import static io.prestosql.spi.type.TypeSignatureParameter.numericParameter;
 import static io.prestosql.spi.type.TypeSignatureParameter.typeParameter;
+import static io.prestosql.spi.type.VarcharType.UNBOUNDED_LENGTH;
 import static io.prestosql.sql.analyzer.SemanticExceptions.semanticException;
 import static io.prestosql.type.IntervalDayTimeType.INTERVAL_DAY_TIME;
 import static io.prestosql.type.IntervalYearMonthType.INTERVAL_YEAR_MONTH;
@@ -52,14 +56,11 @@
 
 public class TypeSignatureTranslator
 {
-    private static final SqlParser PARSER = new SqlParser();
-
     private TypeSignatureTranslator() {}
 
     public static DataType toSqlType(Type type)
     {
-        // TODO: convert Type -> TypeSignature and translate to DataType by walking the tree
-        return PARSER.createType(type.getDisplayName());
+        return toDataType(type.getTypeSignature());
     }
 
     public static TypeSignature toTypeSignature(DataType type)
@@ -162,4 +163,56 @@ private static String canonicalize(Identifier identifier)
 
         return identifier.getValue().toLowerCase(Locale.ENGLISH); // TODO: make this toUpperCase to match standard SQL semantics
     }
+
+    @VisibleForTesting
+    static DataType toDataType(TypeSignature typeSignature)
+    {
+        switch (typeSignature.getBase()) {
+            case INTERVAL_YEAR_TO_MONTH:
+                return new IntervalDayTimeDataType(Optional.empty(), IntervalDayTimeDataType.Field.YEAR, IntervalDayTimeDataType.Field.MONTH);
+            case INTERVAL_DAY_TO_SECOND:
+                return new IntervalDayTimeDataType(Optional.empty(), IntervalDayTimeDataType.Field.DAY, IntervalDayTimeDataType.Field.SECOND);
+            case StandardTypes.TIMESTAMP_WITH_TIME_ZONE:
+                return new DateTimeDataType(Optional.empty(), DateTimeDataType.Type.TIMESTAMP, true, Optional.empty());
+            case StandardTypes.TIMESTAMP:
+                return new DateTimeDataType(Optional.empty(), DateTimeDataType.Type.TIMESTAMP, false, Optional.empty());
+            case StandardTypes.TIME_WITH_TIME_ZONE:
+                return new DateTimeDataType(Optional.empty(), DateTimeDataType.Type.TIME, true, Optional.empty());
+            case StandardTypes.TIME:
+                return new DateTimeDataType(Optional.empty(), DateTimeDataType.Type.TIME, false, Optional.empty());
+            case StandardTypes.ROW:
+                return new RowDataType(
+                        Optional.empty(),
+                        typeSignature.getParameters().stream()
+                                .map(parameter -> new RowDataType.Field(
+                                        Optional.empty(),
+                                        parameter.getNamedTypeSignature().getFieldName().map(fieldName -> new Identifier(fieldName.getName(), false)),
+                                        toDataType(parameter.getNamedTypeSignature().getTypeSignature())))
+                                .collect(toImmutableList()));
+            case StandardTypes.VARCHAR:
+                return new GenericDataType(
+                        Optional.empty(),
+                        new Identifier(typeSignature.getBase(), false),
+                        typeSignature.getParameters().stream()
+                                .filter(parameter -> parameter.getLongLiteral() != UNBOUNDED_LENGTH)
+                                .map(parameter -> new NumericParameter(Optional.empty(), String.valueOf(parameter)))
+                                .collect(toImmutableList()));
+            default:
+                return new GenericDataType(
+                        Optional.empty(),
+                        new Identifier(typeSignature.getBase(), false),
+                        typeSignature.getParameters().stream()
+                                .map(parameter -> {
+                                    switch (parameter.getKind()) {
+                                        case LONG:
+                                            return new NumericParameter(Optional.empty(), String.valueOf(parameter.getLongLiteral()));
+                                        case TYPE:
+                                            return new TypeParameter(toDataType(parameter.getTypeSignature()));
+                                        default:
+                                            throw new UnsupportedOperationException("Unsupported parameter kind");
+                                    }
+                                })
+                                .collect(toImmutableList()));
+        }
+    }
 }
diff --git a/presto-parser/src/main/java/io/prestosql/sql/tree/DateTimeDataType.java b/presto-parser/src/main/java/io/prestosql/sql/tree/DateTimeDataType.java
index 3988760fdb4f..e5ff64f32240 100644
--- a/presto-parser/src/main/java/io/prestosql/sql/tree/DateTimeDataType.java
+++ b/presto-parser/src/main/java/io/prestosql/sql/tree/DateTimeDataType.java
@@ -35,7 +35,12 @@ public enum Type
 
     public DateTimeDataType(NodeLocation location, Type type, boolean withTimeZone, Optional<String> precision)
     {
-        super(Optional.of(location));
+        this(Optional.of(location), type, withTimeZone, precision);
+    }
+
+    public DateTimeDataType(Optional<NodeLocation> location, Type type, boolean withTimeZone, Optional<String> precision)
+    {
+        super(location);
         this.type = requireNonNull(type, "type is null");
         this.withTimeZone = withTimeZone;
         this.precision = requireNonNull(precision, "precision is null");
diff --git a/presto-parser/src/main/java/io/prestosql/sql/tree/GenericDataType.java b/presto-parser/src/main/java/io/prestosql/sql/tree/GenericDataType.java
index 874afce8e9ed..b8a4cdef2f68 100644
--- a/presto-parser/src/main/java/io/prestosql/sql/tree/GenericDataType.java
+++ b/presto-parser/src/main/java/io/prestosql/sql/tree/GenericDataType.java
@@ -34,6 +34,13 @@ public GenericDataType(NodeLocation location, Identifier name, List<DataTypePara
         this.arguments = requireNonNull(arguments, "arguments is null");
     }
 
+    public GenericDataType(Optional<NodeLocation> location, Identifier name, List<DataTypeParameter> arguments)
+    {
+        super(location);
+        this.name = requireNonNull(name, "name is null");
+        this.arguments = requireNonNull(arguments, "arguments is null");
+    }
+
     public Identifier getName()
     {
         return name;
diff --git a/presto-parser/src/main/java/io/prestosql/sql/tree/IntervalDayTimeDataType.java b/presto-parser/src/main/java/io/prestosql/sql/tree/IntervalDayTimeDataType.java
index 82457bf9203e..0eda8408071a 100644
--- a/presto-parser/src/main/java/io/prestosql/sql/tree/IntervalDayTimeDataType.java
+++ b/presto-parser/src/main/java/io/prestosql/sql/tree/IntervalDayTimeDataType.java
@@ -39,7 +39,12 @@ public enum Field
 
     public IntervalDayTimeDataType(NodeLocation location, Field from, Field to)
     {
-        super(Optional.of(location));
+        this(Optional.of(location), from, to);
+    }
+
+    public IntervalDayTimeDataType(Optional<NodeLocation> location, Field from, Field to)
+    {
+        super(location);
         this.from = requireNonNull(from, "from is null");
         this.to = requireNonNull(to, "to is null");
     }
diff --git a/presto-parser/src/main/java/io/prestosql/sql/tree/NumericParameter.java b/presto-parser/src/main/java/io/prestosql/sql/tree/NumericParameter.java
index ae0bee031a55..48007b05689e 100644
--- a/presto-parser/src/main/java/io/prestosql/sql/tree/NumericParameter.java
+++ b/presto-parser/src/main/java/io/prestosql/sql/tree/NumericParameter.java
@@ -28,7 +28,12 @@ public class NumericParameter
 
     public NumericParameter(NodeLocation location, String value)
     {
-        super(Optional.of(location));
+        this(Optional.of(location), value);
+    }
+
+    public NumericParameter(Optional<NodeLocation> location, String value)
+    {
+        super(location);
         this.value = requireNonNull(value, "value is null");
     }
 
diff --git a/presto-parser/src/main/java/io/prestosql/sql/tree/RowDataType.java b/presto-parser/src/main/java/io/prestosql/sql/tree/RowDataType.java
index b8d38c454879..ded8c75a3281 100644
--- a/presto-parser/src/main/java/io/prestosql/sql/tree/RowDataType.java
+++ b/presto-parser/src/main/java/io/prestosql/sql/tree/RowDataType.java
@@ -32,6 +32,12 @@ public RowDataType(NodeLocation location, List<Field> fields)
         this.fields = ImmutableList.copyOf(fields);
     }
 
+    public RowDataType(Optional<NodeLocation> location, List<Field> fields)
+    {
+        super(location);
+        this.fields = ImmutableList.copyOf(fields);
+    }
+
     public List<Field> getFields()
     {
         return fields;
@@ -82,6 +88,14 @@ public Field(NodeLocation location, Optional<Identifier> name, DataType type)
             this.type = requireNonNull(type, "type is null");
         }
 
+        public Field(Optional<NodeLocation> location, Optional<Identifier> name, DataType type)
+        {
+            super(location);
+
+            this.name = requireNonNull(name, "name is null");
+            this.type = requireNonNull(type, "type is null");
+        }
+
         public Optional<Identifier> getName()
         {
             return name;


Test patch:
diff --git a/presto-main/src/test/java/io/prestosql/sql/analyzer/TestTypeSignatureTranslator.java b/presto-main/src/test/java/io/prestosql/sql/analyzer/TestTypeSignatureTranslator.java
new file mode 100644
index 000000000000..8bebc94f1ed0
--- /dev/null
+++ b/presto-main/src/test/java/io/prestosql/sql/analyzer/TestTypeSignatureTranslator.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed 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 io.prestosql.sql.analyzer;
+
+import io.prestosql.sql.parser.SqlParser;
+import io.prestosql.sql.tree.Identifier;
+import org.testng.annotations.Test;
+
+import java.util.Comparator;
+import java.util.Locale;
+
+import static io.prestosql.sql.analyzer.TypeSignatureTranslator.toDataType;
+import static io.prestosql.sql.analyzer.TypeSignatureTranslator.toTypeSignature;
+import static io.prestosql.sql.parser.ParserAssert.type;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestTypeSignatureTranslator
+{
+    private static final SqlParser SQL_PARSER = new SqlParser();
+
+    private void assertRoundTrip(String expression)
+    {
+        assertThat(type(expression))
+                .ignoringLocation()
+                .withComparatorForType(Comparator.comparing(identifier -> identifier.getValue().toLowerCase(Locale.ENGLISH)), Identifier.class)
+                .isEqualTo(toDataType(toTypeSignature(SQL_PARSER.createType(expression))));
+    }
+
+    @Test
+    public void testSimpleTypes()
+    {
+        assertRoundTrip("varchar");
+        assertRoundTrip("BIGINT");
+        assertRoundTrip("DOUBLE");
+        assertRoundTrip("BOOLEAN");
+    }
+
+    @Test
+    public void testDayTimeTypes()
+    {
+        assertRoundTrip("TIMESTAMP");
+        assertRoundTrip("TIMESTAMP WITHOUT TIME ZONE");
+        assertRoundTrip("TIMESTAMP WITH TIME ZONE");
+        assertRoundTrip("TIME");
+        assertRoundTrip("TIME WITHOUT TIME ZONE");
+        assertRoundTrip("TIME WITH TIME ZONE");
+    }
+
+    @Test
+    public void testIntervalTypes()
+    {
+        assertRoundTrip("INTERVAL DAY TO SECOND");
+        assertRoundTrip("INTERVAL YEAR TO MONTH");
+    }
+
+    @Test
+    public void testParametricTypes()
+    {
+        assertRoundTrip("ARRAY(TINYINT)");
+        assertRoundTrip("MAP(BIGINT, SMALLINT)");
+        assertRoundTrip("VARCHAR(123)");
+        assertRoundTrip("DECIMAL(1)");
+        assertRoundTrip("DECIMAL(1, 38)");
+    }
+
+    @Test
+    public void testArray()
+    {
+        assertRoundTrip("foo(42, 55) ARRAY");
+        assertRoundTrip("VARCHAR(7) ARRAY");
+        assertRoundTrip("VARCHAR(7) ARRAY array");
+    }
+
+    @Test
+    public void testRowType()
+    {
+        assertRoundTrip("ROW(a BIGINT, b VARCHAR)");
+        assertRoundTrip("ROW(a BIGINT,b VARCHAR)");
+        assertRoundTrip("ROW(\"a\" BIGINT, \"from\" VARCHAR)");
+    }
+
+    @Test
+    public void testComplexTypes()
+    {
+        assertRoundTrip("ROW(x BIGINT, y DOUBLE PRECISION, z ROW(m array<bigint>,n map<double,varchar>))");
+    }
+}