Apache IoTDB’s UDF源码分析(1)

目录

前言

命令行注册UDF函数(Create Function xxx as "全限定类名")

语法分析

 生成物理计划

执行物理计划进行函数注册

 Select带有UDF函数的查询


前言

        继上个月开始了Apache IoTDB的源码贡献,闲来有空时,便会看看感兴趣模块的代码。这次主要跟大家分享一下自己对Apache IoTDB’s UDF相关源码的一些分析与理解。至于为什么选择阅读它,或者说为什么对它感兴趣,可能我觉得这也是一个软件扩展性的一个体现吧!好了,废话不多说,开始上菜~

注:再啰嗦一下哈!如果大家对Apache IoTDB的UDF的使用没有一个认识的话,建议最好可以看一下它的官方文档:UDF官方文档,我刚开始看的时候感觉有点难看,建议读者如果跟我一样的感受,最好是自己跟着写一个小Demo就清楚了,它github仓库代码也有例子。

命令行注册UDF函数(Create Function xxx as "全限定类名")

IoTDB> create function example1 as "org.study.demo.UDTFExample" 
Msg: The statement is executed successfully.

这句话背后到底发生了什么?

语法分析

        在server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java里visitCreateFunction方法里将该语句转换成了内部的CreateFunctionOperator。

@Override
public Operator visitCreateFunction(IoTDBSqlParser.CreateFunctionContext ctx) {
  CreateFunctionOperator createFunctionOperator =
      new CreateFunctionOperator(SQLConstant.TOK_FUNCTION_CREATE);
  createFunctionOperator.setUdfName(parseIdentifier(ctx.udfName.getText()));
  createFunctionOperator.setClassName(parseStringLiteral(ctx.className.getText()));
  return createFunctionOperator;
}

 生成物理计划

server/src/main/java/org/apache/iotdb/db/qp/logical/sys/CreateFunctionOperator.java

@Override
public PhysicalPlan generatePhysicalPlan(PhysicalGenerator generator)
    throws QueryProcessException {
  return new CreateFunctionPlan(udfName, className);
}

执行物理计划进行函数注册

server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java

@Override
public boolean processNonQuery(PhysicalPlan plan)
    throws QueryProcessException, StorageGroupNotSetException, StorageEngineException {
  、、、
  case CREATE_FUNCTION:
        return operateCreateFunction((CreateFunctionPlan) plan);
  、、、
}

private boolean operateCreateFunction(CreateFunctionPlan plan) throws UDFRegistrationException {
  UDFRegistrationService.getInstance().register(plan.getUdfName(), plan.getClassName(), true);
  return true;
}

由此可见关键就在于UDFRegistrationService的注册方法

server/src/main/java/org/apache/iotdb/db/query/udf/service/UDFRegistrationService.java

public void register(String functionName, String className, boolean writeToTemporaryLogFile)
    throws UDFRegistrationException {
  functionName = functionName.toUpperCase();
  validateFunctionName(functionName, className);	// 检查函数名是否合法,主要是跟内置的一些函数是否冲突[1]
  checkIfRegistered(functionName, className);		// 检查是否注册过
  doRegister(functionName, className);				// 注册,通过反射构造Method对象,放进ConcurrentHashMap中
  tryAppendRegistrationLog(functionName, className, writeToTemporaryLogFile);
}

看到这,UDF注册已经没有密码了,就是根据用户传进的全限定类名进行反射,获得Method引用,保存起来用于后续UDF查询的调用。(详见代码注释)

其实还有个问题,就是Apache IoTDB如何知道到哪加载用户类呢?

这个其实类似一种约定,代码内部是写死的,用户要将写好的UDF类打包的jar放到指定目录ext/udf下,具体见下面的代码片段。

server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java

/** External lib directory for UDF, stores user-uploaded JAR files */
private String udfDir =
    IoTDBConstant.EXT_FOLDER_NAME + File.separator + IoTDBConstant.UDF_FOLDER_NAME;

node-commons/src/main/java/org/apache/iotdb/commons/conf/IoTDBConstant.java

public static final String EXT_FOLDER_NAME = "ext";

public static final String UDF_FOLDER_NAME = "udf";

 Select带有UDF函数的查询

SELECT s1, example1(s1), s2, example1(s2) FROM root.sg1.d1;

