自己动手写数据库: select 查询语句对应查询树的构造和执行

首先我们需要给原来代码打个补丁,在SelectScan 结构体初始化时需要传入 UpdateScan 接口对象,但很多时候我们需要传入的是 Scan 对象,因此我们需要做一个转换,也就是当初始化 SelectScan 时,如果传入的是 Scan 对象,那么我们就将其封装成 UpdateScan 接口对象,因此在 query 目录下增加一个名为 updatescan_wrapper.go 的文件,在其中输入内容如下:

package queryimport ("record_manager"
)type UpdateScanWrapper struct {scan Scan
}func NewUpdateScanWrapper(s Scan) *UpdateScanWrapper {return &UpdateScanWrapper{scan: s,}
}func (u *UpdateScanWrapper) GetScan() Scan {return u.scan
}func (u *UpdateScanWrapper) SetInt(fldName string, val int) {//DO NOTHING
}func (u *UpdateScanWrapper) SetString(fldName string, val string) {//DO NOTHING
}func (u *UpdateScanWrapper) SetVal(fldName string, val *Constant) {//DO NOTHING
}func (u *UpdateScanWrapper) Insert() {//DO NOTHING
}func (u *UpdateScanWrapper) Delete() {//DO NOTHING
}func (u *UpdateScanWrapper) GetRid() *record_manager.RID {return nil
}func (u *UpdateScanWrapper) MoveToRid(rid *record_manager.RID) {// DO NOTHING
}

上面代码逻辑简单,如果调用 Scan 对象接口时,他直接调用其 Scan 内部对象的接口,如果调用到 UpdateScan 的接口,那么它什么都不做。完成上面代码后,我们在select_plan.go 中进行一些修改:

func (s *SelectPlan) Open() interface{} {scan := s.p.Open()updateScan, ok := scan.(query.UpdateScan)if !ok {updateScanWrapper := query.NewUpdateScanWrapper(scan.(query.Scan))return query.NewSelectionScan(updateScanWrapper, s.pred)}return query.NewSelectionScan(updateScan, s.pred)
}

上面代码在创建 SelectScan 对象时,先判断传进来的对象是否能类型转换为 UpdateScan,如果不能,那意味着s.p.Open 获取的是 Scan 对象,因此我们使用前面的代码封装一下再用来创建 SelectScan 对象。完成这里的修改后,我们进入正题。

前面我们在实现 sql 解析器后,在解析完一条查询语句后会创建一个 QueryData 对象,本节我们看看如何根据这个对象构建出合适的查询规划器(Plan)。我们将采取由简单到负责的原则,首先我们直接构建 QueryData 的信息去构建查询规划对象,此时我们不考虑它所构造的查询树是否足够优化,后面我们再慢慢改进构造算法,直到算法能构建出足够优化的查询树。

我们先看一个具体例子,假设我们现在有两个表 STUDENT, EXAM,第一个表包含两个字段分别是学生 id 和姓名:

idname
1Tom
2Jim
3John

第二个表包含的是学生 id,科目名称,考试乘机:

stuidexamgrad
1mathA
1algorithmB
2writingC
2physicsC
3chemicalB
3englishC

现在我们使用 sql 语句查询所有考试成绩得过 A 的学生:

select name from STUDENT, EXAM where id = student_id and grad='A'

当 sql 解释器读取上面语句后,他就会创建一个 QueryData 结构,里面 Tables 对了就包含两个表的名字,也就是 STUDENT, EXAM。由于这两个表不是视图,因此上面代码中判断 if viewDef != nil 不成立,于是进入 else 部分,也就是代码会为这两个表创建对应的 TablePlan 对象,接下来直接对这两个表执行 Product 操作,也就是将左边表的一行跟右边表的每一行合起来形成新表的一行,Product 操作在 STUDENT 和 EXAM 表后所得结果如下:

idnamestudent_idexamgrad
1Tom1mathA
1Tom1algorithmB
1Tom2writingA
1Tom2physicsC
1Tom3chemicalB
1Tom3englishA

接下来代码创建 ScanSelect 对象在上面的表上,接着获取该表的每一行,然后检测该行的 id 字段是否跟 student_id 字段一样,如果相同,那么查看其 grad 字段,如果该字段是’A’,就将该行的 name 字段显示出来。

