Flink源码阅读(六) 自研SQL引擎的实现方法

2,865 阅读14分钟

承接上文 Flink源码阅读(五) FLink SQL之解析流程

在上文简述完SQL解析流程,并声明自定义SQL引擎中涉及改动到的类之后。本文基于 calcite 1.21.0 阐述如何自定义一个SQL引擎并应用到实际项目当中

自定义SQL工程需要实现的部分

1.生成语法解析器
2.实现SqlNode转换为逻辑算子
3.将逻辑算子转换为RexNode 
4.简化RexNode 表达式,为每个表达式写一个文字到列表中
5.设置新的TableEnv

工程路径: E:\marble-master\marble-table-hive\src\main\java\org\apache\calcite\adapter\hive\HiveTableEnv.java

 public static TableEnv getTableEnv() {
    TableConfig tableConfig = new TableConfig();
    //SqlNode转换为逻辑算子 此处重写SqlOperatorTable类
    tableConfig.setSqlOperatorTable(
        ChainedSqlOperatorTable.of(HiveSqlOperatorTable.instance(),
            SqlStdOperatorTable.instance()));
            
     //语法解析器,此处HiveSqlParserImpl类为通过javacc基于语法模板编译生成
    tableConfig.setSqlParserConfig(SqlParser
        .configBuilder()
        .setLex(Lex.JAVA).setCaseSensitive(false).setConformance(
            SqlConformanceEnum.HIVE)
        .setParserFactory(HiveSqlParserImpl.FACTORY)
        .build());
//    tableConfig.setRelDataTypeSystem(new HiveTypeSystemImpl());
    Properties prop = new Properties();
    prop.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(),
        String.valueOf(tableConfig.getSqlParserConfig().caseSensitive()));
    tableConfig.setCalciteConnectionConfig(
        new CalciteConnectionConfigImpl(prop));
     //将逻辑算子转换为RelNode,此处重写ConvertletTable类
    tableConfig.setConvertletTable(new HiveConvertletTable());
    //简化RelNode表达式,为每个表达式写一个文字到列表中
    RexExecutor rexExecutor = new HiveRexExecutorImpl(
        Schemas.createDataContext(null, null));
    tableConfig.setRexExecutor(rexExecutor);
    //设置新的TableEnv
    TableEnv tableEnv = new HiveTableEnv(tableConfig);
    //add table functions
    tableEnv.addFunction("", "explode",
        "org.apache.calcite.adapter.hive.udtf.UDTFExplode", "eval");
    return tableEnv;
  }

生成语法解析器

配置maven插件

