在日常需求中,需要Calcite中添加对DDL语法的支持,这里记录一下调研过程以及实施结果。
parser.jj、parserImpls.ftl和config.fmpp
其中core模块中的parser.jj中定义了支持的语法模板,core模块中的parserImpls.ftl为空,在Server模块中的parserImpls.ftl中定义了具体的DDL语法,作为对core模块中的parserr.jj的补充,即如果采用serser模块中编译生成的SqlDdlParserImpl,是可以解析dql与ddl的,如果只是用core模块编译生成的SqlParserImpl则只能支持dql。
新增drop mask function语法解析
1:在parserImpls.ftl中新增
SqlDrop SqlDropMaskFunction(Span s, boolean replace) :
{
final boolean ifExists;
final SqlIdentifier id;
}
{
<MASK> <FUNCTION> ifExists = IfExistsOpt()
id = CompoundIdentifier() {
return SqlDdlNodes.dropMaskFunction(s.end(this), ifExists, id);
}
}
上述的 就对应的Sql语句里面的Mask Function
drop mask function if exists "my udf"
其中 ifExists = IfExistsOpt()是判断语句中是否包含if exists,如果是则ifExists 为true,最终会作为参数调用SqlDdlNodes.dropMaskFunction这个方法。而在SqlDdlNodes中,定义了Calcite当前支持的所有Ddl语法,当前具体包括:
其实这个类更像是工厂类,根据不同的方法去实例化不通的操作对象。
具体dropMaskFunction:
/**
* Create a drop mask function
*/
public static SqlDrop dropMaskFunction(SqlParserPos pos,
boolean ifExists, SqlIdentifier name) {
return new SqlDropMaskFunction(pos, ifExists, name);
}
SqlNode是Calcite中解析Sql后得到的抽象语法树的组成元素,因此我们需要新增相应的SqlNode对应新增的语句:
public class SqlDropMaskFunction extends SqlDropObject {
// SqlKind.DROP_MASK_FUNCTION 新增
private static final SqlOperator OPERATOR =
new SqlSpecialOperator("DROP MASK FUNCTION", SqlKind.DROP_MASK_FUNCTION);
/**
* Creates a SqlDropFunction.
*
* @param pos 当前Sql的行数以及开始和结束位置
* @param ifExists 是否已经存在
* @param name 函数名
*/
public SqlDropMaskFunction(SqlParserPos pos, boolean ifExists, SqlIdentifier name) {
super(OPERATOR ,pos, ifExists, name);
}
}
2:在config.fmpp中新增
再根据需要添加保留关键字即可。
到这里其实解析已经可以过了。可以在ServerParserTest中添加:
3:测试
final String sql = "drop mask function if exists \"my udf\"";
final String expected = "DROP MASK FUNCTION IF EXISTS `my udf`";
sql(sql).ok(expected);
进行相应测试。
新增drop mask function语法执行
上述只是新增了drop mask function语法支持,具体要具有执行能力的话,还需添加具体的执行能力,我们知道在使用JDBC执行Sql语句时,通过statement的execute方法,因此我们从AvaticaStatement的execute的方法入手,去查找具体DDL的执行逻辑:
// AvaticaStatement
public boolean execute(String sql) throws SQLException {
checkOpen();
checkNotPreparedOrCallable("execute(String)");
// 执行具体的Sql
executeInternal(sql);
// Result set is null for DML or DDL.
// Result set is closed if user cancelled the query.
return openResultSet != null && !openResultSet.isClosed();
}
在继续查看executeInternal方法,其中重复调用了connection的prepareAndExecuteInternal方法:
Meta.ExecuteResult x =
connection.prepareAndExecuteInternal(this, sql, maxRowCount1);
继续查看可以看到最终调用了CalcitePrepareImpl中的prepare2_:
if (sqlNode.getKind().belongsTo(SqlKind.DDL)) {
executeDdl(context, sqlNode);
return new CalciteSignature<>(query.sql,
ImmutableList.of(),
ImmutableMap.of(), null,
ImmutableList.of(), Meta.CursorFactory.OBJECT,
null, ImmutableList.of(), -1, null,
Meta.StatementType.OTHER_DDL);
}
上述判断当前语法是否是DDL,如果是直接执行executeDdl方法
首先需要将新增的SqlKind.DROP_MASK_FUNCTION新增为SqlKind.DDL中:
执行的具体方法如下:
@Override public void executeDdl(Context context, SqlNode node) {
final CalciteConnectionConfig config = context.config();
final SqlParserImplFactory parserFactory =
config.parserFactory(SqlParserImplFactory.class, SqlParserImpl.FACTORY);
final DdlExecutor ddlExecutor = parserFactory.getDdlExecutor();
ddlExecutor.executeDdl(context, node);
}
其中默认的ddlExecutor实现是ServerDdlExecutor,其中定义了根据不同SqlNode参数调用的方法:
可以看出具体的执行逻辑,我们新增的drop mask func在SqlDropObject中,其中根据枚举不同的ddl类型执行不同的操作,是否需要新增drop mask func的类型,就由用户自行决定 。
实现Demo如下:
case DROP_FUNCTION:
existed = schemaExists && schema.removeFunction(objectName);
if (!existed && !drop.ifExists) {
throw SqlUtil.newContextException(drop.name.getParserPosition(),
RESOURCE.functionNotFound(objectName));
}
break;
case DROP_MASK_FUNCTION:
System.out.println("Drop Mask Function Execute");
break;
case OTHER_DDL:
default:
throw new AssertionError(drop.getKind());
直接运行可以看到具体的内容输出。