diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java index 7b69e2c3edc..b01b6ad9511 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java +++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java @@ -18,6 +18,7 @@ import org.apache.calcite.DataContext; import org.apache.calcite.DataContexts; +import org.apache.calcite.adapter.java.AbstractQueryableTable; import org.apache.calcite.adapter.java.JavaTypeFactory; import org.apache.calcite.avatica.AvaticaConnection; import org.apache.calcite.avatica.AvaticaFactory; @@ -36,6 +37,7 @@ import org.apache.calcite.linq4j.BaseQueryable; import org.apache.calcite.linq4j.Enumerable; import org.apache.calcite.linq4j.Enumerator; +import org.apache.calcite.linq4j.Linq4j; import org.apache.calcite.linq4j.Ord; import org.apache.calcite.linq4j.QueryProvider; import org.apache.calcite.linq4j.Queryable; @@ -46,6 +48,8 @@ import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.prepare.CalciteCatalogReader; import org.apache.calcite.rel.type.DelegatingTypeSystem; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rel.type.TimeFrameSet; import org.apache.calcite.rel.type.TimeFrames; @@ -76,6 +80,7 @@ import java.lang.reflect.Type; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -149,6 +154,10 @@ protected CalciteConnectionImpl(Driver driver, AvaticaFactory factory, requireNonNull(rootSchema != null ? rootSchema : CalciteSchema.createRootSchema(true)); + // Add dual table metadata when isSupportedDualTable return true + if (cfg.conformance().isSupportedDualTable()) { + this.rootSchema.add("DUAL", new DualTable(String.class)); + } checkArgument(this.rootSchema.isRoot(), "must be root schema"); this.properties.put(InternalProperty.CASE_SENSITIVE, cfg.caseSensitive()); this.properties.put(InternalProperty.UNQUOTED_CASING, cfg.unquotedCasing()); @@ -621,4 +630,25 @@ static class CalciteServerStatementImpl } } + /** Implementation of {@link AbstractQueryableTable} for dual table. */ + private static class DualTable extends AbstractQueryableTable { + + DualTable(Class clazz) { + super(clazz); + } + + @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { + return typeFactory.createStructType( + // Dual table has one column DUMMY, and defined to be VARCHAR2(1) + Collections.singletonList(typeFactory.createJavaType(String.class)), + Collections.singletonList("DUMMY")); + } + + @SuppressWarnings("unchecked") + @Override public Queryable asQueryable(QueryProvider queryProvider, + SchemaPlus schema, String tableName) { + // Dual table contains one row with a value X + return Linq4j.asEnumerable(Collections.singletonList("X")).asQueryable(); + } + } } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java index 3205a48b409..ce750242fc5 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java @@ -33,6 +33,10 @@ public abstract class SqlAbstractConformance implements SqlConformance { return SqlConformanceEnum.DEFAULT.allowCharLiteralAlias(); } + @Override public boolean isSupportedDualTable() { + return SqlConformanceEnum.DEFAULT.isSupportedDualTable(); + } + @Override public boolean isGroupByAlias() { return SqlConformanceEnum.DEFAULT.isGroupByAlias(); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java index b8501f02f14..2517e4577b8 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java @@ -177,6 +177,21 @@ public interface SqlConformance { */ boolean isSortByAliasObscures(); + /** + * Whether this dialect supports dual table. + * + *

For example, + * + *

SELECT 1 + 1 FROM DUAL
+ * + *

Among the built-in conformance levels, true in + * {@link SqlConformanceEnum#MYSQL_5}, + * {@link SqlConformanceEnum#ORACLE_10}, + * {@link SqlConformanceEnum#ORACLE_12}, + * false otherwise. + */ + boolean isSupportedDualTable(); + /** * Whether {@code FROM} clause is required in a {@code SELECT} statement. * diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java index a610f19f169..66358f96850 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java @@ -103,6 +103,16 @@ public enum SqlConformanceEnum implements SqlConformance { } } + @Override public boolean isSupportedDualTable() { + switch (this) { + case MYSQL_5: + case ORACLE_10: + case ORACLE_12: + return true; + default: + return false; + } + } @Override public boolean isGroupByAlias() { switch (this) { diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java index c8e2f7cdd2e..5cac7afca71 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java @@ -39,6 +39,10 @@ protected SqlDelegatingConformance(SqlConformance delegate) { return delegate.allowCharLiteralAlias(); } + @Override public boolean isSupportedDualTable() { + return delegate.isSupportedDualTable(); + } + @Override public boolean isGroupByAlias() { return delegate.isGroupByAlias(); } diff --git a/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java b/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java index e7a3c636ab5..be327c89a83 100644 --- a/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java +++ b/core/src/test/java/org/apache/calcite/test/CoreQuidemTest.java @@ -121,6 +121,15 @@ public static void main(String[] args) throws Exception { SqlConformanceEnum.MYSQL_5) .with(CalciteAssert.Config.SCOTT) .connect(); + case "scott-oracle": + // Same as "scott", but uses Oracle conformance. + return CalciteAssert.that() + .with(CalciteConnectionProperty.PARSER_FACTORY, + ExtensionDdlExecutor.class.getName() + "#PARSER_FACTORY") + .with(CalciteConnectionProperty.CONFORMANCE, + SqlConformanceEnum.ORACLE_10) + .with(CalciteAssert.Config.SCOTT) + .connect(); case "steelwheels": return CalciteAssert.that() .with(CalciteConnectionProperty.PARSER_FACTORY, diff --git a/core/src/test/resources/sql/dummy.iq b/core/src/test/resources/sql/dummy.iq index 166e1b06d87..7647c72dd6a 100644 --- a/core/src/test/resources/sql/dummy.iq +++ b/core/src/test/resources/sql/dummy.iq @@ -20,4 +20,58 @@ values 1; EXPR$0 1 !ok + +# [CALCITE-6678] Support dual table query (enabled in MySQL, Oracle libraries) +!set outputformat mysql +!use scott-mysql + +# MySQL supports users to specify the dual table, and also supports users not to specify the dual table. +SELECT 1 + 1 FROM DUAL; ++--------+ +| EXPR$0 | ++--------+ +| 2 | ++--------+ +(1 row) + +!ok + +SELECT 1 + 1; ++--------+ +| EXPR$0 | ++--------+ +| 2 | ++--------+ +(1 row) + +!ok + +# Oracle supports users to specify the dual table, but not supports users not to specify the dual table. +!use scott-oracle + +SELECT 1 + 1 FROM DUAL; ++--------+ +| EXPR$0 | ++--------+ +| 2 | ++--------+ +(1 row) + +!ok + +SELECT 1 + 1; +java.sql.SQLException: Error while executing SQL "SELECT 1 + 1": From line 1, column 1 to line 1, column 12: SELECT must have a FROM clause + +!error + +SELECT * FROM DUAL; ++-------+ +| DUMMY | ++-------+ +| X | ++-------+ +(1 row) + +!ok + # End dummy.iq