You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2019/09/24 19:12:23 UTC

[calcite] branch master updated: [CALCITE-3365] Don't require use of JdbcSchema in QuerySqlStatisticProvider (Lindsey Meyer)

This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new 3005ff9  [CALCITE-3365] Don't require use of JdbcSchema in QuerySqlStatisticProvider (Lindsey Meyer)
3005ff9 is described below

commit 3005ff97ddb6173ac339b45affb0b4da5d406345
Author: Lindsey Meyer <la...@users.noreply.github.com>
AuthorDate: Thu Sep 19 10:53:29 2019 -0700

    [CALCITE-3365] Don't require use of JdbcSchema in QuerySqlStatisticProvider (Lindsey Meyer)
    
    Add a test case (Julian Hyde)
    
    Close apache/calcite#1467
---
 .../org/apache/calcite/adapter/jdbc/JdbcTable.java | 10 +++++
 .../statistic/QuerySqlStatisticProvider.java       | 43 ++++++++++---------
 .../calcite/materialize/LatticeSuggesterTest.java  | 31 ++++++++++++++
 .../org/apache/calcite/test/CalciteAssert.java     | 49 ++++++++++++++++++++++
 4 files changed, 111 insertions(+), 22 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
index 6285501..c21d8b0 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcTable.java
@@ -100,6 +100,16 @@ public class JdbcTable extends AbstractQueryableTable
     return jdbcTableType;
   }
 
