自己动手写数据库: 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.rhkb.cn/news/217326.html

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

相关文章

JWT令牌的作用和生成

JWT令牌&#xff08;JSON Web Token&#xff09;是一种用于身份验证和授权的安全令牌。它由三部分组成&#xff1a;头部、载荷和签名。 JWT令牌的作用如下&#xff1a; 身份验证&#xff1a;JWT令牌可以验证用户身份。当用户登录后&#xff0c;服务器会生成一个JWT令牌并返回…

小白学开源

第一步 访问程序员交友网站&#xff0c;注册GitHub账号&#xff0c;注册流程请自行了解&#xff0c;如果已有账户请登录 第二步 在搜索框输入你想查找的项目&#xff0c;例如cjson 第三步 选择你感兴趣的那个项目&#xff0c;这里选择排行最前的那个&#xff0c;它的收藏数有…

electron中获取mac地址

electron中获取mac地址 引入为了方便做单点登录&#xff0c;我们往往需要使用某个唯一标识来标记客户端的设备&#xff0c;mac地址就是一个还不错的选择思路 我们可以使用Node.js的内置模块os&#xff0c;调用其中的networkInterfaces方法。该方法会返回一个包含网络接口信息…

Navicat 技术指引 | 适用于 GaussDB 分布式的备份/还原功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

windows下mysql8修改my.ini设置datadir后无法启动问题

mysql默认数据库文件在安装目录下。创建数据库的时候&#xff0c;并不能指定数据文件存放位置。这就很扯。它只能通过修改mysql.ini来更改数据文件存放目录&#xff0c;而且是一刀切的&#xff0c;没有办法做到数据文件和日志文件分开存放。假如服务器有多个盘&#xff0c;就不…

SpringBoot的Starter自动化配置,自己编写配置maven依赖且使用及短信发送案例

目录 一、Starter机制 1. 是什么 2. 有什么用 3. 应用场景 二、短信发送案例 1. 创建 2. 配置 3. 编写 4. 形成依赖 6. 其他项目的使用 每篇一获 一、Starter机制 1. 是什么 SpringBoot中的starter是一种非常重要的机制(自动化配置)&#xff0c;能够抛弃以前繁杂…

vite(一)——基本了解和依赖预构建

文章目录 一、什么是构建工具&#xff1f;1.为什么使用构建工具&#xff1f;2.构建工具的作用&#xff1f;3.构建工具怎么用&#xff1f; 二、经典面试题&#xff1a;webpack和vite的区别1.编译方式不同2.基础概念不同3.开发效率不同4.扩展性不同5.应用场景不同6.总结&#xff…

详细说说vuex

Vuex 是什么 Vuex有几个属性及作用注意事项vuex 使用举例Vuex3和Vuex4有哪些区别 创建 Store 的方式在组件中使用 Store辅助函数的用法响应式的改进Vuex4 支持多例模式 Vuex 是什么 Vuex是一个专门为Vue.js应用设计的状态管理构架&#xff0c;它统一管理和维护各个Vue组件的可…

C语言趣练习:两个字符串不用strcmp函数怎么比较大小

目录 1习题一&#xff1a;两个字符串不用strcmp函数怎么比较大小&#xff0c;并输出其差值 2不用strcpy函数将s2字符串中内容复制到s1中 3译密码问题 4总结&#xff1a; 1习题一&#xff1a;两个字符串不用strcmp函数怎么比较大小&#xff0c;并输出其差值 解题思路&#x…

MYSQL练题笔记-子查询-换座位

一、题目相关内容 1&#xff09;相关的表和题目 2&#xff09;帮助理解题目的示例&#xff0c;提供返回结果的格式 二、自己初步的理解 没啥思路&#xff0c;我还没做过交换的这种题&#xff0c;所以我觉得这类交换的题以后值得做一个合集&#xff0c;是有点灵活度在里面的&a…

【mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar安装】linux安装mysql5.7

之前安装的时候遇到了很多问题&#xff0c;浪费了一些时间。整理出这份教程&#xff0c;照着做基本一遍过。 这是安装包: 链接&#xff1a;https://pan.baidu.com/s/1gBuQBjA4R5qRYZKPKN3uXw?pwd1nuz 1.下载安装包&#xff0c;上传到linux。我这里就放到downloads目录下面…

猫粮选什么牌子好?好的主食冻干猫粮品牌十大排行

主食冻干猫粮作为一个猫咪的新主食产品越来越频繁的出现在各位猫主人们的视野中&#xff0c;但是国内的人对主食冻干猫粮的认知却仅仅止步于它是生肉制作而成&#xff0c;可以当作主食长期喂养猫咪&#xff0c;对于主食冻干猫粮的选购并没有更深入的思考和探索&#xff0c;如今…

大象elephant目标检测数据集VOC+YOLO格式2300张

大象是长鼻目象科的哺乳动物&#xff0c;有两个属&#xff0c;是世界上最大的陆生动物。其像柱子一样的四肢和宽厚的脚掌可以稳稳支撑住庞大的身体。巨大的头上长有蒲扇状的大耳朵和长且有弹性的鼻子。象耳上有丰富的血管&#xff0c;可以有效散热。鼻子和上唇合而为一的象鼻由…

【MATLAB】基于SVMD分解的信号去噪算法(基础版)

代码的使用说明 【MATLAB】基于SVMD去噪的信号去噪算法&#xff08;基础版&#xff09; 代码的原理 1.SVMD原理 连续变分模式分解&#xff08;Successive Variational Mode Decomposition&#xff0c;SVMD&#xff09;是一种用于将混合信号根据其频率特性分离成各个独立分量的…

Nginx rewrite 参数

目录 常用的Nginx 正则表达式 rewrite 和 location的区别 location location 大致分三类&#xff1a; location 常用的匹配规则&#xff1a; location 优先级&#xff1a; rewrite rewrite跳转实现 rewrite 执行顺序如下 语法格式 flag标记说明 rewrite实际操作 基…

Ubuntu服务设置

0. systemd介绍 Linux系统的systemd是第一个运行的进程&#xff0c;用来创建系统的守护进程&#xff0c;管理整个Linux系统。systemd支持并行启动守护进程&#xff0c;systemd初始化整个系统所需的资源。 0.1 systemd基本命令 systemctl systemctl是systemd中最基本的命令&a…

SQL命令---修改字段的数据类型

介绍 使用sql语句修改字段的数据类型。 命令 alter table 表明 modify 字段名 数据类型;例子 有一张a表&#xff0c;表里有一个id字段&#xff0c;长度为11。使用命令将长度修改为12 下面使用命令进行修改&#xff1a; alter table a modify id int(12) NOT NULL;下面使修…

绿盟 SAS堡垒机 local_user.php 权限绕过漏洞复现

绿盟 SAS堡垒机 local_user.php 权限绕过漏洞复现 一、 产品简介二、漏洞概述三、 复现环境四、漏洞复现五、小龙检测 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&…

Java数据类型相关

数据类型 Java有哪些数据类型 定义&#xff1a;Java语言是强类型语言&#xff0c;对于每一种数据都定义了明确的具体的数据类 型&#xff0c;在内存中分配了不同大小的内存空间。 分类&#xff1a; 基本数据类型 数值型 整数类型(byte,short,int,long) 浮点类型(float,dou…

实战章节:在Linux上部署各类软件

详细资料见文章的资源绑定 一、前言 1.1 为什么学习各类软件在Linux上的部署 在前面&#xff0c;我们学习了许多的Linux命令和高级技巧&#xff0c;这些知识点比较零散&#xff0c;同学们跟随着课程的内容进行练习虽然可以基础掌握这些命令和技巧的使用&#xff0c;但是并没…