下面我们看看如何使用代码把上面描述的流程实现出来。首先我们先对接口进行定义,在 Planner 目录下的 interface.go 文件中增加如下内容:

type QueryPlanner interface {CreatePlan(data *query.QueryData, tx tx.Transaction) Plan
}

接着在 Planner 目录下创建文件 query_planner.go,同时输入以下代码,代码的实现逻辑将接下来的文章中进行说明:

package plannerimport ("metadata_management""parser""tx"
)type BasicQueryPlanner struct {mdm *metadata_management.MetaDataManager
}func CreateBasicQueryPlanner(mdm *metadata_management.MetaDataManager) QueryPlanner {return &BasicQueryPlanner{mdm: mdm,}
}func (b *BasicQueryPlanner) CreatePlan(data *parser.QueryData, tx *tx.Transaction) Plan {//1,直接创建 QueryData 对象中的表plans := make([]Plan, 0)tables := data.Tables()for _, tblname := range tables {//获取该表对应视图的 sql 代码viewDef := b.mdm.GetViewDef(tblname, tx)if viewDef != nil {//直接创建表对应的视图parser := parser.NewSQLParser(viewDef)viewData := parser.Query()//递归的创建对应表的规划器plans = append(plans, b.CreatePlan(viewData, tx))} else {plans = append(plans, NewTablePlan(tx, tblname, b.mdm))}}//将所有表执行 Product 操作,注意表的次序会对后续查询效率有重大影响,但这里我们不考虑表的次序,只是按照//给定表依次执行 Product 操作,后续我们会在这里进行优化p := plans[0]plans = plans[1:]for _, nextPlan := range plans {p = NewProductPlan(p, nextPlan)}p = NewSelectPlan(p, data.Pred())return NewProjectPlan(p, data.Fields())
}

上面代码中 QueryData就是解析器在解析 select 语句后生成的对象,它的 Tables 数组包含了 select 语句要查询的表,所以上面代码的 CreatePlan 函数先从 QueryData 对象获得 select 语句要查询的表,然后使用遍历这些表,使用 NewProductPlan 创建这些表对应的 Product 操作,最后在 Product 的基础上我们再创建 SelectPlan,这里我们就相当于使用 where 语句中的条件,在 Product 操作基础上将满足条件的行选出来,最后再创建 ProjectPlan,将在选出的行基础上,将需要的字段选择出来。

下面我们测试一下上面代码的效果,首先在 main.go 中,我们先把 student, exam 两个表构造出来,代码如下:

