content: /*
   * 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.calcite.table;
  
  import org.apache.calcite.DataContext;
  import org.apache.calcite.adapter.enumerable.EnumerableCalc;
  import org.apache.calcite.adapter.enumerable.EnumerableConvention;
  import org.apache.calcite.adapter.enumerable.EnumerableInterpretable;
  import org.apache.calcite.adapter.enumerable.EnumerableRel;
  import org.apache.calcite.adapter.java.JavaTypeFactory;
  import org.apache.calcite.avatica.remote.TypedValue;
  import org.apache.calcite.config.CalciteConnectionConfig;
  import org.apache.calcite.config.CalciteConnectionConfigImpl;
  import org.apache.calcite.config.CalciteConnectionProperty;
  import org.apache.calcite.jdbc.CalciteSchema;
  import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
  import org.apache.calcite.linq4j.Enumerable;
  import org.apache.calcite.linq4j.EnumerableDefaults;
  import org.apache.calcite.plan.Contexts;
  import org.apache.calcite.plan.RelOptLattice;
  import org.apache.calcite.plan.RelOptMaterialization;
  import org.apache.calcite.plan.RelOptPlanner;
  import org.apache.calcite.plan.RelTraitSet;
  import org.apache.calcite.prepare.CalciteCatalogReader;
  import org.apache.calcite.prepare.Prepare;
  import org.apache.calcite.rel.RelNode;
  import org.apache.calcite.rel.RelRoot;
  import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
  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.rex.RexBuilder;
  import org.apache.calcite.rex.RexExecutor;
  import org.apache.calcite.rex.RexNode;
  import org.apache.calcite.rex.RexProgram;
  import org.apache.calcite.rex.RexUtil;
  import org.apache.calcite.runtime.Bindable;
  import org.apache.calcite.runtime.Hook;
  import org.apache.calcite.schema.AggregateFunction;
  import org.apache.calcite.schema.ScalarFunction;
  import org.apache.calcite.schema.SchemaPlus;
  import org.apache.calcite.schema.TableFunction;
  import org.apache.calcite.schema.TableMacro;
  import org.apache.calcite.schema.impl.AbstractSchema;
  import org.apache.calcite.schema.impl.AggregateFunctionImpl;
  import org.apache.calcite.schema.impl.ScalarFunctionImpl;
  import org.apache.calcite.schema.impl.TableFunctionImpl;
  import org.apache.calcite.schema.impl.TableMacroImpl;
  import org.apache.calcite.sql.SqlNode;
  import org.apache.calcite.sql.SqlOperatorTable;
  import org.apache.calcite.sql.fun.SqlStdOperatorTable;
  import org.apache.calcite.sql.parser.SqlParser;
  import org.apache.calcite.sql.type.SqlTypeName;
  import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
  import org.apache.calcite.sql2rel.SqlRexConvertletTable;
  import org.apache.calcite.sql2rel.StandardConvertletTable;
  import org.apache.calcite.tools.FrameworkConfig;
  import org.apache.calcite.tools.Frameworks;
  import org.apache.calcite.tools.Planner;
  import org.apache.calcite.tools.Program;
  import org.apache.calcite.tools.Programs;
  import org.apache.calcite.util.Pair;
  import org.apache.calcite.util.Util;
  
  import org.apache.commons.lang3.StringUtils;
  
  import com.google.common.cache.Cache;
  import com.google.common.cache.CacheBuilder;
  import com.google.common.collect.Maps;
  
  import java.lang.reflect.Field;
  import java.sql.ResultSet;
  import java.sql.ResultSetMetaData;
  import java.sql.Types;
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.Collections;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Properties;
  import java.util.concurrent.ConcurrentHashMap;
  
  /**
   * A TableEnv can be used to:
   * 1.register a Table in [TableEnv]'s catalog
   * 2.add subSchema and Function in [TableEnv]'s catalog
   * 3.convert a JDBC ResultSet to a Table
   * 5.convert a simple java pojo list to a Table
   * 6.execute a sql query to get a Table
   * <p> a TableEnv supports the calcite-sql query by default,
   * but developers can extend a TableEnv to implement
   * a HiveTableEnv or MysqlTableEnv etc...,to support other sql dialects.
   */
  public class TableEnv {
  
    private static int maxSqlPlanCacheSize;
    private static Cache<String, TableExecutionPlan> sql2ExecutionPlanCache;
  
    /**
     * for user to conveniently inspect the generated code
     */
    private static final Cache<String, String> SQL_2_EXECUTION_PLAN_CODE_CACHE
        = CacheBuilder
        .newBuilder()
        .maximumSize(200)
        .build();
  
    private static final Map<String, Class> SQL_2_BINDABLE_CLASS_MAP = new
        ConcurrentHashMap<>();
  
  
    private String cacheKeyPrefix = "";
  
    public String getCacheKeyPrefix() {
      return cacheKeyPrefix;
    }
  
    public void setCacheKeyPrefix(
        String cacheKeyPrefix) {
      this.cacheKeyPrefix = cacheKeyPrefix;
    }
  
    public static void clearExecutionPlan() {
      if (sql2ExecutionPlanCache != null) {
        sql2ExecutionPlanCache.invalidateAll();
      }
    }
  
    public void registerSqlBindableClass(String sql, Class clazz) {
      SQL_2_BINDABLE_CLASS_MAP.put(cacheKeyPrefix + sql,
          clazz);
    }
  
    public static int getMaxSqlPlanCacheSize() {
      return maxSqlPlanCacheSize;
    }
  
    public static synchronized void enableSqlPlanCacheSize(
        int maxSqlPlanCacheSize) {
      if (sql2ExecutionPlanCache == null) {
        TableEnv.maxSqlPlanCacheSize = maxSqlPlanCacheSize;
        sql2ExecutionPlanCache = CacheBuilder
            .newBuilder()
            .maximumSize(maxSqlPlanCacheSize)
            .build();
      } else {
        throw new RuntimeException(
            "sql2ExecutionPlanCache has already been enabled,maxSqlPlanCacheSize="
                + maxSqlPlanCacheSize);
      }
    }
  
    public static TableEnv getTableEnv() {
      return new TableEnv(new TableConfig());
    }
  
    public static TableEnv getTableEnv(TableConfig tableConfig) {
      return new TableEnv(tableConfig);
    }
  
    protected TableConfig tableConfig;
    private CalciteSchema internalSchema = CalciteSchema.createRootSchema(false,
        false);
    protected SchemaPlus rootSchema = internalSchema.plus();
    protected FrameworkConfig frameworkConfig;
    protected CalciteCatalogReader calciteCatalogReader;
  
  
    public TableEnv(TableConfig tableConfig) {
      try {
        this.tableConfig = tableConfig;
        SqlParser.Config sqlParserConfig = tableConfig.getSqlParserConfig()
            != null ? tableConfig.getSqlParserConfig() : SqlParser
            .configBuilder().setCaseSensitive(false)
            .build();
        SqlOperatorTable sqlStdOperatorTable = tableConfig
            .getSqlOperatorTable()
            != null
            ? tableConfig.getSqlOperatorTable()
            : ChainedSqlOperatorTable.of(SqlStdOperatorTable.instance());
        CalciteConnectionConfig calciteConnectionConfig = tableConfig
            .getCalciteConnectionConfig()
            != null
            ? tableConfig.getCalciteConnectionConfig()
            : createDefaultConnectionConfig(sqlParserConfig);
        RelDataTypeSystem typeSystem = tableConfig.getRelDataTypeSystem() != null
            ? tableConfig.getRelDataTypeSystem()
            : calciteConnectionConfig.typeSystem(RelDataTypeSystem.class,
                RelDataTypeSystem.DEFAULT);
        SqlRexConvertletTable convertletTable = tableConfig
            .getConvertletTable()
            != null
            ? tableConfig
            .getConvertletTable()
            : StandardConvertletTable.INSTANCE;
        RexExecutor rexExecutor = tableConfig.getRexExecutor() != null
            ? tableConfig.getRexExecutor()
            : RexUtil.EXECUTOR;
        this.calciteCatalogReader = new CalciteCatalogReader(
            CalciteSchema.from(rootSchema),
            CalciteSchema.from(rootSchema).path(null),
            new JavaTypeFactoryImpl(typeSystem),
            calciteConnectionConfig);
        this.frameworkConfig = createFrameworkConfig(sqlParserConfig,
            ChainedSqlOperatorTable.of(sqlStdOperatorTable,
                calciteCatalogReader), convertletTable,
            calciteConnectionConfig, typeSystem, rexExecutor);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  
    public TableConfig getTableConfig() {
      return tableConfig;
    }
  
    protected Program createProgram() {
      final Program program1 =
          (planner, rel, requiredOutputTraits, materializations, lattices) -> {
            //override the default ruleSet of planner here
            planner.setRoot(rel);
            for (RelOptMaterialization materialization : materializations) {
              planner.addMaterialization(materialization);
            }
            for (RelOptLattice lattice : lattices) {
              planner.addLattice(lattice);
            }
  
            final RelNode rootRel2 =
                rel.getTraitSet().equals(requiredOutputTraits)
                    ? rel
                    : planner.changeTraits(rel, requiredOutputTraits);
            assert rootRel2 != null;
  
            planner.setRoot(rootRel2);
            final RelOptPlanner planner2 = planner.chooseDelegate();
            final RelNode rootRel3 = planner2.findBestExp();
            assert rootRel3 != null : "could not implement exp";
            return rootRel3;
          };
      DefaultRelMetadataProvider metadataProvider = DefaultRelMetadataProvider
          .INSTANCE;
      return Programs.sequence(Programs.subQuery(metadataProvider),
          new Programs.DecorrelateProgram(),
          new Programs.TrimFieldsProgram(),
          program1,
  
          // Second planner pass to do physical "tweaks". This the first time
          // that EnumerableCalcRel is introduced.
          Programs.calc(metadataProvider));
    }
  
    private FrameworkConfig createFrameworkConfig(
        SqlParser.Config sqlParserConfig, SqlOperatorTable sqlOperatorTable,
        SqlRexConvertletTable convertletTable,
        CalciteConnectionConfig calciteConnectionConfig,
        RelDataTypeSystem relDataTypeSystem, RexExecutor rexExecutor) {
      return Frameworks
          .newConfigBuilder()
          .defaultSchema(rootSchema)
          .parserConfig(sqlParserConfig)
          .operatorTable(sqlOperatorTable)
          .convertletTable(convertletTable)
          .typeSystem(relDataTypeSystem)
          .executor(rexExecutor)
          .context(Contexts.of(calciteConnectionConfig))
          .build();
    }
  
    private CalciteConnectionConfig createDefaultConnectionConfig(
        SqlParser.Config sqlParserConfig) {
      Properties prop = new Properties();
      prop.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(),
          String.valueOf(sqlParserConfig.caseSensitive()));
      return new CalciteConnectionConfigImpl(prop);
    }
  
    public DataTable getTable(String tableName) {
      return (DataTable) rootSchema.getTable(tableName.toUpperCase());
    }
  
    public DataTable getTable(String subSchemaName, String tableName) {
      return (DataTable) rootSchema.getSubSchema(subSchemaName.toUpperCase())
          .getTable(tableName.toUpperCase());
    }
  
    public void registerTable(String tableName, DataTable dataTable) {
      rootSchema.add(tableName.toUpperCase(), dataTable);
    }
  
    public void registerTable(String subSchemaName, String tableName,
        DataTable dataTable) {
      rootSchema.getSubSchema(subSchemaName.toUpperCase())
          .add(tableName.toUpperCase(), dataTable);
    }
  
    public SchemaPlus addSubSchema(String subSchemaName) {
      return rootSchema.add(subSchemaName.toUpperCase(), new AbstractSchema());
    }
  
    public void addFunction(String schemaName, String functionName,
        String className, String methodName) {
      boolean upCase = true;
      SchemaPlus schema;
      schemaName = schemaName.toUpperCase();
      functionName = functionName.toUpperCase();
      if (StringUtils.isBlank(schemaName)) {
        schema = rootSchema;
      } else {
        schema = rootSchema.getSubSchema(schemaName);
        if (schema == null) {
          schema = addSubSchema(schemaName);
        }
      }
      final Class<?> clazz;
      try {
        clazz = Class.forName(className);
      } catch (ClassNotFoundException e) {
        throw new RuntimeException("UDF class '"
            + className + "' not found");
      }
      final TableFunction tableFunction =
          TableFunctionImpl.create(clazz, Util.first(methodName, "eval"));
      if (tableFunction != null) {
        schema.add(functionName, tableFunction);
        return;
      }
      // Must look for TableMacro before ScalarFunction. Both have an "eval"
      // method.
      final TableMacro macro = TableMacroImpl.create(clazz);
      if (macro != null) {
        schema.add(functionName, macro);
        return;
      }
      if (methodName != null && methodName.equals("*")) {
        for (Map.Entry<String, ScalarFunction> entry
            : ScalarFunctionImpl.createAll(clazz).entries()) {
          String name = entry.getKey();
          if (upCase) {
            name = name.toUpperCase(Locale.ROOT);
          }
          schema.add(name, entry.getValue());
        }
        return;
      } else {
        final ScalarFunction function =
            ScalarFunctionImpl.create(clazz, Util.first(methodName, "eval"));
        if (function != null) {
          final String name;
          if (functionName != null) {
            name = functionName;
          } else if (upCase) {
            name = methodName.toUpperCase(Locale.ROOT);
          } else {
            name = methodName;
          }
          schema.add(name, function);
          return;
        }
      }
      if (methodName == null) {
        final AggregateFunction aggFunction = AggregateFunctionImpl.create(clazz);
        if (aggFunction != null) {
          schema.add(functionName, aggFunction);
          return;
        }
      }
      throw new RuntimeException("Not a valid function class: " + clazz
          + ". Scalar functions and table macros have an 'eval' method; "
          + "aggregate functions have 'init' and 'add' methods, and optionally "
          + "'initAdd', 'merge' and 'result' methods.");
    }
  
  
    protected void executeBeforeSqlQuery(String sql) {
      //
    }
  
    protected void executeAfterSqlQuery(String sql) {
      //
    }
  
    public TableExecutionPlan getExecutionPlan(String sql) {
      return sql2ExecutionPlanCache == null
          ? null
          : sql2ExecutionPlanCache.getIfPresent(sql);
    }
  
    public String getExecutionCode(String sql) {
      return SQL_2_EXECUTION_PLAN_CODE_CACHE.getIfPresent(
          cacheKeyPrefix + sql);
    }
  
    public DataTable sqlQuery(String sql) {
      try {
        executeBeforeSqlQuery(sql);
        if (sql2ExecutionPlanCache != null) {
          TableExecutionPlan executionPlan = sql2ExecutionPlanCache
              .getIfPresent(
                  cacheKeyPrefix + sql);
          if (executionPlan != null) {
            return implement(executionPlan.getBindable(),
                executionPlan.getRowType(),
                calciteCatalogReader.getTypeFactory());
          }
        }
        TableExecutionPlan executionPlan = toExecutionPlan(sql);
        DataTable dt = implement(executionPlan.getBindable(),
            executionPlan.rowType,
            calciteCatalogReader.getTypeFactory());
        if (sql2ExecutionPlanCache != null) {
          sql2ExecutionPlanCache.put(cacheKeyPrefix + sql,
              executionPlan);
        }
        executeAfterSqlQuery(sql);
        return dt;
      } catch (Throwable t) {
        throw new RuntimeException(
            "current rootSchema:" + rootSchema.getTableNames(), t);
      }
    }
  
  
    public TableExecutionPlan toExecutionPlan(String sql) throws Throwable {
      RelRoot root = getSqlPlanRel(sql);
      return toExecutionPlan(root, sql);
    }
  
    protected RelRoot getSqlPlanRel(String sql) throws Throwable {
      try (Planner planner = Frameworks.getPlanner(frameworkConfig)) {
        RelRoot root;
        final SqlNode parsedSqlNode = planner.parse(sql);
        final Pair<SqlNode, RelDataType> validatedSqlNodeAndType = planner
            .validateAndGetType(
                parsedSqlNode);
        root = planner.rel(validatedSqlNodeAndType.getKey());
        final Program program = createProgram();
        //getDesiredTraits
        final RelTraitSet desiredTraits = root.rel.getTraitSet()
            .replace(EnumerableConvention.INSTANCE)
            .replace(root.collation)
            .simplify();
  
        RelNode logicalRelNode = root.rel;
        final RelNode optimizedRelNode = program.run(
            root.rel.getCluster().getPlanner(), logicalRelNode, desiredTraits,
            Collections.emptyList(), Collections.emptyList());
        root = root.withRel(optimizedRelNode);
        return root;
      }
  
    }
  
  
    private TableExecutionPlan toExecutionPlan(RelRoot root, String sql) {
      EnumerableRel enumerable = (EnumerableRel) root.rel;
      if (!root.isRefTrivial()) {
        final List<RexNode> projects = new ArrayList<>();
        final RexBuilder rexBuilder = enumerable.getCluster().getRexBuilder();
        for (int field : Pair.left(root.fields)) {
          projects.add(rexBuilder.makeInputRef(enumerable, field));
        }
        RexProgram program = RexProgram.create(enumerable.getRowType(),
            projects, null, root.validatedRowType, rexBuilder);
        enumerable = EnumerableCalc.create(enumerable, program);
      }
      Bindable bindable = null;
      final TableExecutionPlan tableExecutionPlan = new TableExecutionPlan();
      Hook.Closeable planHookHandle = Hook.JAVA_PLAN.addThread((code) -> {
        SQL_2_EXECUTION_PLAN_CODE_CACHE.put(cacheKeyPrefix + sql,
            (String) code);
        tableExecutionPlan.setCode((String) code);
      });
      try {
        Prepare.CatalogReader.THREAD_LOCAL.set(calciteCatalogReader);
        try {
          Class clazz = SQL_2_BINDABLE_CLASS_MAP.get(
              cacheKeyPrefix + sql);
          if (clazz != null) {
            bindable = (Bindable) clazz.newInstance();
          }
        } catch (Exception ex) {
          throw new RuntimeException(ex);
        }
        if (bindable == null) {
          bindable = toBindable(enumerable);
        }
        tableExecutionPlan.setBindable(bindable);
        tableExecutionPlan.setRowType(root.validatedRowType);
  
      } finally {
        Prepare.CatalogReader.THREAD_LOCAL.remove();
        planHookHandle.close();
      }
  
      return tableExecutionPlan;
    }
  
    protected Bindable toBindable(EnumerableRel rel) {
      return EnumerableInterpretable.toBindable(Maps.newHashMap(),
          null, rel, EnumerableRel.Prefer.ARRAY);
    }
  
    private DataTable implement(Bindable bindable, RelDataType rowType,
        RelDataTypeFactory typeFactory) {
      final DataContext dataContext = new TableDataContexImpl(null, rootSchema,
          (JavaTypeFactory) typeFactory, tableConfig);
      Enumerable<Object[]> enumerableResult = bindable.bind(dataContext);
      List rows = EnumerableDefaults.toList(enumerableResult);
      return new DataTable(rowType, rows);
    }
  
    protected SqlTypeName getSqlTypeNameForJdbcType(int jdbcType) {
      //FIX Calcite jdbc type converting bug
      SqlTypeName typeName = SqlTypeName.getNameForJdbcType(jdbcType);
      if (jdbcType == Types.LONGVARCHAR) {
        typeName = SqlTypeName.VARCHAR;
      }
      if (jdbcType == Types.SMALLINT || jdbcType == Types.TINYINT) {
        //the type of jdbc value is INTEGER when jdbcType is SMALLINT or TINYINT
        typeName = SqlTypeName.INTEGER;
      }
      return typeName;
    }
  
    public DataTable fromJdbcResultSet(
        ResultSet resultSet) {
      try {
        final RelDataTypeFactory.Builder builder = calciteCatalogReader
            .getTypeFactory()
            .builder();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        for (int i = 1; i <= columnCount; i++) {
          String columnName = metaData.getColumnLabel(i);
          int jdbcType = metaData.getColumnType(i);
          builder.add(columnName.toUpperCase(),
              getSqlTypeNameForJdbcType(jdbcType)).nullable(true);
        }
        RelDataType rowType = builder.build();
        List<Object[]> convertRows = new ArrayList<>();
        Calendar calendar = Calendar
            .getInstance(tableConfig.getTimeZone(), Locale.ROOT);
        while (resultSet.next()) {
          Object[] row = new Object[columnCount];
          for (int i = 1; i <= columnCount; i++) {
            Object jdbcObject = resultSet.getObject(i);
            Object convertedCalciteObject = TypedValue.ofJdbc(jdbcObject,
                calendar).value;
            row[i - 1] = convertedCalciteObject;
          }
          convertRows.add(row);
        }
        return new DataTable(rowType, convertRows);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  
    public DataTable fromJavaPojoList(List pojoList) {
      return fromJavaPojoList(pojoList, null, false);
    }
  
    public DataTable fromJavaPojoList(List pojoList,
        Map<String, String> fieldName2SqlColumnNameMap, boolean upperUnderscore) {
      try {
        PojoTypeConverter pojoTypeConverter = new PojoTypeConverter(
            calciteCatalogReader.getTypeFactory(), fieldName2SqlColumnNameMap,
            upperUnderscore, tableConfig.getTimeZone());
        Class pojoClass = null;
        List<Field> fieldList = null;
        RelDataType relDataType = null;
        List rows = new DataTable.DataTableStoreList();
        for (Object pojo : pojoList) {
          if (pojoClass == null) {
            pojoClass = pojo.getClass();
          } else {
            if (!pojoClass.equals(pojo.getClass())) {
              throw new RuntimeException(
                  "the classes of elements in pojoList must be same !");
            }
          }
          if (fieldList == null) {
            fieldList = PojoTypeConverter.getAllDeclaredFields(pojoClass);
          }
          PojoRelDataTypeValue relDataTypeValue = pojoTypeConverter
              .getPojoRelDataTypeValue(
                  pojoClass, fieldList, pojo);
          rows.add(relDataTypeValue.getValue());
          if (relDataType == null && relDataTypeValue != null) {
            relDataType = relDataTypeValue.getRelDataType();
          }
        }
        return new DataTable(relDataType, rows);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  
    public DataTable fromRowListWithSqlTypeMap(List<Map<String, Object>> rowList,
        Map<String, SqlTypeName> sqlTypeMap) {
      try {
        final RelDataTypeFactory.Builder builder = calciteCatalogReader
            .getTypeFactory()
            .builder();
        PojoTypeConverter pojoTypeConverter = new PojoTypeConverter(
            calciteCatalogReader.getTypeFactory(), null,
            true, tableConfig.getTimeZone());
        RelDataType rowType;
        List convertRows = new DataTable.DataTableStoreList();
  
        List<String> schemaKeys = new ArrayList<>(sqlTypeMap.keySet());
        schemaKeys.forEach(key -> {
          builder.add(key.toLowerCase(), sqlTypeMap.get(key)).nullable(true);
        });
        rowType = builder.build();
  
        for (Map<String, Object> map : rowList) {
          int i = 0;
          Object[] row = new Object[sqlTypeMap.size()];
          for (String key : schemaKeys) {
  
            Object o = map.get(key);
            PojoRelDataTypeValue calciteValue = pojoTypeConverter
                .javaObjectToCalciteObject(
                    sqlTypeMap.get(key), o, tableConfig.getTimeZone(), null);
            row[i] = calciteValue.getValue();
            i++;
          }
          convertRows.add(row);
        }
        return new DataTable(rowType, convertRows);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  
    /**
     * the execution plan for a sql query
     */
    public static class TableExecutionPlan {
      private String code;
      private Bindable bindable;
      private RelDataType rowType;
  
      public TableExecutionPlan() {
      }
  
      public TableExecutionPlan(String code, Bindable bindable,
          RelDataType rowType) {
        this.code = code;
        this.bindable = bindable;
        this.rowType = rowType;
      }
  
      public Bindable getBindable() {
        return bindable;
      }
  
      public RelDataType getRowType() {
        return rowType;
      }
  
      public String getCode() {
        return code;
      }
  
      public void setCode(String code) {
        this.code = code;
      }
  
      public void setBindable(Bindable bindable) {
        this.bindable = bindable;
      }
  
      public void setRowType(RelDataType rowType) {
        this.rowType = rowType;
      }
    }
  
  }
  
  // End TableEnv.java
  