diff --git a/sql/src/main/java/io/deephaven/sql/TypeAdapter.java b/sql/src/main/java/io/deephaven/sql/TypeAdapter.java index da775daf69f..644bb9b1a73 100644 --- a/sql/src/main/java/io/deephaven/sql/TypeAdapter.java +++ b/sql/src/main/java/io/deephaven/sql/TypeAdapter.java @@ -117,13 +117,13 @@ public RelDataType visit(InstantType instantType) { @Override public RelDataType visit(ArrayType arrayType) { // SQLTODO(array-type) - throw new UnsupportedOperationException("SQLTODO(array-type)"); + return typeFactory.createJavaType(SqlTodoArrayType.class); } @Override public RelDataType visit(CustomType customType) { // SQLTODO(custom-type) - throw new UnsupportedOperationException("SQLTODO(custom-type)"); + return typeFactory.createJavaType(SqlTodoCustomType.class); } private RelDataType create(SqlTypeName typeName) { @@ -133,4 +133,19 @@ private RelDataType create(SqlTypeName typeName) { private RelDataType nullable(RelDataType relDataType) { return typeFactory.createTypeWithNullability(relDataType, true); } + + // We currently need our type parsing to always succeed; right now, we implicitly inherit the user's scope, and if + // they happen to have a table with column of CustomType or ArrayType, we need to have that not immediately fail. + // Firstly, the user might not even be referencing that table / column. Secondly, the user might just be passing + // the column through unchanged, in which case Calcite is happy to create the plan. + // See https://github.com/deephaven/deephaven-core/issues/5443 for more context. + + static class SqlTodoType { + } + + static class SqlTodoArrayType extends SqlTodoType { + } + + static class SqlTodoCustomType extends SqlTodoType { + } } diff --git a/sql/src/test/java/io/deephaven/sql/SqlAdapterTest.java b/sql/src/test/java/io/deephaven/sql/SqlAdapterTest.java index 752bab3a6ea..aace3947804 100644 --- a/sql/src/test/java/io/deephaven/sql/SqlAdapterTest.java +++ b/sql/src/test/java/io/deephaven/sql/SqlAdapterTest.java @@ -8,6 +8,8 @@ import io.deephaven.qst.table.TableHeader; import io.deephaven.qst.table.TableSpec; import io.deephaven.qst.table.TicketTable; +import io.deephaven.qst.type.Type; +import org.apache.calcite.runtime.CalciteContextException; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -17,6 +19,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; public class SqlAdapterTest { @@ -45,6 +48,14 @@ public class SqlAdapterTest { private static final TableHeader TIME2 = TableHeader.of(ColumnHeader.ofInstant("Time2")); + interface MyCustomType { + + } + + private static final TableHeader CUSTOM = TableHeader.of( + ColumnHeader.of("Foo", Type.longType()), + ColumnHeader.of("Bar", Type.ofCustom(MyCustomType.class))); + @Test void sql1() throws IOException, URISyntaxException { final Scope scope = scope( @@ -270,10 +281,46 @@ void sql35() throws IOException, URISyntaxException { check(scope, 35); } + @Test + void sql36() throws IOException, URISyntaxException { + final Scope scope = scope("custom", CUSTOM); + check(scope, 36); + } + + @Test + void sql37() throws IOException, URISyntaxException { + final Scope scope = scope("custom", CUSTOM); + check(scope, 37); + } + + @Test + void sql38() throws IOException, URISyntaxException { + final Scope scope = scope("custom", CUSTOM); + check(scope, 38); + } + + @Test + void sql39() throws IOException, URISyntaxException { + final Scope scope = scope("custom", CUSTOM); + checkError(scope, 39, CalciteContextException.class, + "Cannot apply '+' to arguments of type ' + '"); + } + private static void check(Scope scope, int index) throws IOException, URISyntaxException { check(scope, String.format("query-%d.sql", index), String.format("qst-%d.dot", index)); } + private static void checkError(Scope scope, int index, Class clazz, String messagePart) + throws IOException, URISyntaxException { + try { + SqlAdapter.parseSql(read(String.format("query-%d.sql", index)), scope); + failBecauseExceptionWasNotThrown(clazz); + } catch (Throwable t) { + assertThat(t).isInstanceOf(clazz); + assertThat(t).hasMessageContaining(messagePart); + } + } + private static void check(Scope scope, String queryResource, String expectedResource) throws IOException, URISyntaxException { checkSql(expectedResource, read(queryResource), scope); diff --git a/sql/src/test/resources/io/deephaven/sql/qst-36.dot b/sql/src/test/resources/io/deephaven/sql/qst-36.dot new file mode 100644 index 00000000000..487b9b03ce5 --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/qst-36.dot @@ -0,0 +1,7 @@ +digraph { +"op_0" ["label"="ticketTable(scan/custom)"] +"op_1" ["label"="view(__p_0_0=Foo,__p_0_1=Bar)"] +"op_2" ["label"="view(Foo=__p_0_0,Bar=__p_0_1)"] +"op_1" -> "op_0" +"op_2" -> "op_1" +} \ No newline at end of file diff --git a/sql/src/test/resources/io/deephaven/sql/qst-37.dot b/sql/src/test/resources/io/deephaven/sql/qst-37.dot new file mode 100644 index 00000000000..88dfc495c6a --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/qst-37.dot @@ -0,0 +1,9 @@ +digraph { +"op_0" ["label"="ticketTable(scan/custom)"] +"op_1" ["label"="view(__p_0_0=Foo,__p_0_1=Bar)"] +"op_2" ["label"="where(!isNull(__p_0_1))"] +"op_3" ["label"="view(Foo=__p_0_0,Bar=__p_0_1)"] +"op_1" -> "op_0" +"op_2" -> "op_1" +"op_3" -> "op_2" +} \ No newline at end of file diff --git a/sql/src/test/resources/io/deephaven/sql/qst-38.dot b/sql/src/test/resources/io/deephaven/sql/qst-38.dot new file mode 100644 index 00000000000..b772a0b58b7 --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/qst-38.dot @@ -0,0 +1,9 @@ +digraph { +"op_0" ["label"="ticketTable(scan/custom)"] +"op_1" ["label"="view(__p_1_0=Foo,__p_1_1=Bar)"] +"op_2" ["label"="view(Foo=__p_1_0,Bar=__p_1_1)"] +"op_3" ["label"="sort([ASCENDING(Bar)])"] +"op_1" -> "op_0" +"op_2" -> "op_1" +"op_3" -> "op_2" +} \ No newline at end of file diff --git a/sql/src/test/resources/io/deephaven/sql/query-36.sql b/sql/src/test/resources/io/deephaven/sql/query-36.sql new file mode 100644 index 00000000000..d9371991550 --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/query-36.sql @@ -0,0 +1,4 @@ +SELECT + * +FROM + custom \ No newline at end of file diff --git a/sql/src/test/resources/io/deephaven/sql/query-37.sql b/sql/src/test/resources/io/deephaven/sql/query-37.sql new file mode 100644 index 00000000000..a334626724c --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/query-37.sql @@ -0,0 +1,6 @@ +SELECT + * +FROM + custom +WHERE + Bar is not NULL diff --git a/sql/src/test/resources/io/deephaven/sql/query-38.sql b/sql/src/test/resources/io/deephaven/sql/query-38.sql new file mode 100644 index 00000000000..f10d0a6aa7a --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/query-38.sql @@ -0,0 +1,6 @@ +SELECT + * +FROM + custom +ORDER BY + Bar diff --git a/sql/src/test/resources/io/deephaven/sql/query-39.sql b/sql/src/test/resources/io/deephaven/sql/query-39.sql new file mode 100644 index 00000000000..6e995cc66f0 --- /dev/null +++ b/sql/src/test/resources/io/deephaven/sql/query-39.sql @@ -0,0 +1,4 @@ +SELECT + Bar + 1 +FROM + custom