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

news2024/9/24 1:24:18

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

package query

import (
	"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 planner

import (
	"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.coloradmin.cn/o/1311114.html

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

相关文章

浅谈故障注入的主要功能是什么?

随着现代软件系统的复杂性不断增加&#xff0c;保障系统的可靠性变得愈发重要。在这一背景下&#xff0c;故障注入技术崭露头角&#xff0c;成为优化系统稳定性和鲁棒性的关键手段。本文将探讨故障注入的主要功能以及在提高系统可靠性方面的作用。 1. 故障模拟与验证 故障注入技…

项目管理系统在律师案件管理中的应用与策略

法律案件管理软件可以帮助律师事务所自动化其日常工作流程&#xff0c;例如查看任务、跟踪计费时间和日历。这有助于公司提高效率并最大化收入&#xff0c;从而发展业务。 使用Zoho Projects项目管理系统进行律师案件管理可以帮助您更好地组织和跟踪案件进程、提高团队协作效率…

【论文精读ICCV_2023】BlendFace: Re-designing Identity Encoders for Face-Swapping

【论文精读ICCV_2023】BlendFace: Re-designing Identity Encoders for Face-Swapping 一、前言Abstract1. Introduction2. Related Work3. Attribute Bias in Face Recognition Models3.1. Identity Distance Loss3.2. Analysis of Identity Similarity 4. BlendFace4.1. Pre-…

中低压MOS 适用于电子烟等产品—— 较小的开关损耗 过流能力好

工作原理&#xff1a; 当用户在吸嘴处抽吸时&#xff0c;气流经过进气孔&#xff0c;穿 过电路板上方的咪头&#xff0c;咪头即产生电信号&#xff0c;驱 动芯片板&#xff0c;让电池供电给雾化芯&#xff0c;雾化芯中的 发热丝将电能转化成热能&#xff0c;当温度达到雾化液…

Zibll子比主题最新学习版

Zibll子比主题5.7.1是一款为WordPress平台设计的优秀主题。它具有独特而富有吸引力的设计风格&#xff0c;同时提供了丰富的功能和卓越的性能&#xff0c;使您的网站在众多网站中脱颖而出。以下是对Zibll子比主题5.7.1的详细介绍。 &#xff08;这是我在“布谷鸟网址导航”上看…

C++ - 哈希

在顺序结构以及平衡树中&#xff0c;由于元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找一个元素时&#xff0c;必须要经过关键码的多次比较&#xff1b;比如顺序表中需要从表头开始依次往后比对寻找&#xff0c;查找时间复杂度为 O(N)&#xff0c;平衡树中需…

通俗理解什么是 LSTM 神经网络

大家好啊&#xff0c;我是董董灿。 刚开始做程序开发时&#xff0c;在公司提交代码前&#xff0c;都需要让大佬们 review(评审)&#xff0c;大佬们看完&#xff0c;总会在评论区打出一串"LGTM"。 当时作为小白的我&#xff0c;天真地以为大佬觉得我提交的代码还不错…

浮动的魅力与挑战:如何在前端设计中巧妙运用浮动(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【UE5.1 MetaHuman】使用mixamo_converter把Mixamo的动画重定向给MetaHuman使用

目录 前言 效果 步骤 一、下载mixamo_converter软件 二、Mixamo动画重定向 三、导入UE 四、动画重定向 五、使用重定向后的动画 前言 上一篇&#xff08;【UE5】初识MetaHuman 创建虚拟角色&#xff09;中我们已经制作了一个MetaHuman&#xff0c;本篇博文将介绍如何…

Unity 关于Rigidbody刚体组件的理解

一、基本了解 刚体Rigidbody因具体物理相关的属性&#xff0c;使得实际应用中更有真实感。应用也多&#xff1a; Rigidbody它可以受到重力、碰撞或者力的作用&#xff0c;所以我们可以用它模拟物体的真实物理行为&#xff0c;如受到重力的作用、与其他刚体对象进行碰撞&#…

【infiniband】ibdump抓包

ibdump用于捕获和转储InfiniBand网络的流量。 这种工具通常用于调试和分析InfiniBand网络问题&#xff0c;包括性能瓶颈和配置错误。ibdump工具在Mellanox InfiniBand环境中较为常用&#xff0c;现由NVIDIA提供支持。 使用ibdump的基本步骤 请注意&#xff0c;您需要在安装了…

11.关注、取消关注 + 关注列表、粉丝列表

目录 1.关注、取消关注 1.1 把数据存到 Redis 中&#xff0c;构造 key 1.2 开发业务层 1.3 开发表现层 1.4 显示正确的关注数据 2.关注列表、粉丝列表 2.1 业务层 2.2 表现层 1.关注、取消关注 需求&#xff1a;开发关注、取消关注功能&#xff1b;统计用户的关注数…

飞天使-docker知识点5-资源限制与容器的不同状态

文章目录 cpu和内存的限制内存限制的部分参数容器的不同状态docker images 的分层docker registry制作镜像 cpu和内存的限制 默认情况下&#xff0c;容器没有资源限制&#xff0c;可以使用主机内核调度程序允许的尽可能多的 给定资源&#xff0c;Docker 提供了控制容器可以限制…

wordpress 修改社交图标

要去掉标记的图标&#xff0c;死活找不到在那里配置。后来找到了&#xff0c;下图&#xff08;wordpress 小白&#xff0c;特此记录&#xff09;

【开源软件】最好的开源软件-2023-第17名 Gravite

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

事务--03---TCC空回滚、悬挂、幂等解决方案

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Seata TCC 模式设计思路TCC存在的问题1、空回滚以及解决方案解决方案&#xff1a; 2、幂等问题以及解决方案解决方案&#xff1a; 3、悬挂问题以及解决方案解决方案…

档案馆数字化建设实施方案

档案馆数字化建设实施方案主要包括以下几个方面的内容&#xff1a; 1. 目标与规划&#xff1a;明确数字化建设的目标和规划&#xff0c;确定数字化建设的优先领域和重点工作&#xff0c;制定长期和短期的发展规划。 2. 技术设施建设&#xff1a;建设专久智能数字化档案管理系统…

gradle-5.4.1-all下载时出现了Connect timed out

问题描述&#xff1a;最近在学习如何在手机端部署YOLO&#xff0c;出现了许多错误&#xff0c;其中之一的错误&#xff1a;gradle-5.4.1-all下载时出现了Connect timed out&#xff0c;大家都知道这是从国外网站下载网络问题导致的。 解决办法: 在我们创建项目中的一个路径下…

回溯热门问题

关卡名 回溯热门问题 我会了✔️ 内容 1.组合总和问题 ✔️ 2.分割回文串问题 ✔️ 3.子集问题 ✔️ 4.排列问题 ✔️ 5.字母全排列问题 ✔️ 6.单词搜索 ✔️ 1. 组合总和问题 LeetCode39题目要求&#xff1a;给你一个无重复元素的整数数组candidates和一个目标整数 ta…

【十】python复合模式

10.1 复合模式简介 在前面的栏目中我们了解了各种设计模式。正如我们所看到的&#xff0c;设计模式可分为三大类:结构型、创建型和行为型设计模式。同时&#xff0c;我们还给出了每种类型的相应示例。然而&#xff0c;在软件实现中&#xff0c;模式并是不孤立地工作的。对于所…