javamysqlapache-calcite

Apache Calcite throws exception when executing query (SchemaPlus.getSubSchema returns null)


[Using Apache Calcite 1.30.0]

With query: SELECT * FROM mydb.employees I can see that when I relNode.explain( .. ) the * is expanded into the correct columns for the database, so I know that the connection to the database is working (somewhat) as expected, from the model.json schema.

LogicalProject(emp_no=[$0], birth_date=[$1], first_name=[$2], last_name=[$3], gender=[$4], hire_date=[$5]): rowcount = 100.0, cumulative cost = {200.0 rows, 701.0 cpu, 0.0 io}, id = 4
  JdbcTableScan(table=[[mydb, employees]]): rowcount = 100.0, cumulative cost = {100.0 rows, 101.0 cpu, 0.0 io}, id = 3

However, if I try: ResultSet resultSet = run.executeQuery();, it throws:

java.sql.SQLException: exception while executing query: Cannot invoke "org.apache.calcite.schema.SchemaPlus.unwrap(java.lang.Class)" because the return value of "org.apache.calcite.schema.SchemaPlus.getSubSchema(String)" is null

What am I missing from the connection/model/schema?

Model.json :

{
  version: '1.0',
  defaultSchema: 'mydb',
  schemas: [
    {
      name: 'mydb',
      type: 'custom',
      factory: 'org.apache.calcite.adapter.jdbc.JdbcSchema$Factory',
      operand: {
        jdbcDriver: 'com.mysql.jdbc.Driver',
        jdbcUrl: 'jdbc:mysql://127.0.0.1/mydb',
        jdbcUser: 'username',
        jdbcPassword: 'password'
      }
    }
  ]
}

Main Code Snippet :

Properties properties = new Properties();
properties.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(), "true");
properties.setProperty(CalciteConnectionProperty.QUOTED_CASING.camelName(), Casing.UNCHANGED.name());
properties.setProperty(CalciteConnectionProperty.UNQUOTED_CASING.camelName(), Casing.UNCHANGED.name());


Connection connection = DriverManager.getConnection("jdbc:calcite:model=src/main/resources/model.json", properties);
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();

SqlParser.Config caseSensitiveParser = SqlParser.configBuilder().setUnquotedCasing(Casing.UNCHANGED).setQuotedCasing(Casing.UNCHANGED).setCaseSensitive(true).build();
FrameworkConfig frameworkConfig = Frameworks.newConfigBuilder()
        .parserConfig(caseSensitiveParser)
        .defaultSchema(rootSchema)
        .build();

Planner planner = Frameworks.getPlanner(frameworkConfig);

String query = "SELECT * FROM mydb.employees";
SqlNode sqlNode = planner.parse(query);
SqlNode sqlNodeValidated = planner.validate(sqlNode);
RelRoot relRoot = planner.rel(sqlNodeValidated);
RelNode relNode = relRoot.project();
final RelWriter relWriter = new RelWriterImpl(new PrintWriter(System.out), SqlExplainLevel.ALL_ATTRIBUTES, false);
relNode.explain(relWriter);

PreparedStatement run = RelRunners.run(relNode);
ResultSet resultSet = run.executeQuery();

Solution

  • You need to unwrap RelRunner from the connection instead of using the dummy connection i.e RelRunners.run(relNode);

    Try this

    RelRunner runner = connection.unwrap(RelRunner.class);
    ResultSet resultSet = runner.prepareStatement(relNode).executeQuery();