func createStudentTable() (*tx.Transation, *metadata_manager.MetaDataManager) {file_manager, _ := fm.NewFileManager("student", 2048)log_manager, _ := lm.NewLogManager(file_manager, "logfile.log")buffer_manager := bmg.NewBufferManager(file_manager, log_manager, 3)tx := tx.NewTransation(file_manager, log_manager, buffer_manager)sch := record_manager.NewSchema()mdm := metadata_manager.NewMetaDataManager(false, tx)sch.AddStringField("name", 16)sch.AddIntField("id")layout := record_manager.NewLayoutWithSchema(sch)ts := query.NewTableScan(tx, "student", layout)ts.BeforeFirst()for i := 1; i <= 3; i++ {ts.Insert() //指向一个可用插槽ts.SetInt("id", i)if i == 1 {ts.SetString("name", "Tom")}if i == 2 {ts.SetString("name", "Jim")}if i == 3 {ts.SetString("name", "John")}}mdm.CreateTable("student", sch, tx)exam_sch := record_manager.NewSchema()exam_sch.AddIntField("stuid")exam_sch.AddStringField("exam", 16)exam_sch.AddStringField("grad", 16)exam_layout := record_manager.NewLayoutWithSchema(exam_sch)ts = query.NewTableScan(tx, "exam", exam_layout)ts.BeforeFirst()ts.Insert() //指向一个可用插槽ts.SetInt("stuid", 1)ts.SetString("exam", "math")ts.SetString("grad", "A")ts.Insert() //指向一个可用插槽ts.SetInt("stuid", 1)ts.SetString("exam", "algorithm")ts.SetString("grad", "B")ts.Insert() //指向一个可用插槽ts.SetInt("stuid", 2)ts.SetString("exam", "writing")ts.SetString("grad", "C")ts.Insert() //指向一个可用插槽ts.SetInt("stuid", 2)ts.SetString("exam", "physics")ts.SetString("grad", "C")ts.Insert() //指向一个可用插槽ts.SetInt("stuid", 3)ts.SetString("exam", "chemical")ts.SetString("grad", "B")ts.Insert() //指向一个可用插槽ts.SetInt("stuid", 3)ts.SetString("exam", "english")ts.SetString("grad", "C")mdm.CreateTable("exam", exam_sch, tx)return tx, mdm
}

然后我们用解析器解析select查询语句生成 QueryData 对象,最后使用BasicQueryPlanner创建好执行树和对应的 Scan 接口对象,最后我们调用 Scan 对象的 Next 接口来获取给定字段,代码如下:

func main() {//构造 student 表tx, mdm := createStudentTable()queryStr := "select name from student, exam where id = stuid and grad=\"A\""p := parser.NewSQLParser(queryStr)queryData := p.Query()test_planner := planner.CreateBasicQueryPlanner(mdm)test_plan := test_planner.CreatePlan(queryData, tx)test_interface := (test_plan.Open())test_scan, _ := test_interface.(query.Scan)for test_scan.Next() {fmt.Printf("name: %s\n", test_scan.GetString("name"))}}

上面代码运行后所得结果如下:
请添加图片描述
从运行结果看到,代码成功执行了 sql 语句并返回了所需要的字段。请感兴趣的同学在 B 站搜索 coding 迪斯尼,通过视频的方式查看我的调试演示过程,这样才能对代码的设计有更好的理解,代码下载:
链接: https://pan.baidu.com/s/16ftSp46cU5NLisScq-ftZg 提取码: js99

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/222075.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE笔试题精讲1

vue专题| ProcessOn免费在线作图,在线流程图,在线思维导图 VUE面试题视频 01-Vue组件之间通信方式有哪些? 1. 组件通信常⽤⽅式有以下8种: props $emit/$on $children/$parent $attrs/$listeners ref $root eventbus vuex 注意vue3中废弃的⼏个API https://v3-mig…

Python学习之——装饰器

Python学习之——装饰器 参考基础闭包概念装饰器系统自带的装饰器propertystaticmethodclassmethod 自定义装饰器函数的装饰器无参数有参数 类的装饰器无参数有参数 functools.wraps装饰器类 装饰器实现单例模式 参考 python装饰器的4种类型&#xff1a;函数装饰函数、函数装饰…

全志V3s之U-Boot

1、安装交叉编译器&#xff1a; ARM交叉编译器的官网&#xff1a;交叉编译器 a、使用wget下载&#xff1a; wget https://releases.linaro.org/components/toolchain/binaries/latest/arm-linux-gnueabihf/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf.tar.xzb、解…

mmseg上手自己的数据集

制作自己的数据集&#xff0c;VOC格式为例。 这三个文件包括数据集的名称。可以使用labelme脚本自动生成。 跟据预测类别修改配置文件 D:\projects\mmsegmentation-main\mmseg\datasets\voc.py 因为是voc格式的数据集&#xff0c;在这个文件里进行配置&#xff0c;修改成自己数…

每日分享,以元旦为题的诗词

元旦佳节即将来临&#xff0c;相信大家都会在朋友圈表达一下自己的情感&#xff0c;不管大家以前是怎么表达的&#xff0c;今天小编给你分享几首以元旦为题的几首诗&#xff0c;喜欢的朋友可以自取&#xff0c;想要更多免费的诗词&#xff0c;请自行百度或小程序搜索&#xff1…

SLAM算法与工程实践——相机篇:传统相机使用(2)

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址&#xff1a; SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

C++共享和保护——(3)静态成员

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 信念&#xff0c;你拿它没办法&#x…

Java 第8章 本章作业

目录 4.通过继承实现员工工资核算打印功能 6.父类和子类中通过this和super都可以调用哪些属性和方法 8.扩展如下的BankAccount类 10.判断测试类中创建的两个对象是否相等 11.向上转型&向下转型 12.equals和的区别 15.什么是多态,多态具体体现有哪些? 16. java的动…

Spring Bean基础

写在最前面: 本文运行的示例在我github项目中的spring-bean模块&#xff0c;源码位置: spring-bean 前言 为什么要先掌握 Spring Bean 的基础知识&#xff1f; 我们知道 Spring 框架提供的一个最重要也是最核心的能力就是管理 Bean 实例。以下是其原因&#xff1a; 核心组件…

新版Spring Security6.2案例 - Authentication用户名密码

前言&#xff1a; 前面有翻译了新版Spring Security6.2架构&#xff0c;包括总体架构&#xff0c;Authentication和Authorization&#xff0c;感兴趣可以直接点链接&#xff0c;这篇翻译官网给出的关于Authentication的Username/Password这页。 首先呢&#xff0c;官网就直接…

前端如何使用express写一个简单的服务

相信不少前端平常在日常工作中肯遇见过后端API接口没开发出来的时候吧 前端提升小技巧 自己使用nodejs——express ,koa&#xff0c;egg开发接口吧(本人比较喜欢egg和express) 今天先分享一下express 下面是一个简单的demo 1、首先咱们可以新建一个文件夹,创建一个app.js 下…

【开源软件】最好的开源软件-2023-第18名 OpenTelemetry

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

Keepalived+Nginx实现高可用(下)

一、背景 上篇文章介绍了基本的Keepalived的简单入门&#xff0c;但是针对预留的问题还有优化的空间。分别是下面3个问题: 1、如果仅仅只提供一个VIP的方式&#xff0c;会存在只有1台服务器处于实际工作&#xff0c;另外1台处于闲置状态。 势必存在成本资源浪费问题&#xff0c…

LLM之RAG实战(四):Self-RAG如何革命工业LLM

论文地址&#xff1a;https://arxiv.org/pdf/2310.11511.pdf Github地址&#xff1a;https://github.com/AkariAsai/self-rag 尽管LLM&#xff08;大型语言模型&#xff09;的模型和数据规模不断增加&#xff0c;但它们仍然面临事实错误的问题。现有的Retrieval-Augmented Gen…

一文讲清 QWidget 大小位置

一文讲清 QWidget 大小位置 前言 ​ QWidget 的位置基于桌面坐标系&#xff0c;以左上角为原点&#xff0c;向右x轴增加&#xff0c;向下y轴增加。 一、图解 ​ ​ 如上图所示&#xff0c;当窗口为顶层窗口时&#xff08;即没有任何父窗口&#xff09;&#xff0c;系统会自…

JVM的五大分区

1.方法区 方法区主要用来存储已在虚拟机加载的类的信息、常量、静态变量以及即时编译器编译后的代码信息。该区域是被线程共享的。 2.虚拟机栈 虚拟机栈也就是我们平时说的栈内存&#xff0c;它是为java方法服务的。每个方法在执行的 时候都会创建一个栈帧&#xff0c;用于存…

数据结构学习 12字母迷宫

dfs 回溯 剪枝 这个题和dfs有关&#xff0c;但是我之前没有接触过&#xff0c;我看了这一篇很好的文章&#xff0c;看完之后写的答案。 我觉得很好的总结&#xff1a; dfs模板 int check(参数) {if(满足条件)return 1;return 0; }void dfs(int step) {判断边界{相应操作}尝试…

【JUC】二十九、synchronized锁升级之轻量锁与重量锁

文章目录 1、轻量锁2、轻量锁的作用3、轻量锁的加锁和释放4、轻量级锁的代码演示5、重量级锁6、重量级锁的原理7、锁升级和hashcode的关系8、锁升级和hashcode关系的代码证明9、synchronized锁升级的总结10、JIT编译器对锁的优化&#xff1a;锁消除和锁粗化11、结语 &#x1f4…

基士得耶速印机印件故障解决方法和印刷机使用注意事项

基士得耶和理光两个品牌的一体化速印机同属于理光公司的两个不同品牌。基士得耶速印机的每个机型&#xff0c;都有和它通用的理光速印机的机型相对应。&#xff08;油墨版纸通用&#xff0c;外观一样&#xff0c;配件全部通用。&#xff09;速印机在印刷的时候&#xff0c;经常…

USB2.0 Spec 中文篇

体系简介 线缆 USB 是一种支持热拔插的高速串行传输总线&#xff0c;使用一对&#xff08;两根&#xff09;差分信号来传输数据&#xff0c;半双工。要求使用屏蔽双绞线。 供电 USB 支持 “总线供电” 和 “自供电” 两种供电模式。在总线供电方式下&#xff0c;设备最多可…