开发postgreSQL connector支持update/delete操作的代码

104 阅读6分钟

背景与需求

当前openLooKeng postgreSQL connector不支持对数据表的update和delete操作,本文拟在openLooKeng框架的基础上,开发postgreSQL connector支持update/delete操作的代码。

分析

openLooKeng语句执行流程:

一条SQL语句的执行的整体流程如下图所示:

这里面主要涉及到两种类型的节点,分别为Coordinator和Worker节点。

  • Coordinator 节点是用来解析语句,执行计划分析和管理 openLooKeng 的 Worker 节点,同时Coordinator 跟踪每个 Work 的活动情况并协调查询语句的执行。Coordinator 为每个查询建立模型,模型包含多个Stage,每个Stage再转为Task 分发到不同的 Worker 上执行。
  • Worker 是负责执行任务和处理数据。Worker 从 Connector 获取数据。Worker 之间会交换中间数据。Coordinator 是负责从 Worker 获取结果并返回最终结果给 Client。

具体步骤为:

  1. 客户端通过协议发送一条查询语句给openLooKeng集群的Coordinator;
  2. Coordinator接手到客户端传送到的查询语句,对语句进行解析、生成查询执行计划,根据查询执行计划一次生成sqlQueryExecution->sqlStageExecution->HttpRemoteTask;
  3. Coordinator将每个task分发到所需要处理的数据所在的Worker节点上执行;
  4. Task通过Connector从数据源中读取数据,并执行Source Stage的task;
  5. 处于下游的Stage中的task会读取上游Stage产生的输出结果,并在该Stage的每个task所处的Worker的内存中进行后续处理;
  6. Coordinator从发布的task后,持续地从Singe Stage中的task获取结果,并将结果缓存到buffer中,知道查询结束;
  7. Client从提交查询之后,会持续地从Coordinator获取本次查询的计算结果,知道获得所有的计算结果。

openLooKeng内核调用

openLooKeng的多源查询能力是通过 Connector 机制来实现的。其中MySQL、postgreSQL等Connector是主要是通过presto-base-jdbc中的代码来实现对SQL等数据源的读写。

其中openLooKeng-spi中主要定义了一些公共接口,供openLooKeng-main中的代码进行调用。

openLooKeng-base-jdbc是数据库连接器的公共模块,对openLooKeng-main进行了实现和补充。其代码经过编译后,会对应加载到mysql-plugin等插件中,实现mysql对数据源的访问功能。base-jdbc本身自己不会编译单独的插件。

通过断点调试,对openLooKeng接受一条update SQL语句的函数栈调用流程进行梳理总结,如下图所示。

结合源码分析,发现一条update语句在connector jdbc中的执行流程为beginUpdate()->getConnection()->buildUpdateSql()->setUpdateSql()->finishUpdate()。在这个过程中需要一个字段作为行标识符来表示数据行。

其中buildUpdateSql将openLooKeng传入的update SQL语句转换为connector更新时的预编译语句,这里用到获取的行标识符作为where的筛选条件;setUpdateSql作用是以where条件对数据行进行更新。

因此若想要实现postgreSQL conncetor的update/delete table功能,我们需要重写上述函数即可完成。

代码实现

方案设计及论证

通过前期的调研,共形成了以主键为标识符和以ctid字段为标识符的两种开发方案。

方案一:以主键为标识符

主键的定义为:表中经常有一个列或列的组合,其值能唯一地标识表中的每一行。由定义可知,主键对应到一张数据表中会设计到多种场景,分别为:没有主键、单字段主键以及多字段组合成的复合主键,因此需要考虑的情况较多。

方案二:以ctid字段为标识符

ctid表示数据行在它所处的表内的物理位置,ctid字段的类型是tid。尽管ctid可以快速定位数据行,每次vacuum full之后,数据行在块内的物理位置就会移动,即ctid会发生变化,所以ctid不能作为长期的行标识符。因此如果要以ctid字段为标识符进行代码开发,需首先验证ctid在同一事务中进行update/delete操作是否会发生变化。

ctid在同一事务中的变化情况

为了验证ctid字段能否用于postgreSQL的update/delete操作,我们分别在单线程和多线程的情况下去验证ctid在同一事务和不同事务的变化情况。

单线程

通过jdbc模拟了单线程,在线程中对数据表进行update操作,分别查询update前后的信息,观察ctid的变化情况。

autocommit=false时,结果显示ctid未发生变化:

autocommit=true时,结果显示ctid发生变化:

多线程

同理,通过jdbc模拟了两个线程,在两个线程中分别对数据表进行update操作,分别查询update前后的信息,观察ctid的变化情况。

autocommit=false时,结果显示ctid未发生变化:

autocommit=true时,结果显示ctid发生变化:

由验证实验结果来看,在同一事务中,update未提交前或者回滚之后ctid是不变的。

开发方案选择

综合对比两种方案,其优缺点对比如下:

ctid字段为行标识符的代码开发

autocommit设置为false,即保证能在同一事务中进行操作,ctid是可以保证唯一性的。而PostgreSql的继承关系为

因此我们的主要开发重点在于重写beginUpdate()-> getConnection()-> buildUpdateSql-> setUpdateSql ->finishUpdate() getUpdateRowIdColumnHandle() 用于获取行标识符

beginUpdate()

getConnection重写BaseJdbcClient该方法,用于将只读模式设为false,以便于进行更新操作

buildUpdateSql将openLooKeng传入的update SQL语句转换为oracle更新时的预编译语句,其内部调用关系为

buildUpdateSql()
buildRemoteSchemaTableName()
getColumnNameFromDataSource()
getColumnNameMap()

其中buildRemoteSchemaTableName()获取数据表名,getColumnNameFromDataSource()用于从源数据中获取要更新的字段名,getColumnNameMap()用于构建字段名的映射关系,将原始字段统一转换为小写字母。具体实现如下图所示:

setUpdateSql是以where条件对数据行进行更新,其内部调用关系为

setUpdateSql()
setStatement()

其中setStatement()用于对需要更新的字段根据数据类型进行赋值。具体实现如下图所示:

到此,我们完成了postgreSQL connector的update功能代码,而delete功能代码与此具有很大的相似性,此处不做展开讲述,具体可参考PostgreSqlClient实现

功能展示

此处我们简单演示postgreSQL connector的update/delete特性。首先需对代码进行编译,运行 mvn clean install -DskipTests -T 1C 即可,编译完成之后,拷贝hetu-server-1.5.0-SNAPSHOT文件夹及hetu-cli-1.50-SNAPSHOT-executable.jar包。紧接着相关配置可参考社区文档。

在web界面输入ip及端口号,显示

数据表已经建好,具体实例为

针对update,执行语句及结果包括

功能点 SQL 结果