当前位置: 首页 > news >正文

自己动手写数据库: 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

相关文章:

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

首先我们需要给原来代码打个补丁&#xff0c;在SelectScan 结构体初始化时需要传入 UpdateScan 接口对象&#xff0c;但很多时候我们需要传入的是 Scan 对象&#xff0c;因此我们需要做一个转换&#xff0c;也就是当初始化 SelectScan 时&#xff0c;如果传入的是 Scan 对象&am…...

扬声器(喇叭)

扬声器(喇叭) 电子元器件百科 文章目录 扬声器(喇叭)前言一、扬声器(喇叭)是什么二、扬声器(喇叭)的类别三、扬声器(喇叭)的应用场景四、扬声器(喇叭)的作用原理总结前言 扬声器广泛应用于音响系统、公共广播系统、汽车音响、电视、电脑和移动设备等各种电子设备…...

汇总大厂-校招/社招 Java面试题--持续补充更新中-大家别光收藏,要看起来,巩固基础,就是干呀!

** 接上篇-汇总大厂-校招/社招 Java面试题&#xff08;补充&#xff09; ** markdown文件。持续更新中&#xff08;阿里、腾讯、网易、美团、京东、华为、快手、字节…&#xff09; 上面这篇也结合着看啊&#xff0c;通宵给整理出来的。 如需下载整套资料。关注公众号后台。…...

六. 函数

基本使用 ts与js一样拥有具名函数和匿名函数两种函数类型。但是ts的函数需要提前定义好参数类型以及函数的返回值类型。 具名函数 function add(num1: number, num2: number):number {return num1 num2 }匿名函数 匿名函数的定义相对麻烦&#xff0c;我们需要提前定义函数的…...

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

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

<蓝桥杯软件赛>零基础备赛20周--第9周--前缀和与差分

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…...

LeetCode-2487. 从链表中移除节点【栈 递归 链表 单调栈】

LeetCode-2487. 从链表中移除节点【栈 递归 链表 单调栈】 题目描述&#xff1a;解题思路一&#xff1a;可以将链表转为数组&#xff0c;然后从后往前遍历&#xff0c;遇到大于等于当前元素的就入栈&#xff0c;最终栈里面的元素即是最终的答案。解题思路二&#xff1a;递归&am…...

Redisson分布式锁原理分析

1.Redisson实现分布式锁 在分布式系统中&#xff0c;涉及到多个实例对同一资源加锁的情况&#xff0c;传统的synchronized、ReentrantLock等单进程加锁的API就不再适用&#xff0c;此时就需要使用分布式锁来保证多服务之间加锁的安全性。 常见的分布式锁的实现方式有&#xff…...

【Linux】:线程(二)互斥

互斥与同步 一.线程的局部存储二.线程的分离三.互斥1.一些概念2.上锁3.锁的原理4.死锁 一.线程的局部存储 例子 可以看到全局变量是所有线程共享的&#xff0c;如果我们想要每个线程都单独访问g_val怎么办呢&#xff1f;其实我们可以在它前面加上__thread修饰。 这就相当于把g…...

vscode报错Pylance client: couldn‘t create connection to server.

问题描述&#xff1a; 一打开vscode&#xff0c;右下角就弹报错&#xff0c;Pylance client: couldn’t create connection to server.&#xff0c;让我打开output&#xff0c;打开后似乎是在说连不上server 因为连不上server&#xff0c;所以我的python代码没法解析&#xff0…...

智能优化算法应用:基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于萤火虫算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.萤火虫算法4.实验参数设定5.算法结果6.参考文…...

MacOS多屏状态栏位置不固定,程序坞不小心跑到副屏

目录 方式一&#xff1a;通过系统设置方式二&#xff1a;鼠标切换 MacOS多屏状态栏位置不固定&#xff0c;程序坞不小心跑到副屏 方式一&#xff1a;通过系统设置 先切换到左边 再切换到底部 就能回到主屏了 方式二&#xff1a;鼠标切换 我的两个屏幕放置位置如下 鼠标在…...

Python:pipdeptree 语法介绍

相信大家在按照一些包的时候经常会碰到版本不兼容&#xff0c;但是又不知道版本之间的依赖关系&#xff0c;今天给大家介绍一个工具&#xff1a;pipdeptree pipdeptree 是一个 Python 包&#xff0c;用于查看已安装的 pip 包及其依赖关系。它以树形结构展示包之间的依赖关系&am…...

【问题处理】—— lombok 的 @Data 大小写区分不敏感

问题描述 今天在项目本地编译的时候&#xff0c;发现有个很奇怪的问题&#xff0c;一直提示某位置找不到符号&#xff0c; 但是实际在Idea中显示确实正常的&#xff0c;一开始以为又是IDEA的故障&#xff0c;所以重启了IDEA&#xff0c;并执行了mvn clean然后重新编译。但是问…...

跟着我学Python基础篇:08.集合和字典

往期文章 跟着我学Python基础篇&#xff1a;01.初露端倪 跟着我学Python基础篇&#xff1a;02.数字与字符串编程 跟着我学Python基础篇&#xff1a;03.选择结构 跟着我学Python基础篇&#xff1a;04.循环 跟着我学Python基础篇&#xff1a;05.函数 跟着我学Python基础篇&#…...

Tomcat部署(图片和HTML等)静态资源时遇到的问题

文章目录 Tomcat部署静态资源问题图中HTML代码启动Tomcat后先确认Tomcat是否启动成功 Tomcat部署静态资源问题 今天&#xff0c;有人突然跟我提到&#xff0c;使用nginx部署静态资源&#xff0c;如图片。可以直接通过url地址访问&#xff0c;为什么他的Tomcat不能通过这样的方…...

在接触新的游戏引擎的时候,如何能快速地熟悉并开发出一款新游戏?

引言 大家好&#xff0c;今天分享点个人经验。 有一定编程经验或者游戏开发经验的小伙伴&#xff0c;在接触新的游戏引擎的时候&#xff0c;如何能快速地熟悉并开发出一款新游戏&#xff1f; 利用现成开发框架。 1.什么是开发框架&#xff1f; 开发框架&#xff0c;顾名思…...

计网 - TCP四次挥手原理全曝光:深度解析与实战演示

文章目录 Pre导图过程分析抓包实战第一次挥手 【FIN ACK】第二次挥手 【ACK】第三次挥手 【FINACK】第四次挥手 【ACK】 小结 Pre 计网 - 传输层协议 TCP&#xff1a;TCP 为什么握手是 3 次、挥手是 4 次&#xff1f; 计网 - TCP三次握手原理全曝光&#xff1a;深度解析与实战…...

个人养老金知多少?

个人养老金政策你了解吗&#xff1f;税优政策你知道吗&#xff1f;你会计算能退多少税吗&#xff1f;… 点这里看一看...

gpt3、gpt2与gpt1区别

参考&#xff1a;深度学习&#xff1a;GPT1、GPT2、GPT-3_HanZee的博客-CSDN博客 Zero-shot Learning / One-shot Learning-CSDN博客 Zero-shot&#xff08;零次学习&#xff09;简介-CSDN博客 GPT1、GPT2、GPT3、InstructGPT-CSDN博客 目录 gpt2与gpt1区别&#xff1a; gp…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...