Eric Pugh created CALCITE-7730:
----------------------------------

             Summary: Provide a hook in the JDBC driver to inject a custom 
SqlOperatorTable without mutating the global SqlStdOperatorTable singleton
                 Key: CALCITE-7730
                 URL: https://issues.apache.org/jira/browse/CALCITE-7730
             Project: Calcite
          Issue Type: Improvement
    Affects Versions: 1.42.0
            Reporter: Eric Pugh


This was discovered during the upgrade of Solr's use of Calcite!  Here is the 
PR where I discovered the gap: [https://github.com/apache/solr/pull/3119]

We can't eliminate our last use of deprecated code ;-(.

 

## Problem

`ReflectiveSqlOperatorTable.register()` is deprecated (since CALCITE-6024) in 
favour of
`SqlOperatorTables.of()` / `SqlOperatorTables.chain()`. However, for projects 
that use
Calcite through its standard JDBC driver (`org.apache.calcite.jdbc.Driver`), 
there is
currently no non-deprecated way to make custom `SqlFunction` operators visible 
to the SQL
validator.

The validator's operator table is assembled inside
`CalcitePrepareImpl.createSqlValidator()`, which is `private static`. It chains:
1. `context.config().fun(SqlOperatorTable.class, 
SqlStdOperatorTable.instance())`
2. `CalciteCatalogReader`

The `fun()` path only supports named built-in libraries (`SqlLibrary`), not 
arbitrary
`SqlOperatorTable` instances. There is no protected method in 
`CalcitePrepareImpl` or
`CalcitePreparingStmt` (a private inner class) to override. 
`Driver.withPrepareFactory()`
exists, but subclassing `CalcitePreparingStmt` to change `createSqlValidator` 
is not
possible without access to the private inner class.

## Consequence

JDBC-based Calcite adapters that need custom SQL-level functions (e.g. 
`SqlFunction`
subclasses) are forced to mutate the global `SqlStdOperatorTable` singleton via 
the
deprecated `register()` call. This is a global side-effect and cannot be scoped 
to a
single connection or driver instance.

Projects using the `Frameworks` API (Apache Flink, etc.) do not have this 
problem because
they construct their own `SqlValidator` directly and can pass any 
`SqlOperatorTable` they
like. The JDBC path has no equivalent injection point.

Note: Flink's `FlinkSqlOperatorTable` also extends `ReflectiveSqlOperatorTable` 
and calls
`register()` for its dynamic functions, suggesting this is a common pain point 
beyond just
Solr.

## Suggested Fix

One or more of the following would resolve the issue:

1. **Add a protected `createOperatorTable()` method to `CalcitePrepareImpl`** 
that
   subclasses can override to return a chained `SqlOperatorTable`.

2. **Add a `CalciteConnectionProperty` (or connection URL parameter) that 
accepts a
   `SqlOperatorTable` instance** (or a factory class name) so JDBC users can 
supply custom
   operators without touching the global singleton.

3. **Make `CalcitePreparingStmt` a top-level or protected class** so that
   `createSqlValidator()` can be overridden by subclasses returned from
   `getPreparingStmt()`.

Any of these would allow the non-deprecated `SqlOperatorTables.of()` / 
`chain()` API to
be used end-to-end in a JDBC context.

## Workaround

Currently the only viable workaround is:

```java
@SuppressWarnings("deprecation")
private void registerUDFs() {
    // register() is deprecated but there is no non-deprecated injection point
    // for custom SqlFunction operators in the JDBC driver path.
    SqlStdOperatorTable.instance().register(new MyCustomFunction());
}

Environment

- Calcite version: 1.42.0
- Usage: JDBC driver (org.apache.calcite.jdbc.Driver subclass)
- Affected class: CalcitePrepareImpl.createSqlValidator (private static, not 
overridable)



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to