+  @Override public <C> C unwrap(Class<C> aClass) {
+    if (aClass.isInstance(jdbcSchema.getDataSource())) {
+      return aClass.cast(jdbcSchema.getDataSource());
+    } else if (aClass.isInstance(jdbcSchema.dialect)) {
+      return aClass.cast(jdbcSchema.dialect);
+    } else {
+      return super.unwrap(aClass);
+    }
+  }
+
   public RelDataType getRowType(RelDataTypeFactory typeFactory) {
     if (protoRowType == null) {
       try {
diff --git a/core/src/main/java/org/apache/calcite/statistic/QuerySqlStatisticProvider.java b/core/src/main/java/org/apache/calcite/statistic/QuerySqlStatisticProvider.java
index 2f4af9d..c15426c 100644
--- a/core/src/main/java/org/apache/calcite/statistic/QuerySqlStatisticProvider.java
+++ b/core/src/main/java/org/apache/calcite/statistic/QuerySqlStatisticProvider.java
@@ -16,10 +16,8 @@
  */
 package org.apache.calcite.statistic;
 
-import org.apache.calcite.adapter.jdbc.JdbcRules;
-import org.apache.calcite.adapter.jdbc.JdbcSchema;
-import org.apache.calcite.adapter.jdbc.JdbcTable;
 import org.apache.calcite.materialize.SqlStatisticProvider;
+import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptSchema;
 import org.apache.calcite.plan.RelOptTable;
@@ -82,16 +80,16 @@ public class QuerySqlStatisticProvider implements SqlStatisticProvider {
   }
 
   public double tableCardinality(RelOptTable table) {
-    final JdbcTable jdbcTable = table.unwrap(JdbcTable.class);
-    return withBuilder(jdbcTable.jdbcSchema,
-        (cluster, relOptSchema, jdbcSchema, relBuilder) -> {
+    final SqlDialect dialect = table.unwrap(SqlDialect.class);
+    final DataSource dataSource = table.unwrap(DataSource.class);
+    return withBuilder(
+        (cluster, relOptSchema, relBuilder) -> {
           // Generate:
           //   SELECT COUNT(*) FROM `EMP`
           relBuilder.push(table.toRel(ViewExpanders.simpleContext(cluster)))
               .aggregate(relBuilder.groupKey(), relBuilder.count());
 
-          final String sql = toSql(relBuilder.build(), jdbcSchema.dialect);
-          final DataSource dataSource = jdbcSchema.getDataSource();
+          final String sql = toSql(relBuilder.build(), dialect);
           try (Connection connection = dataSource.getConnection();
                Statement statement = connection.createStatement();
                ResultSet resultSet = statement.executeQuery(sql)) {
@@ -111,9 +109,10 @@ public class QuerySqlStatisticProvider implements SqlStatisticProvider {
 
   public boolean isForeignKey(RelOptTable fromTable, List<Integer> fromColumns,
       RelOptTable toTable, List<Integer> toColumns) {
-    final JdbcTable jdbcTable = fromTable.unwrap(JdbcTable.class);
-    return withBuilder(jdbcTable.jdbcSchema,
-        (cluster, relOptSchema, jdbcSchema, relBuilder) -> {
+    final SqlDialect dialect = fromTable.unwrap(SqlDialect.class);
+    final DataSource dataSource = fromTable.unwrap(DataSource.class);
+    return withBuilder(
+        (cluster, relOptSchema, relBuilder) -> {
           // EMP(DEPTNO) is a foreign key to DEPT(DEPTNO) if the following
           // query returns 0:
           //
@@ -135,8 +134,7 @@ public class QuerySqlStatisticProvider implements SqlStatisticProvider {
               .minus(false, 2)
               .aggregate(relBuilder.groupKey(), relBuilder.count());
 
-          final String sql = toSql(relBuilder.build(), jdbcSchema.dialect);
-          final DataSource dataSource = jdbcTable.jdbcSchema.getDataSource();
+          final String sql = toSql(relBuilder.build(), dialect);
           try (Connection connection = dataSource.getConnection();
                Statement statement = connection.createStatement();
                ResultSet resultSet = statement.executeQuery(sql)) {
@@ -155,9 +153,10 @@ public class QuerySqlStatisticProvider implements SqlStatisticProvider {
   }
 
   public boolean isKey(RelOptTable table, List<Integer> columns) {
-    final JdbcTable jdbcTable = table.unwrap(JdbcTable.class);
-    return withBuilder(jdbcTable.jdbcSchema,
-        (cluster, relOptSchema, jdbcSchema, relBuilder) -> {
+    final SqlDialect dialect = table.unwrap(SqlDialect.class);
+    final DataSource dataSource = table.unwrap(DataSource.class);
+    return withBuilder(
+        (cluster, relOptSchema, relBuilder) -> {
           // The collection of columns ['DEPTNO'] is a key for 'EMP' if the
           // following query returns no rows:
           //
@@ -174,9 +173,8 @@ public class QuerySqlStatisticProvider implements SqlStatisticProvider {
               .filter(
                   relBuilder.call(SqlStdOperatorTable.GREATER_THAN,
                       Util.last(relBuilder.fields()), relBuilder.literal(1)));
-          final String sql = toSql(relBuilder.build(), jdbcSchema.dialect);
+          final String sql = toSql(relBuilder.build(), dialect);
 
-          final DataSource dataSource = jdbcSchema.getDataSource();
           try (Connection connection = dataSource.getConnection();
                Statement statement = connection.createStatement();
                ResultSet resultSet = statement.executeQuery(sql)) {
@@ -201,21 +199,22 @@ public class QuerySqlStatisticProvider implements SqlStatisticProvider {
     return sql;
   }
 
-  private <R> R withBuilder(JdbcSchema jdbcSchema, BuilderAction<R> action) {
+  private <R> R withBuilder(BuilderAction<R> action) {
     return Frameworks.withPlanner(
         (cluster, relOptSchema, rootSchema) -> {
           final RelBuilder relBuilder =
-              JdbcRules.JDBC_BUILDER.create(cluster, relOptSchema);
-          return action.apply(cluster, relOptSchema, jdbcSchema, relBuilder);
+              RelBuilder.proto(Contexts.of()).create(cluster, relOptSchema);
+          return action.apply(cluster, relOptSchema, relBuilder);
         });
   }
 
   /** Performs an action with a {@link RelBuilder}.
    *
    * @param <R> Result type */
+  @FunctionalInterface
   private interface BuilderAction<R> {
     R apply(RelOptCluster cluster, RelOptSchema relOptSchema,
-        JdbcSchema jdbcSchema, RelBuilder relBuilder);
+        RelBuilder relBuilder);
   }
 }
 
diff --git a/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java b/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java
index 8c691f2..c13898b 100644
--- a/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java
+++ b/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java
@@ -676,6 +676,37 @@ public class LatticeSuggesterTest {
             + " [foodmart, customer], +($2, 2):+($0, 1))])"));
   }
 
+  /** Tests that we can run the suggester against non-JDBC schemas.
+   *
+   * <p>{@link org.apache.calcite.test.CalciteAssert.SchemaSpec#FAKE_FOODMART}
+   * is not based on {@link org.apache.calcite.adapter.jdbc.JdbcSchema} or
+   * {@link org.apache.calcite.adapter.jdbc.JdbcTable} but can provide a
+   * {@link javax.sql.DataSource}
+   * and {@link SqlDialect} for executing statistics queries.
+   *
+   * <p>The query has a join, and so we have to execute statistics queries
+   * to deduce the direction of the foreign key.
+   */
+  @Test public void testFoodmartSimpleJoin() throws Exception {
+    checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec.JDBC_FOODMART);
+    checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec.FAKE_FOODMART);
+  }
+
+  private void checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec schemaSpec)
+      throws Exception {
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(Tester.schemaFrom(schemaSpec))
+        .statisticProvider(QuerySqlStatisticProvider.SILENT_CACHING_INSTANCE)
+        .build();
+    final Tester t = new Tester(config);
+    final String q = "select *\n"
+        + "from \"time_by_day\" as \"t\",\n"
+        + " \"sales_fact_1997\" as \"s\"\n"
+        + "where \"s\".\"time_id\" = \"t\".\"time_id\"\n";
+    final String g = "sales_fact_1997 (time_by_day:time_id)";
+    assertThat(t.addQuery(q), isGraphs(g, "[]"));
+  }
+
   /** Creates a matcher that matches query graphs to strings. */
   private BaseMatcher<List<Lattice>> isGraphs(
       String... strings) {
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 201a10e..151102d 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -35,6 +35,8 @@ import org.apache.calcite.model.ModelHandler;
 import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.runtime.CalciteException;
 import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.GeoFunctions;
@@ -42,10 +44,14 @@ import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.TableFunction;
+import org.apache.calcite.schema.Wrapper;
 import org.apache.calcite.schema.impl.AbstractSchema;
+import org.apache.calcite.schema.impl.AbstractTable;
 import org.apache.calcite.schema.impl.TableFunctionImpl;
 import org.apache.calcite.schema.impl.ViewTable;
 import org.apache.calcite.schema.impl.ViewTableMacro;
+import org.apache.calcite.sql.SqlDialect;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.sql.validate.SqlValidatorException;
 import org.apache.calcite.tools.FrameworkConfig;
@@ -878,6 +884,48 @@ public class CalciteAssert {
             ImmutableList.<String>of(), ImmutableList.of("POST", "TICKER"),
             null));
       return post;
+    case FAKE_FOODMART:
+      // Similar to FOODMART, but not based on JdbcSchema.
+      // Contains 2 tables that do not extend JdbcTable.
+      // They redirect requests for SqlDialect and DataSource to the real JDBC
+      // FOODMART, and this allows statistics queries to be executed.
+      foodmart = addSchemaIfNotExists(rootSchema, SchemaSpec.JDBC_FOODMART);
+      final Wrapper salesTable = (Wrapper) foodmart.getTable("sales_fact_1997");
+      SchemaPlus fake =
+          rootSchema.add(schema.schemaName, new AbstractSchema());
+      fake.add("time_by_day", new AbstractTable() {
+        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+          return typeFactory.builder()
+              .add("time_id", SqlTypeName.INTEGER)
+              .add("the_year", SqlTypeName.INTEGER)
+              .build();
+        }
+
+        public <C> C unwrap(Class<C> aClass) {
+          if (aClass.isAssignableFrom(SqlDialect.class)
+              || aClass.isAssignableFrom(DataSource.class)) {
+            return salesTable.unwrap(aClass);
+          }
+          return super.unwrap(aClass);
+        }
+      });
+      fake.add("sales_fact_1997", new AbstractTable() {
+        public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+          return typeFactory.builder()
+              .add("time_id", SqlTypeName.INTEGER)
+              .add("customer_id", SqlTypeName.INTEGER)
+              .build();
+        }
+
+        public <C> C unwrap(Class<C> aClass) {
+          if (aClass.isAssignableFrom(SqlDialect.class)
+              || aClass.isAssignableFrom(DataSource.class)) {
+            return salesTable.unwrap(aClass);
+          }
+          return super.unwrap(aClass);
+        }
+      });
+      return fake;
     case AUX:
       SchemaPlus aux =
           rootSchema.add(schema.schemaName, new AbstractSchema());
@@ -1882,6 +1930,7 @@ public class CalciteAssert {
   /** Specification for common test schemas. */
   public enum SchemaSpec {
     REFLECTIVE_FOODMART("foodmart"),
+    FAKE_FOODMART("foodmart"),
     JDBC_FOODMART("foodmart"),
     CLONE_FOODMART("foodmart2"),
     JDBC_FOODMART_WITH_LATTICE("lattice"),