新建工程,在pom文件中配置插件,包括````freemarker和javacc```,如下所示


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
         
    <modelVersion>4.0.0</modelVersion>
         
    <groupId>com.u51.marble</groupId>
    <artifactId>marble</artifactId>
    <version>1.0.1-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.calcite/calcite-core -->
        <dependency>
            <groupId>org.apache.calcite</groupId>
            <artifactId>calcite-core</artifactId>
            <version>1.21.0</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>javacc-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>javacc</id>
                        <goals>
                            <goal>javacc</goal>
                        </goals>
                        <configuration>
                            <sourceDirectory>${project.build.directory}/generated-sources/fmpp</sourceDirectory>
                            <includes>
                                <include>**/Parser.jj</include>
                            </includes>
                            <lookAhead>2</lookAhead>
                            <isStatic>false</isStatic>
                        </configuration>
                    </execution>
                    <execution>
                        <id>javacc-test</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>javacc</goal>
                        </goals>
                        <configuration>
                            <sourceDirectory>${project.build.directory}/generated-test-sources/fmpp</sourceDirectory>
                            <outputDirectory>${project.build.directory}/generated-test-sources/javacc</outputDirectory>
                            <includes>
                                <include>**/Parser.jj</include>
                            </includes>
                            <lookAhead>2</lookAhead>
                            <isStatic>false</isStatic>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.drill.tools</groupId>
                <artifactId>drill-fmpp-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <configuration>
                            <config>src/main/codegen/config.fmpp</config>
                            <output>${project.build.directory}/generated-sources/fmpp</output>
                            <templates>src/main/codegen/templates</templates>
                        </configuration>
                        <id>generate-fmpp-sources</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/codegen</directory>
                <targetPath>codegen</targetPath>
            </resource>
        </resources>
    </build>
</project>
引入calcite源码中的语法模板工程

官方文档: calcite.apache.org/downloads/

对应源码包地址: github.com/apache/calc…

将红框中的目录迁移到新建工程的对应目录下

编写自定义SqlNode解析类(具体语法的解析规则)

现在假设我们想实现这样一条Sql: submit job as 'select * from test

代表捕获submit job 关键字,执行as后面的语句,解析规则如下:

package org.apache.calcite.sql;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.util.SqlVisitor;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Litmus;

/**
 * sql提交类
 *
 */
public class SqlSubmit extends SqlNode {


    String jobString;

    /**
     * Creates a node.
     *
     * @param pos Parser position, must not be null.
     */
    public SqlSubmit(SqlParserPos pos, String jobString) {
        super(pos);
        this.jobString = jobString;
    }

    public String getJobString() {
        System.out.println("getJobString");
        return jobString;
    }


    
    public SqlNode clone(SqlParserPos pos) {
        return null;
    }

    
    //在进行SqlNode的语法解析之前做一些预解析
    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
        writer.keyword("submit");
        writer.keyword("job");
        writer.print("\n");
    }

    //SqlNode验证部分
    public void validate(SqlValidator validator, SqlValidatorScope scope) {

    }

    //SqlNode解析部分
    public <R> R accept(SqlVisitor<R> visitor) {
        return null;
    }

    //递归语法解析树的深度终止条件
    public boolean equalsDeep(SqlNode node, Litmus litmus) {
        return false;
    }
}
修改config.fmpp文件,指定自定义的语法解析类

意思就是自定义的SQL解析,会编译这个类文件中。

图中 class 的json节后面的属性指定为自己写的SQL解析类,以Flink SQL引擎为例

flink-sql-parser 模块下执行 mvn package -Dmaven.test.skip=true 构建这个模板,会在 target目录下生成如下文件FlinkSqlParserImpl

同理,在自定义SQL引擎工程中 marble-table-hive 模块执行相同的Maven命令构建,则会编译生成相应的HiveSqlParserImpl

修改Parser.jj文件

在文件中导入相关SqlNode的语法解析类

import org.apache.calcite.sql.SqlSubmit;

Parser.jj 加入下面的逻辑

/*
 * Sql Submit a job
*/

SqlNode SqlSubmit() :
{
    SqlNode stringNode;
}
{
    <SUBMIT> <JOB> <AS>
    stringNode = StringLiteral()
    {
        return new SqlSubmit(getPos(), token.image);
    }
}

声明SqlNode解析函数


</#if>
        stmt = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY)
    |
        stmt = SqlExplain()
    |
        stmt = SqlDescribe()
    |
        stmt = SqlInsert()
    |
        stmt = SqlDelete()
    |
        stmt = SqlUpdate()
    |
        stmt = SqlMerge()
    |
        stmt = SqlProcedureCall()
    |   //在此处添加自己需要解析的语句
        stmt = SqlSubmit()
    )
    {
        return stmt;
    }
}

对比自定义SQL引擎工程

自定义SqlMerge 的SqlNode的实现逻辑

这里51信用卡的团队是给calcite提了一个patch,里面重写了SqlMerge的自定义解析逻辑

参考工程提交的calcite-patch中的单元测试 org.apache.calcite.sql.parser.SqlParserTest#

假设需要实现这样一条SQL

 final String expected = "MERGE INTO `EMPS` AS `E`\n"
        + "USING (SELECT *\n"
        + "FROM `TEMPEMPS`\n"
        + "WHERE (`DEPTNO` IS NULL)) AS `T`\n"
        + "ON (`E`.`EMPNO` = `T`.`EMPNO`)\n"
        + "WHEN MATCHED THEN UPDATE SET `NAME` = `T`.`NAME`\n"
        + ", `DEPTNO` = `T`.`DEPTNO`\n"
        + ", `SALARY` = (`T`.`SALARY` * 0.1)\n"
        + "WHEN NOT MATCHED THEN INSERT (`NAME`, `DEPT`, `SALARY`) "
        + "(VALUES (ROW(`T`.`NAME`, 10, (`T`.`SALARY` * 0.15))))";
/**
 * A <code>SqlMerge</code> 是表示MERGE的分析树的节点的语句
 */
public class SqlMerge extends SqlCall {
  public static final SqlSpecialOperator OPERATOR =
      new SqlSpecialOperator("MERGE", SqlKind.MERGE);

  SqlNode targetTable;
  SqlNode condition;
  SqlNode source;
  SqlUpdate updateCall;
  SqlInsert insertCall;
  SqlSelect sourceSelect;
  SqlIdentifier alias;

  //~ Constructors -----------------------------------------------------------

  public SqlMerge(SqlParserPos pos,
      SqlNode targetTable,
      SqlNode condition,
      SqlNode source,
      SqlUpdate updateCall,
      SqlInsert insertCall,
      SqlSelect sourceSelect,
      SqlIdentifier alias) {
    super(pos);
    this.targetTable = targetTable;
    this.condition = condition;
    this.source = source;
    this.updateCall = updateCall;
    this.insertCall = insertCall;
    this.sourceSelect = sourceSelect;
    this.alias = alias;
  }

  //~ Methods ----------------------------------------------------------------

  public SqlOperator getOperator() {
    return OPERATOR;
  }

  @Override public SqlKind getKind() {
    return SqlKind.MERGE;
  }

  public List<SqlNode> getOperandList() {
    return ImmutableNullableList.of(targetTable, condition, source, updateCall,
        insertCall, sourceSelect, alias);
  }

  @Override public void setOperand(int i, SqlNode operand) {
    switch (i) {
    case 0:
      assert operand instanceof SqlIdentifier;
      targetTable = operand;
      break;
    case 1:
      condition = operand;
      break;
    case 2:
      source = operand;
      break;
    case 3:
      updateCall = (SqlUpdate) operand;
      break;
    case 4:
      insertCall = (SqlInsert) operand;
      break;
    case 5:
      sourceSelect = (SqlSelect) operand;
      break;
    case 6:
      alias = (SqlIdentifier) operand;
      break;
    default:
      throw new AssertionError(i);
    }
  }

  /**
   * @return 合并目标表的标识符
   */
  public SqlNode getTargetTable() {
    return targetTable;
  }

  /**
   * @return 合并目标表的别名
   */
  public SqlIdentifier getAlias() {
    return alias;
  }

  /**
   * @return 合并的来源
   */
  public SqlNode getSourceTableRef() {
    return source;
  }

  public void setSourceTableRef(SqlNode tableRef) {
    this.source = tableRef;
  }

  /**
   * @return 合并的更新语句
   */
  public SqlUpdate getUpdateCall() {
    return updateCall;
  }

  /**
   * @return 合并的插入语句
   */
  public SqlInsert getInsertCall() {
    return insertCall;
  }

  /**
   * @return 条件表达式,以确定是更新还是插入
   */
  public SqlNode getCondition() {
    return condition;
  }

  /**
   * 获取要更新/插入的数据的源SELECT表达式。
   *在语句被扩展之前,返回null
   * {@link SqlValidatorImpl#performUnconditionalRewrites(SqlNode,boolean)}。
   *
   * @返回源SELECT以更新要更新的数据
   */
  public SqlSelect getSourceSelect() {
    return sourceSelect;
  }

  public void setSourceSelect(SqlSelect sourceSelect) {
    this.sourceSelect = sourceSelect;
  }

  @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
    final SqlWriter.Frame frame =
        writer.startList(SqlWriter.FrameTypeEnum.SELECT, "MERGE INTO", "");
    final int opLeft = getOperator().getLeftPrec();
    final int opRight = getOperator().getRightPrec();
    targetTable.unparse(writer, opLeft, opRight);
    if (alias != null) {
      writer.keyword("AS");
      alias.unparse(writer, opLeft, opRight);
    }

    writer.newlineAndIndent();
    writer.keyword("USING");
    source.unparse(writer, opLeft, opRight);

    writer.newlineAndIndent();
    writer.keyword("ON");
    condition.unparse(writer, opLeft, opRight);

    if (updateCall != null) {
      writer.newlineAndIndent();
      //当匹配到WHEN MATCHED THEN UPDATE关键字
      writer.keyword("WHEN MATCHED THEN UPDATE");
      //转换需要Merge词法中的源表为数组
      final SqlWriter.Frame setFrame =
          writer.startList(
              SqlWriter.FrameTypeEnum.UPDATE_SET_LIST,
              "SET",
              "");

	//Merge操作词法解析,UPDATE语句中的更新源字段和条件字段
      for (Pair<SqlNode, SqlNode> pair : Pair.zip(
          updateCall.targetColumnList, updateCall.sourceExpressionList)) {
        writer.sep(",");
        SqlIdentifier id = (SqlIdentifier) pair.left;
        id.unparse(writer, opLeft, opRight);
        writer.keyword("=");
        SqlNode sourceExp = pair.right;
        sourceExp.unparse(writer, opLeft, opRight);
      }
      writer.endList(setFrame);
    }

    if (insertCall != null) {
      writer.newlineAndIndent();
      writer.keyword("WHEN NOT MATCHED THEN INSERT");
      if (insertCall.getTargetColumnList() != null) {
        insertCall.getTargetColumnList().unparse(writer, opLeft, opRight);
      }
      insertCall.getSource().unparse(writer, opLeft, opRight);

      writer.endList(frame);
    }
  }

  public void validate(SqlValidator validator, SqlValidatorScope scope) {
    validator.validateMerge(this);
  }
}

自定义SqlMerge函数声明

SqlNode SqlMerge() :
{
    SqlNode table;
    SqlNodeList extendList = null;
    SqlIdentifier alias = null;
    SqlNode sourceTableRef;
    SqlNode condition;
    SqlUpdate updateCall = null;
    SqlInsert insertCall = null;
    final Span s;
}

SqlNode转换为逻辑算子

HiveSqlOperatorTable.java

//SqlNode转换为逻辑算子 此处重写SqlOperatorTable类
    tableConfig.setSqlOperatorTable(
        ChainedSqlOperatorTable.of(HiveSqlOperatorTable.instance(),
            SqlStdOperatorTable.instance()));

进入HiveSqlOperatorTable的构造函数

参考calcite文档 SqlOperator : 运算符或者是udf都可以

https://calcite.apache.org/javadocAggregate/org/apache/calcite/sql/SqlOperator.html

意义:
SqlOperator是SQL解析树中的一种节点(不是SQL解析树中的节点)。
它包括函数,运算符(例如,“ =”)和句法构造(例如,“ case”语句)。
运算符可以表示查询级别的表达式(例如SqlSelectOperator或行级别的表达式(例如)SqlBetweenOperator。
运算符具有形式化的操作数,表示对其进行操作的值的有序(和可选的命名)占位符。
例如,除法运算符采用两个操作数;第一个是分子,第二个是分母。
在子类的上下文中SqlFunction,形式操作数称为参数。

实例化运算符时,将为SqlCall它提供实际的操作数。
例如,在表达式中3 / 5,文字表达式3是与分子相对应的实际操作数,
并且5是与分母相对应的实际操作数。
在SqlFunction的上下文中,实际的操作数称为参数

private static Set<String> hiveFunctionPackages = new HashSet<>();


public HiveSqlOperatorTable() {
    for (SqlOperator op : operatorListOfSqlStdOperatorTable) {
      operatorMapOfSqlStdOperatorTable.put(op.getName() + "_" + op.getSyntax(),
          op);
    }
    //register hive operator,注册hive的运算符
    ConfigurationBuilder builder = new ConfigurationBuilder();
    //说实在其实蛮奇怪,干嘛要注册这个路径,虽然看包名感觉应该是sql查询用的,保险起见还是去看了下hive的文档
    // https://hive.apache.org/javadocs/r1.2.2/api/org/apache/hadoop/hive/ql/udf/package-summary.html
    //其实是把Hive原生提供的udf,预先注册为calcite的SqlOperactor
    hiveFunctionPackages.add("org.apache.hadoop.hive.ql");
    builder.forPackages(hiveFunctionPackages.toArray(new String[0]));
    builder.setExpandSuperTypes(false);
    Reflections reflections = new Reflections(builder);
    //注册UDF函数
    registerUDF(reflections);
    //注册UDAF函数
    registerUDAF(reflections);
  }
注册UDF函数
private void registerUDF(Reflections reflections) {
    //合并所有原生UDF,以及基于原生UDF拓展的UDF类
    Set<Class> udfClasses = Sets.union(
        reflections.getSubTypesOf(GenericUDF.class),
        reflections.getSubTypesOf(UDF.class));
    for (Class clazz : udfClasses) {
      //获取UDF元信息
      Description desc = (Description) clazz.getAnnotation(Description.class);
      //对于需要被排除的udf,跳过
      if (desc == null || EXCLUDED_HIVE_UDF_LIST.contains(clazz)) {
        continue;
      }
      String[] names = desc.name().split(",");
      for (int i = 0; i < names.length; i++) {
        names[i] = names[i].trim();
      }
      for (String name : names) {
        String upName = name.toUpperCase();
        registerUDF(upName, clazz);
      }
    }
  }

这里 @Description 代表一个UDF的元信息,下面以UDFAtan举例

@Description(
    name = "atan",
    value = "_FUNC_(x) - returns the atan (arctan) of x (x is in radians)",
    extended = "Example:\n   > SELECT _FUNC_(0) FROM src LIMIT 1;\n  0"
)
@VectorizedExpressions({FuncATanLongToDouble.class, FuncATanDoubleToDouble.class})
public class UDFAtan extends UDFMath {
    private final DoubleWritable result = new DoubleWritable();

    public UDFAtan() {
    }

    protected DoubleWritable doEvaluate(DoubleWritable a) {
        this.result.set(Math.atan(a.get()));
        return this.result;
    }
}
注册UDF函数的细节
1.解析udf类为calcite的自定义函数
2.将解析后的得到的calcite自定义函数包装成一个运算符
3.如果为空,说明在语法模板阶段中没有定义,重新注册为hive udf
4.如果不为空,calcite将尝试从OP Table查找新的SqlOperator。在派生类型时替换默认的SqlOperator
5.此时,如果该udf没有包装成SqlOperator,并且 目标运算符已经存在于SqlStdOperatorTable中,
以及倘若它的SqlSyntax是一个Function,建立了一个新的HiveSqlFunction来装饰它,
并保留该函数的原始SqlKind,因为 calcite查询优化器将使用该类型来选择是否执行
private void registerUDF(String upName, Class clazz) {
	//1.解析udf类为calcite的自定义函数
    SqlSyntax[] sqlSyntaxArray = getSqlSyntaxInCalcite(clazz);
    for (SqlSyntax sqlSyntax : sqlSyntaxArray) {
      methodsUDF.put(upName + "_" + sqlSyntax, clazz);
      /// 2.将解析后的得到的calcite自定义函数包装成一个运算符
      SqlOperator operatorInSqlStdOperatorTable =
          getOperatorInSqlStdOperatorTable(
              upName, sqlSyntax, false);
      if (operatorInSqlStdOperatorTable == null) {
        //3. 如果为空,说明在语法模板中没有定义,从新注册为hive udf
        register(
            new HiveSqlFunction(upName,
                HiveSqlUDFReturnTypeInference.INSTANCE));
      } else {
        /**
         * calcite将尝试从OP Table查找新的SqlOperator
         * 在派生类型时替换默认的SqlOperator,
         * 它提供了注入定制Sqloperator的机会。
         * 请参见{@link SqlOperator#deriveType}中的详细信息:
         * <code>
         *      final SqlOperator sqlOperator =
         *         SqlUtil.lookupRoutine(validator.getOperatorTable(),
         *         getNameAsId(),
         *             argTypes, null, null, getSyntax(), getKind());
         *
         *     ((SqlBasicCall) call).setOperator(sqlOperator);
         * </code>
         */
        if (operatorInSqlStdOperatorTable.getSyntax()
            == SqlSyntax.FUNCTION) {
          /**
           * 如果目标运算符已经存在于SqlStdOperatorTable中,并且
           * 它的SqlSyntax是一个Function,我们建立了一个新的HiveSqlFunction来
           * 装饰它并保留该函数的原始SqlKind,因为
           * calcite查询优化器将使用该类型来选择是否执行
           * 优化。
           **/
          if (operatorInSqlStdOperatorTable == SqlStdOperatorTable.TRIM) {
            break;
          }
          SqlFunction functionInStd =
              (SqlFunction) operatorInSqlStdOperatorTable;
          SqlOperator newOp = new HiveSqlFunction(
              functionInStd.getNameAsId(),
              functionInStd.getKind(),
              HiveSqlUDFReturnTypeInference.INSTANCE,
              functionInStd.getFunctionType());
          register(newOp);
        } else {
          SqlOperator newOp;
          //根据运算符,解析运算符的左表达式和右表达式
          // select a,b from c where b = 1 ,其中 b为左表达式 ,1为右表达式
          switch (sqlSyntax) {
          case BINARY:
            if (operatorInSqlStdOperatorTable
                == SqlStdOperatorTable.PERCENT_REMAINDER) {
              /**
               *  use the default operator, SqlStdOperatorTable
               *  .PERCENT_REMAINDER
               *  see{@link ReflectiveConvertletTable#addAlias}
               */
              break;
            }
            newOp = new SqlBinaryOperator(upName,
                operatorInSqlStdOperatorTable.getKind(),
                operatorInSqlStdOperatorTable.getLeftPrec(),
                operatorInSqlStdOperatorTable.getRightPrec(),
                HiveSqlUDFReturnTypeInference.INSTANCE, null,
                HiveSqlFunction.ArgChecker.INSTANCE);
            register(newOp);
            break;
          
          case PREFIX:
            newOp = new SqlPrefixOperator(upName,
                operatorInSqlStdOperatorTable.getKind(),
                operatorInSqlStdOperatorTable.getLeftPrec(),
                operatorInSqlStdOperatorTable.getRightPrec(),
                HiveSqlUDFReturnTypeInference.INSTANCE, null,
                HiveSqlFunction.ArgChecker.INSTANCE);
            register(newOp);
            break;
          case SPECIAL:
            break;
          default:
            break;
          }
        }
      }
    }
  }

将逻辑算子转换为RexNode

1.根据这个SqlNode的表达式,分别左表达式和右表达式
2. 假设现在存在一个时间窗口查询,包含以下表达式
    /**
 	  * INTERVAL '1:23:45.678' HOUR TO SECOND
 	  * INTERVAL '1 2:3:4' DAY TO SECOND
 	  * INTERVAL '1 2:3:4' DAY(4) TO SECOND(4)
 	  **/
3.如果右节点是包含 HOUR,DAY等一些Interval表达式是SqlNode,则转换为相应的SqlNode子类
此时如果左节点为Interval表达式的常量值(带精度的),则进行一些常量精度的转换
4.如果左节点只是数字常量,也就是存在表达式中间没有带:的,
则针对数字部分做兼容,:连接的表达式分别进行验证
 例如: INTERVAL '1 2:3:4' DAY TO SECOND
5.兼容类型转换的时候出现空值
//例如 hive cast: select cast('' as double) 将返回空,我们可以使他设置为默认为空的值
6.将兼容好的SqlNode子类转换为RexNode,准备从RexNode翻译成RelNode节点,预先尝试兼容成RelNode支持的数据类型
7.通过RexNode工厂创建表达式,此处还未转换成RelNode物理算子节点,为一个携带将要转换为的RelNodesh
RexNode定义
RexNode: 行表达式。
每个行表达式都有一个类型。(与相比SqlNode,它是在验证之前创建的,因此类型可能不可用。)

一些常见的行表达式是:(RexLiteral常数值), RexVariable(变量),RexCall(使用操作数调用运算符)。
通常使用RexBuilder 工厂创建表达式。

RexNode的所有子类都是不可变的。
//将逻辑算子转换为RelNode,此处重写ConvertletTable类
    tableConfig.setConvertletTable(new HiveConvertletTable());

同样进入HiveConvertletTable类的构造函数

public HiveConvertletTable() {
    super();
    registerOp(HiveSqlOperatorTable.CAST, this::convertCast);
  }
  //这里会调用此类的convertCast方法,把逻辑算子转为SqlRelNode
  protected void registerOp(SqlOperator op, SqlRexConvertlet convertlet) {
        this.map.put(op, convertlet);
    }

HiveConvertletTable#convertCast

将SqlNode转换为RexNode

可以看出此方法是实现SqlRexConvertlet 类的方法接口 此类归属于calcite原生模块calcite-core的目录下 calcite官方文档: calcite.apache.org/javadocAggr…

@Override protected RexNode convertCast(SqlRexContext cx,
      SqlCall call) {
      
    RelDataTypeFactory typeFactory = cx.getTypeFactory();
    assert call.getKind() == SqlKind.CAST;
    //根据这个SqlNode的表达式,分别左表达式和右表达式
    final SqlNode left = call.operand(0);
    final SqlNode right = call.operand(1);
    // 假设现在存在一个时间窗口查询,包含以下表达式
    /**
 	  * INTERVAL '1:23:45.678' HOUR TO SECOND
 	  * INTERVAL '1 2:3:4' DAY TO SECOND
 	  * INTERVAL '1 2:3:4' DAY(4) TO SECOND(4)
 	  **/
     //如果右节点是包含 HOUR,DAY等一些Interval表达式是SqlNode,则转换为相应的SqlNode子类
    if (right instanceof SqlIntervalQualifier) {
      final SqlIntervalQualifier intervalQualifier =
          (SqlIntervalQualifier) right;
       //此时如果左节点为Interval表达式的常量值(带精度的),则进行一些常量精度的转换
       // INTERVAL '1:23:45.678' HOUR TO SECOND
      if (left instanceof SqlIntervalLiteral) {
        RexLiteral sourceInterval =
            (RexLiteral) cx.convertExpression(left);
        BigDecimal sourceValue =
            (BigDecimal) sourceInterval.getValue();
        RexLiteral castedInterval =
            cx.getRexBuilder().makeIntervalLiteral(sourceValue,
                intervalQualifier);
        return castToValidatedType(cx, call, castedInterval);
       //如果左节点只是数字常量,也就是存在表达式中间没有带:的,则针对数字部分做兼容,:连接的表达式分别进行验证
       //INTERVAL '1 2:3:4' DAY TO SECOND
      } else if (left instanceof SqlNumericLiteral) {
        RexLiteral sourceInterval =
            (RexLiteral) cx.convertExpression(left);
        BigDecimal sourceValue =
            (BigDecimal) sourceInterval.getValue();
        final BigDecimal multiplier = intervalQualifier.getUnit().multiplier;
        sourceValue = sourceValue.multiply(multiplier);
        RexLiteral castedInterval =
            cx.getRexBuilder().makeIntervalLiteral(
                sourceValue,
                intervalQualifier);
        return castToValidatedType(cx, call, castedInterval);
      }
      return castToValidatedType(cx, call, cx.convertExpression(left));
    }
    
    SqlDataTypeSpec dataType = (SqlDataTypeSpec) right;
    //例如 hive cast: select cast('' as double) 将返回空,
    // 我们可以使他设置为默认为空的值作为后续创建物理算子节点的数据类型
    RelDataType type = dataType.deriveType(typeFactory, true);
    if (SqlUtil.isNullLiteral(left, false)) {
      final SqlValidatorImpl validator = (SqlValidatorImpl) cx.getValidator();
      validator.setValidatedNodeType(left, type);
      return cx.convertExpression(left);
    }
    //SqlNode转换为RexNode
    RexNode arg = cx.convertExpression(left);
    if (type == null) {
      type = cx.getValidator().getValidatedNodeType(dataType.getTypeName());
    }
    //RelNode 物理算子节点是否为空
    if (arg.getType().isNullable()) {
      //设置RelNode物理算子节点的数据类型
      type = typeFactory.createTypeWithNullability(type, true);
    }
    
    //准备从RexNode翻译成RelNode节点,预先尝试兼容成RelNode支持的数据类型
    //查询这是否是结构化类型。
    //
    // @return此类型是否具有字段;示例包括行和
    // SQL中用户定义的结构化类型,即RelDataTypeField,物理算子节点的每个字段类型是否为空
    if (null != dataType.getCollectionsTypeName()) {
      final RelDataType argComponentType =
          arg.getType().getComponentType();
      final RelDataType componentType = type.getComponentType();
      //
      if (argComponentType.isStruct()
          && !componentType.isStruct()) {
        RelDataType tt =
            typeFactory.builder()
                .add(
                    argComponentType.getFieldList().get(0).getName(),
                    componentType)
                .build();
            
        tt = typeFactory.createTypeWithNullability(
            tt,
            componentType.isNullable());
        boolean isn = type.isNullable();
        type = typeFactory.createMultisetType(tt, -1);
        type = typeFactory.createTypeWithNullability(type, isn);
      }
    }
    return cx.getRexBuilder().makeCast(type, arg);
  }

简化RexNode 表达式,为每个表达式写一个文字到列表中

1.从将逻辑算子转RexNode步骤中得到RexBuildler,再将一系列RexNode节点翻译成java代码,
传入表结构,查询语句表达式等一些信息
2.从上一步得到兼容后的RexNode节点中获取RexBuilder,解析待转换的RelNode数据类型
3.通过DataContext连接到数据库,查询相关表达式,这里的DataContext需要自己定义
4.查询SQL语义遵循指定数据源(此处为Hive)
5.为每一个sql都可以拆分成多个表达式。
这里所有表达式的构建由BlockBuilder进行构建完成之后,一个表达式对应一个block
6.将一些自定义的udf函数,这里FUNCTION1_APPLY指代的是scala的解构函数,
可以理解为java中的构造函数,通过反射得到这个类的构造函数后
得到方法声明,记录到生成的表达式集合中
7.将表达式列表转换为Java源代码
//简化RexNode表达式,为每个表达式写一个文字到列表中
    RexExecutor rexExecutor = new HiveRexExecutorImpl(
        Schemas.createDataContext(null, null));
    tableConfig.setRexExecutor(rexExecutor);

我们知道在java语言中,类初始化所实现接口的时候,会先调用实现接口的方法 也就是这里的 reduce方法,进一步用来简化RexNode表达式

53454.png

精简RexNode表达式
/** Can reduce expressions, writing a literal for each into a list. */
public interface RexExecutor {

  / **精简表达式,并将其结果写入{@code ReducedValues}中。
   *
   * 如果无法将表达式简化,则写入原始表达式。
   * 例如,{@ code CAST('abc'AS INTEGER)}在执行时给出错误,因此执行者
   * 忽略错误并写入原始表达式。
   *
   * @param rexBuilder Rex生成器
   * @param constExps 精简后的RexNode表达式
   * @param ReducedValues附加了简化表达式的列表
  void reduce(RexBuilder rexBuilder, List<RexNode> constExps, List<RexNode> reducedValues);
}
将一系列RexNode节点翻译成java代码,执行精简后的表达式

HiveRexExecutorImpl.java


@Override
public void reduce(RexBuilder rexBuilder, List<RexNode> constExps,
      List<RexNode> reducedValues) {
    //将一系列RexNode节点翻译成java代码,传入表结构,查询语句表达式等一些信息
    final String code = compile(rexBuilder, constExps,
        (list, index, storageType) -> {
          throw new UnsupportedOperationException();
        });

    final RexExecutable executable = new RexExecutable(code, constExps);
    //连接到对应数据源
    executable.setDataContext(dataContext);
    //执行精简后的表达式
    executable.reduce(rexBuilder, constExps, reducedValues);
  }

HiveRexExecutorImpl.java

//DataContext: 运行时上下文允许访问数据库中的表。
private final DataContext dataContext;



public HiveRexExecutorImpl(DataContext dataContext) {
    this.dataContext = dataContext;
  }
  
  
 /**
   * 解析待转换的RelNode数据类型
   * @param rexBuilder 构建的RexNode SQL表达式
   * @param constExps 精简后的表达式
   * @param getter 翻译为行的物理类型
   * @return
   */
  private String compile(RexBuilder rexBuilder, List<RexNode> constExps,
      RexToLixTranslator.InputGetter getter) {

    //从上一步得到兼容后的RexNode节点中获取RexBuilder,解析待转换的RelNode数据类型
    final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
    final RelDataType emptyRowType = typeFactory.builder().build();
    return compile(rexBuilder, constExps, getter, emptyRowType);
  }

  /**
   * 将RexNode翻译成java代码 
   * @param rexBuilder 构建的RexNode SQL表达式
   * @param constExps 精简后的表达式
   * @param getter 翻译为行的物理类型
   * @param rowType 每行数据的物理数据结构,包括字段类型,表结构
   * @return
   */
  private String compile(RexBuilder rexBuilder, List<RexNode> constExps,
      RexToLixTranslator.InputGetter getter, RelDataType rowType) {
    final RexProgramBuilder programBuilder =
        new RexProgramBuilder(rowType, rexBuilder);
    for (RexNode node : constExps) {
      programBuilder.addProject(
          node, "c" + programBuilder.getProjectList().size());
    }
    final JavaTypeFactoryImpl javaTypeFactory =
        new JavaTypeFactoryImpl(rexBuilder.getTypeFactory().getTypeSystem());
    final BlockBuilder blockBuilder = new BlockBuilder();
    final ParameterExpression root0_ =
        Expressions.parameter(Object.class, "root0");
    final ParameterExpression root_ = DataContext.ROOT;
    //连接到数据库,查询相关表达式
    blockBuilder.add(
        Expressions.declare(
            Modifier.FINAL, root_,
            Expressions.convert_(root0_, DataContext.class)));
    //指定查询SQL语义遵循Hive
    final SqlConformance conformance = SqlConformanceEnum.HIVE;
    final RexProgram program = programBuilder.getProgram();

    //calcite引入了block的概念,他认为每一个sql都可以拆分成多个表达式
    //这里所有表达式的构建由BlockBuilder进行构建完成之后,一个表达式对应一个block
    final List<Expression> expressions =
        RexToLixTranslator.translateProjects(program, javaTypeFactory,
            conformance, blockBuilder, null, root_, getter, null);
    blockBuilder.add(
        Expressions.return_(null,
            Expressions.newArrayInit(Object[].class, expressions)));
    //描述一些自定义的udf函数,这里FUNCTION1_APPLY指代的是scala的解构函数,
    // 可以理解为java中的构造函数,通过反射得到这个类的构造函数后
    // 得到方法声明,记录到生成的表达式集合中
    final MethodDeclaration methodDecl =
        Expressions.methodDecl(Modifier.PUBLIC, Object[].class,
            BuiltInMethod.FUNCTION1_APPLY.method.getName(),
            ImmutableList.of(root0_), blockBuilder.toBlock());
    //将表达式列表转换为Java源代码
    String code = Expressions.toString(methodDecl);
    if (CalcitePrepareImpl.DEBUG) {
      Util.debugCode(System.out, code);
    }
    return code;
  }