Status
Current state: Under Discussion
...
Please keep the discussion on the mailing list rather than commenting on the wiki (wiki discussions get unwieldy fast).
Motivation
Currently, with FLIP-123, FLIP-152, we have supported Hive dialect. But it's much dependent on Flink planner. Also the interfaces involved is more like to be internal, which is not convenient and available to implement other dialects.
So, this FLIP is to Introduce pluggable dialect with some public interfaces to that make convenient to support other dialects. Also, at the same time, it's intend to slove the legacy problem brought by supporting Hive dialect that the Hive connector is coupled to Flink planner, which brings much complexity and maintenance burden.
Public Interfaces
We propose the following interfaces:
- Parser
- ParserFactory
- CatalogRegistry
- OperationContext
Parser
Code Block | ||
---|---|---|
| ||
/** Provides methods for parsing SQL objects from a SQL string. */ @PublicEvolving public interface Parser { // the interface has existed in current codebase, here we just expose it. } |
ParserFactory
Code Block | ||
---|---|---|
| ||
/** * Factory that creates {@link Parser}. * * <p>The {@link #factoryIdentifier()} is identified by matching it against {@link * TableConfigOptions#TABLE_SQL_DIALECT}. */ @PublicEvolving public interface ParserFactory extends Factory { /** Creates a new parser. */ Parser create(Context context); /** Context provided when a parser is created. */ @PublicEvolving interface Context { CatalogRegistry getCatalogRegistry(); // interfaces provided dealing with get catalog, qulify identifier, etc. OperationContext getOperationContext(); // interfaces provided to build Operation. } } |
CatalogRegistry
Code Block | ||
---|---|---|
| ||
/** * A catalog registry for dealing with catalogs */ @PublicEvolving public interface CatalogRegistry { String getCurrentDatabase(); String getCurrentCatalog(); ObjectIdentifier qualifyIdentifier(UnresolvedIdentifier identifier); Optional<Catalog> getCatalog(String catalogName); } |
OperationContext
Code Block | ||
---|---|---|
| ||
/** * A context for building {@link org.apache.flink.table.operations.Operation} */ @PublicEvolving public interface OperationContext { public QueryOperation scan(String path); public QueryOperation project(List<Expression> projectList, QueryOperation child); public QueryOperation project( List<Expression> projectList, QueryOperation child, boolean explicitAlias); public QueryOperation project( List<Expression> projectList, QueryOperation child, List<OverWindow> overWindows) // omit other public interaces has implemented in OperationTreeBuilder } |
Implement detail
We will introuce a slim module may called flink-table-planner-spi, which only expose these public interfaces refered above. And then, when we want to support other dialect, we need to include this slim module and implement ParserFactory to create a Parser for the specific dialect.
Example for supporting other dialect
In here, I would like to give an example for support MySQL dialect.
MySQLParserFactory
Code Block | ||
---|---|---|
| ||
public interface MySQLParserFactory extends ParserFactory { @Override public String factoryIdentifier() { return "mysql"; } Parser create(Context context) { return new MySqlParser(Context); } } |
MySQLParser
Code Block | ||
---|---|---|
| ||
public class MySQLParser implements Parser { private OperationContext operationContext; public MySQLParser(OperationContext operation) { this.operation = operation; } @Override List<Operation> parse(String statement) { // parse it to AST(Abstract Semantic Tree) MySQLAST mysqlAST = parseStament(statement); // convert the AST to Flink OperationTree List<Operation> operations = convertToOperation(mysqlAST); } private List<Operation> convertToOperation(MySQLAST mysqlAST) { // may look like Operation operation = operationContext.project(Arrays.asList(Expressions.$("f0"), operationContext.scan("t1")); return Collections.singletonList(operation) } @Override UnresolvedIdentifier parseIdentifier(String identifier) { // may need to identifier `db.t` to array of [db, t] string[] names = parseMySQLIdentifier(identifier); return UnresolvedIdentifier.of(names); } @Override ResolvedExpression parseSqlExpression( String sqlExpression, RowType inputRowType, @Nullable LogicalType outputType) { // parse sqlExpression to ResolvedExpression } @Override String[] getCompletionHints(String statement, int position) { // just for example, return empty string array directly return new String[0]; } } |
...
After that, you can switch to MySQL dialect by setting table.sql-dialect whiling excuting the sql.
Implementation for migrating Hive dialect
The support for Hive dialect should also do like this way: parsing to AST and convert it to Operation via OperationContext. But we has implemeted Hive dialect, and the current implementation is converting the sql to Calcite’s RelNode which is consistent to Hive’s implementation when using CBO in Hive. It'll take much efforts for we need to rewrite the codebase about Hive dialect totally for it's totally different from the current implementation. It's hard to migrate to Operation tree at one shot.
So the tempory way is to provide the calcite dependency in flink-table-planner-spi module, along with the ability to create RelNode, which invoves accessing the RelOptCluster, RelBuilder, etc, provided by PlannerContext. But it's internal and only used by Hive connector. At the end, the calcite dependency should be removed and the Hive dialect should be migrate to Operation tree. To do this, we propose to introduce the following interal interfaces/class:
CalciteContext
Code Block | ||
---|---|---|
| ||
// Context for creating RelNode @Internal public interface CalciteContext extends ParserFactory.Context { CalciteCatalogReader createCatalogReader( boolean lenientCaseSensitivity, String currentCatalog, String currentDatabase); RelOptCluster getCluster(); FrameworkConfig createFrameworkConfig(); RelDataTypeFactory getTypeFactory(); RelBuilder createRelBuilder(String currentCatalog, String currentDatabase); } |
The interfaces have been implemented in PlannerContext, but we need to expose them to enable Hive connector to use.
CalciteQueryOperation
Code Block | ||
---|---|---|
| ||
/** Wrapper for Calcite RelNode tree. */ @Internal public class CalciteQueryOperation implements QueryOperatio { private final RelNode calciteTree; private final ResolvedSchema resolvedSchema; public CalciteQueryOperation(RelNode calciteTree, ResolvedSchema resolvedSchema) { this.calciteTree = calciteTree; this.resolvedSchema = resolvedSchema; } } |
Proposed Changes
- Introduce a slim module flink-table-planner-spl exposing interfaces for supporting other dialects.
- Add calcite dependencys for Hive dialect in moudle flink-table-planner-spl. But's it's just a tempory way to migrating Hive dialect, the specific dependencys or logic should be dropped at the end.
Compatibility, Deprecation, and Migration Plan
For Hive dialect, first replace the dependency on flink-table-planner with flink-table-planner-spl, and then drop the calcite dependencys in flink-table-planner-spl and migrate it to Flink's OperationTree.
Test Plan
It's just refactor work, which can be tested by existing tests.
...