接下来就是带有UDF查询的SQL语句背后的执行机制,讲真的我觉得对于现在的我来说,也是好不容易看懂,但你真要我比较清晰地讲出来,可能得再过段时间了,再梳理梳理,这里先稍微简单讲讲吧。

基本思想,肯定后面会根据前面注册的那个Method引用在适当的地方进行对查询的数据进行转换。但在具体实现时,那位作者将其分成三层

  1. InputLayer 2. TransformerLayer 3. OutputLayer

InputLayer: 一个原始的timeseries数据的scan的输入(多个用到该timeseries的数据也只需一个,避免重复资源浪费、效率等等吧);

TransformerLayer: 如果有UDF会进行转换,有的是直接透传

OutputLayer: 最终查询列的输出形式

上述的语句最终会转换成UDTFQueryOperator,然后会进一步进行处理,这里贴出一些关键代码给大家解解馋,敬请期待下文哈!如果大家实在等不了,可以自行阅读代码,如果有什么见解,欢迎与我讨论,共同进步~

@Override
public IntermediateLayer constructIntermediateLayer(	// 构建中间层是进行UDF的调用
    long queryId,
    UDTFPlan udtfPlan,
    RawQueryInputLayer rawTimeSeriesInputLayer,
    Map<Expression, IntermediateLayer> expressionIntermediateLayerMap,
    Map<Expression, TSDataType> expressionDataTypeMap,
    LayerMemoryAssigner memoryAssigner)
    throws QueryProcessException, IOException {
  if (!expressionIntermediateLayerMap.containsKey(this)) {
    float memoryBudgetInMB = memoryAssigner.assign();
    Transformer transformer;
    if (isPlainAggregationFunctionExpression) {
      transformer =
          new TransparentTransformer(	// 等于没有转换,相当于透传
              rawTimeSeriesInputLayer.constructPointReader(
                  udtfPlan.getReaderIndexByExpressionName(toString())));
    } else {
      IntermediateLayer udfInputIntermediateLayer =
          constructUdfInputIntermediateLayer(
              queryId,
              udtfPlan,
              rawTimeSeriesInputLayer,
              expressionIntermediateLayerMap,
              expressionDataTypeMap,
              memoryAssigner);
      transformer =
          constructUdfTransformer(	// 执行了validate和beforeStart两步,根据相应的访问策略AccessStrategy返回对应的Transformer
              queryId,
              udtfPlan,
              expressionDataTypeMap,
              memoryAssigner,
              udfInputIntermediateLayer);
    }
    expressionDataTypeMap.put(this, transformer.getDataType());
    expressionIntermediateLayerMap.put(
        this,
        memoryAssigner.getReference(this) == 1
            ? new SingleInputColumnSingleReferenceIntermediateLayer(
                this, queryId, memoryBudgetInMB, transformer)
            : new SingleInputColumnMultiReferenceIntermediateLayer(
                this, queryId, memoryBudgetInMB, transformer));	// 它的LayerPointReader是transformer
  }

  return expressionIntermediateLayerMap.get(this);
}
private UDFQueryTransformer constructUdfTransformer(
    long queryId,
    UDTFPlan udtfPlan,
    Map<Expression, TSDataType> expressionDataTypeMap,
    LayerMemoryAssigner memoryAssigner,
    IntermediateLayer udfInputIntermediateLayer)
    throws QueryProcessException, IOException {
  UDTFExecutor executor = udtfPlan.getExecutorByFunctionExpression(this);

  executor.beforeStart(queryId, memoryAssigner.assign(), expressionDataTypeMap);

  AccessStrategy accessStrategy = executor.getConfigurations().getAccessStrategy();
  switch (accessStrategy.getAccessStrategyType()) {
    case ROW_BY_ROW:
      return new UDFQueryRowTransformer(udfInputIntermediateLayer.constructRowReader(), executor);
    case SLIDING_SIZE_WINDOW:
    case SLIDING_TIME_WINDOW:
      return new UDFQueryRowWindowTransformer(
          udfInputIntermediateLayer.constructRowWindowReader(
              accessStrategy, memoryAssigner.assign()),
          executor);
    default:
      throw new UnsupportedOperationException("Unsupported transformer access strategy");
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值