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

自己动手写数据库系统:实现一个小型SQL解释器(中)

我们接上节内容继续完成SQL解释器的代码解析工作。下面我们实现对update语句的解析,其语法如下:
UpdateCmd -> INSERT | DELETE | MODIFY | CREATE
Create -> CreateTable | CreateView | CreateIndex
Insert -> INSERT INTO ID LEFT_PARAS FieldList RIGHT_PARAS VALUES LEFT_PARS ConstList RIGHT_PARAS
FieldList -> Field ( COMMA FieldList)?
ConstList -> Constant ( COMMA ConstList)?
Delete -> DELETE FROM ID [WHERE Predicate)?
Modify -> UPDATE ID SET Field ASSIGN_OPERATOR Expression (WHERE Predicate)?
CreateTable -> CREATE TABLE ID (FieldDefs)?
FieldDefs -> FieldDef ( COMMA FieldDefs)?
FieldDef -> ID TypeDef
TypeDef -> INT | VARCHAT LEFT_PARAS NUM RIGHT_PARAS
CreateView -> CREATE VIEW ID AS Query
CreateIndex -> CREATE INDEX ID ON ID LEFT_PARAS Field RIGHT_PARAS

我们对上面的语法做一些基本说明:
UpdateCmd -> INSERT | DELETE | MODIFY | CREATE
这句语法表明SQL语言中用于更新表的语句一定由insert, delete, modify , create等几个命令开始。insert 语句由关键字insert开始,然后跟着insert into两个关键字,接着是左括号,跟着是由列名(column)组成的字符串,他们之间由逗号隔开,然后跟着右括号,接着是关键字VALUES,然后是左括号,接着是一系列常量和逗号组成的序列,最后以又括号结尾,其他语法大家可以参照SQL相关命令来理解,下面我们看看代码的实现,继续在parser.go中添加如下代码:

func (p *SQLParser) UpdateCmd() interface{} {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag == lexer.INSERT {p.sqlLexer.ReverseScan()return p.Insert()} else if tok.Tag == lexer.DELETE {p.sqlLexer.ReverseScan()return p.Delete()} else if tok.Tag == lexer.UPDATE {p.sqlLexer.ReverseScan()return p.Update()} else {p.sqlLexer.ReverseScan()return p.Create()}
}func (p *SQLParser) Create() interface{} {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.CREATE {panic("token is not create")}tok, err = p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag == lexer.TABLE {return p.CreateTable()} else if tok.Tag == lexer.VIEW {return p.CreateView()} else {return p.CreateIndex()}
}func (p *SQLParser) CreateView() interface{} {return nil
}func (p *SQLParser) CreateIndex() interface{} {return nil
}func (p *SQLParser) CreateTable() interface{} {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.ID {panic("token should be ID for table name")}tblName := p.sqlLexer.Lexemetok, err = p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.LEFT_BRACKET {panic("missing left bracket")}sch := p.FieldDefs()tok, err = p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.RIGHT_BRACKET {panic("missing right bracket")}return NewCreateTableData(tblName, sch)
}func (p *SQLParser) FieldDefs() *record_manager.Schema {schema := p.FieldDef()tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag == lexer.COMMA {schema2 := p.FieldDefs()schema.AddAll(schema2)} else {p.sqlLexer.ReverseScan()}return schema
}func (p *SQLParser) FieldDef() *record_manager.Schema {_, fldName := p.Field()return p.FieldType(fldName)
}func (p *SQLParser) FieldType(fldName string) *record_manager.Schema {schema := record_manager.NewSchema()tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag == lexer.INT {schema.AddIntField(fldName)} else if tok.Tag == lexer.VARCHAR {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.LEFT_BRACKET {panic("missing left bracket")}tok, err = p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.NUM {panic("it is not a number for varchar")}num := p.sqlLexer.LexemefldLen, err := strconv.Atoi(num)if err != nil {panic(err)}schema.AddStringField(fldName, fldLen)tok, err = p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.RIGHT_BRACKET {panic("missing right bracket")}}return schema
}

在上面代码中我们需要定义一个CreateTableData结构,因此增加一个create_data.go文件,添加代码如下:

package parserimport ("record_manager"
)type CreateTableData struct {tblName stringsch     *record_manager.Schema
}func NewCreateTableData(tblName string, sch *record_manager.Schema) *CreateTableData {return &CreateTableData{tblName: tblName,sch:     sch,}
}func (c *CreateTableData) TableName() string {return c.tblName
}func (c *CreateTableData) NewSchema() *record_manager.Schema {return c.sch
}

最后我们在main.go中添加代码,调用上面的代码实现:

package main//import (
//	bmg "buffer_manager"
//	fm "file_manager"
//	"fmt"
//	lm "log_manager"
//	"math/rand"
//	mm "metadata_management"
//	record_mgr "record_manager"
//	"tx"
//)import ("parser"
)func main() {sql := "create table person (PersonID int, LastName varchar(255), FirstName varchar(255)," +"Address varchar(255), City varchar(255) )"sqlParser := parser.NewSQLParser(sql)sqlParser.UpdateCmd()}

在main中,我们定义了一个create table的sql语句,然后调用UpdateCmd接口实现语法解析,大家可以在b站搜索”coding迪斯尼“,查看代码的调试演示视频,由于上面语法解析的逻辑稍微复杂和繁琐,因此通过视频来跟踪代码的单步调试过程才能更简单省力的理解实现逻辑。

下面我们看看insert语句的解析实现,在parser.go中添加代码如下:

func (p *SQLParser) checkWordTag(wordTag lexer.Tag) {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != wordTag {panic("token is not match")}
}func (p *SQLParser) isMatchTag(wordTag lexer.Tag) bool {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag == wordTag {return true} else {p.sqlLexer.ReverseScan()return false}
}func (p *SQLParser) fieldList() []string {L := make([]string, 0)_, field := p.Field()L = append(L, field)if p.isMatchTag(lexer.COMMA) {fields := p.fieldList()L = append(L, fields...)}return L
}func (p *SQLParser) constList() []*query.Constant {L := make([]*query.Constant, 0)L = append(L, p.Constant())if p.isMatchTag(lexer.COMMA) {consts := p.constList()L = append(L, consts...)}return L
}func (p *SQLParser) Insert() interface{} {/*根据语法规则:Insert -> INSERT INTO ID LEFT_PARAS FieldList RIGHT_PARAS VALUES LEFT_PARS ConstList RIGHT_PARAS我们首先要匹配四个关键字,分别为insert, into, id, 左括号,然后就是一系列由逗号隔开的field,接着就是右括号,然后是关键字values接着是常量序列,最后以右括号结尾*/p.checkWordTag(lexer.INSERT)p.checkWordTag(lexer.INTO)p.checkWordTag(lexer.ID)tblName := p.sqlLexer.Lexemep.checkWordTag(lexer.LEFT_BRACKET)flds := p.fieldList()p.checkWordTag(lexer.RIGHT_BRACKET)p.checkWordTag(lexer.VALUES)p.checkWordTag(lexer.LEFT_BRACKET)vals := p.constList()p.checkWordTag(lexer.RIGHT_BRACKET)return NewInsertData(tblName, flds, vals)
}

我们调用上面代码测试一下解析效果:

func main() {sql := "INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country) " +"VALUES (\"Cardinal\", \"Tom B. Erichsen\", \"Skagen 21\", \"Stavanger\", 4006, \"Norway\")"sqlParser := parser.NewSQLParser(sql)sqlParser.UpdateCmd()} 

请大家在b站搜索coding迪斯尼,通过视频调试演示的方式能更直白和有效的了解代码逻辑。接下来我们看看 create 命令如何创建 view 和 index 两个对象,首先我们看看 view 的创建,根据 create view 的语法:

CreateView -> CREATE VIEW ID AS QUERY

首先我们要判断语句的前两个 token 是否对应 关键字 CREATE, VIEW,然后接着的token 必须是 ID类型,然后跟着关键字 AS,最后我们调用 QUERY 对应的解析规则来解析后面的字符串,我们看看代码实现,在 parser.go 中添加如下代码:

func (p *SQLParser) CreateView() interface{} {p.checkWordTag(lexer.ID)viewName := p.sqlLexer.Lexemep.checkWordTag(lexer.AS)qd := p.Query()vd := NewViewData(viewName, qd)vdDef := fmt.Sprintf("vd def: %s", vd.ToString())fmt.Println(vdDef)return vd
}

然后新增文件 create_view.go,添加如下代码:

package parserimport "fmt"type ViewData struct {viewName  stringqueryData *QueryData
}func NewViewData(viewName string, qd *QueryData) *ViewData {return &ViewData{viewName:  viewName,queryData: qd,}
}func (v *ViewData) ViewName() string {return v.viewName
}func (v *ViewData) ViewDef() string {return v.queryData.ToString()
}func (v *ViewData) ToString() string {s := fmt.Sprintf("view name %s, viewe def: %s", v.viewName, v.ViewDef())return s
}

最后我们在 main.go 中添加如下测试代码:

func main() {//sql := "create table person (PersonID int, LastName varchar(255), FirstName varchar(255)," +//	"Address varchar(255), City varchar(255) )"sql := "create view Customer as select CustomerName, ContactName from customers where country=\"China\""sqlParser := parser.NewSQLParser(sql)sqlParser.UpdateCmd()}

上面代码运行后结果如下:

vd def: view name Customer, viewe def: select CustomerName, ContactName, from customers, where  and country=China

更详细的内容请在 b 站搜索 coding 迪斯尼。下面我们看看索引创建的语法解析,其对应的语法为:

CreateIndex -> CREATE INDEX ID ON ID LEFT_BRACKET Field RIGHT_BRACKET

从语法规则可以看出,在解析时我们需要判断语句必须以 CREATE INDEX 这两个关键字开头,然后接着的字符串要能满足 ID 的定义,然后又需要跟着关键字 ON, 然后跟着的字符串要满足 ID 定义,接下来读入的字符必须是左括号,然后接着的内容要满足 Field 的定义,最后要以右括号结尾,我们看看代码实现在 parser.go 中添加如下代码:

func (p *SQLParser) Create() interface{} {tok, err := p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag != lexer.CREATE {panic("token is not create")}tok, err = p.sqlLexer.Scan()if err != nil {panic(err)}if tok.Tag == lexer.TABLE {return p.CreateTable()} else if tok.Tag == lexer.VIEW {return p.CreateView()} else if tok.Tag == lexer.INDEX {return p.CreateIndex()}panic("sql string with create should not end here")
}func (p *SQLParser) CreateIndex() interface{} {p.checkWordTag(lexer.ID)idexName := p.sqlLexer.Lexemep.checkWordTag(lexer.ON)p.checkWordTag(lexer.ID)tableName := p.sqlLexer.Lexemep.checkWordTag(lexer.LEFT_BRACKET)_, fldName := p.Field()p.checkWordTag(lexer.RIGHT_BRACKET)idxData := NewIndexData(idexName, tableName, fldName)fmt.Printf("create index result: %s", idxData.ToString())return idxData
}

新建 create_index_data.go 文件,在里面添加代码如下:

package parserimport "fmt"type IndexData struct {idxName stringtblName stringfldName string
}func NewIndexData(idxName string, tblName string, fldName string) *IndexData {return &IndexData{idxName: idxName,tblName: tblName,fldName: fldName,}
}func (i *IndexData) IdexName() string {return i.idxName
}func (i *IndexData) tableName() string {return i.tblName
}func (i *IndexData) fieldName() string {return i.fldName
}func (i *IndexData) ToString() string {str := fmt.Sprintf("index name: %s, table name: %s, field name: %s", i.idxName, i.tblName, i.fldName)return str
}

在 main.go 中我们使用 sql 语句中的 create index 语句测试一下上面代码实现:

func main() {//sql := "create table person (PersonID int, LastName varchar(255), FirstName varchar(255)," +//	"Address varchar(255), City varchar(255) )"sql := "create index idxLastName on persons (lastname)"sqlParser := parser.NewSQLParser(sql)sqlParser.UpdateCmd()}

上面代码运行后所得结果如下:

create index result: index name: idxLastName, table name: persons, field name: lastname

到这里所有有关 create 语句的解析就基本完成,更多的调试演示和代码逻辑的讲解,请在 b 站搜索 coding 迪斯尼

相关文章:

自己动手写数据库系统:实现一个小型SQL解释器(中)

我们接上节内容继续完成SQL解释器的代码解析工作。下面我们实现对update语句的解析,其语法如下: UpdateCmd -> INSERT | DELETE | MODIFY | CREATE Create -> CreateTable | CreateView | CreateIndex Insert -> INSERT INTO ID LEFT_PARAS Fie…...

HTML 与 XHTML 二者有什么区别

HTML 与 XHTML 二者有什么区别,你觉得应该使用哪一个并说出理由。 HTML 与 XHTML 之间的差别,主要分为功能上的差别和书写习惯的差别两方面。 关于功能上的差别,主要是 XHTML 可兼容各大浏览器、手机以及 PDA,并且浏览器也能快速正…...

fiddler抓包问题记录,支持https、解决 tunnel to 443

fiddler下载安装步骤及基本配置 fiddler抓包教程,如何抓取HTTPS请求,详细教程 可能遇到的问题及解决方案 1. 不能正常访问页面(所有https都无法访问) 解决方案:查看下面配置是否正确 Rules-customization 找到 OnB…...

Kubesphere中DevOps流水线无法部署/部署失败

摘要 总算能让devops运行以后,流水线却卡在了deploy这一步。碰到了两个比较大的问题,一个是无法使用k8sp自带的kubeconfig认证去部署;一个是部署好了以后但是没有办法解析镜像名。 版本信息 k8s:v1.21.5 k8sp:v3.3.…...

使用Nginx解决跨域问题

前言: 项目是公司的老项目,只有部署在服务器上的时候,项目才可以正常运行(接口是通的);现在需求:在现有的项目代码上进行修改,请求接口是第三方给的。接口是正常的,通过A…...

在 OpenCV 中使用深度学习进行年龄检测-附源码

文末附完整源码和模型文件下载链接 在本教程中,我们将了解使用 OpenCV 创建年龄预测器和性别分类器项目的整个过程。 年龄检测 我们的目标是创建一个程序,使用图像来预测人的性别和年龄。但预测年龄可能并不像你想象的那么简单,为什么呢?您可能会认为年龄预测是一个回归问…...

【BASH】回顾与知识点梳理(三十一)

【BASH】回顾与知识点梳理 三十一 三十一. 进程的管理31.1 给进程发送讯号kill -signal PIDlinux系统后台常驻进程killall -signal 指令名称 31.2 关于进程的执行顺序Priority 与 Nice 值nice :新执行的指令即给予新的 nice 值renice :已存在进程的 nice…...

Linux 终端命令之文件浏览(3) less

Linux 文件浏览命令 cat, more, less, head, tail,此五个文件浏览类的命令皆为外部命令。 hannHannYang:~$ which cat /usr/bin/cat hannHannYang:~$ which more /usr/bin/more hannHannYang:~$ which less /usr/bin/less hannHannYang:~$ which head /usr/bin/he…...

【精通性能优化:解锁JMH微基准测试】一基本用法

文章目录 1. 什么是JMH1.1 用JMH进行微基准测试1. JmhExample01.java2. 程序输出JmhExample01.java 2.2 JMH的基本用法2.1 Benchmark标记基准测试方法2.2 Warmup以及Measurement1. 设置全局的Warmup和Measurement(一)2. 设置全局的Warmup和Measurement&a…...

.Net程序调试时接受外部命令行参数方式

1.对项目右键,属性 2.在调试中打开常规,打开调试启动配置文件UI 3.输入需要的命令行参数...

Mariadb高可用MHA (四十二)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、概述 1.1 概念 1.2 组成 1.3 特点 1.4 工作原理 二、构建MHA 2.1 ssh免密登录 2.2 主从复制 2.3 MHA安装 2.3.1所有节点安装perl环境 2.3..2 node 2.3.…...

Vue3 setup中使用$refs

在 Vue 3 中的 Composition API 中,$refs 并不直接可用于 setup 函数。这是因为 $refs 是 Vue 2 的实例属性,而在 Vue 3 中,setup 函数是与模板实例分离的,不再使用实例属性。 实际工作中确实有需求,在setup 函数使用…...

什么是React的上下文(Context)?如何使用和传递上下文信息?

1、什么是React的上下文(Context)?如何使用和传递上下文信息? React上下文(Context)是React提供的一种功能,允许你在组件之间传递数据和状态。通过使用上下文,你无需通过props一层一层地传递数据,从而减少了代码的复杂…...

CentOS Linux 78安全基线检查

阿里云标准-CentOS Linux 7/8安全基线检查 检查项类别描述加固建议等级密码复杂度检查身份鉴别检查密码长度和密码是否使用多种字符类型编辑/etc/security/pwquality.conf,把minlen(密码最小长度)设置为8-32位,把minclass(至少包含小写字母、大写字母、数…...

Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】

Java之SpringCloud Alibaba【四】【微服务 Sentinel服务熔断】 一、分布式系统遇到的问题1、服务挂掉的一些原因 二、解决方案三、Sentinel:分布式系统的流量防卫兵1、Sentinel是什么2、Sentinel和Hystrix对比3、Sentinel快速开发4、通过注解的方式来控流5、启动Sen…...

Kubernetes 企业级高可用部署

目录 1、Kubernetes高可用项目介绍 2、项目架构设计 2.1、项目主机信息 2.2、项目架构图 2.3、项目实施思路 3、项目实施过程 3.1、系统初始化 3.2、配置部署keepalived服务 3.3、配置部署haproxy服务 3.4、配置部署Docker服务 3.5、部署kubelet kubeadm kubectl工具…...

8.1 C++ STL 变易拷贝算法

C STL中的变易算法&#xff08;Modifying Algorithms&#xff09;是指那些能够修改容器内容的算法&#xff0c;主要用于修改容器中的数据&#xff0c;例如插入、删除、替换等操作。这些算法同样定义在头文件 <algorithm> 中&#xff0c;它们允许在容器之间进行元素的复制…...

攻击LNMP架构Web应用

环境配置(centos7) 1.php56 php56-fpm //配置epel yum install epel-release rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm//安装php56&#xff0c;php56-fpm及其依赖 yum --enablereporemi install php56-php yum --enablereporemi install php…...

深度学习入门-3-计算机视觉-图像分类

1.概述 图像分类是根据图像的语义信息对不同类别图像进行区分&#xff0c;是计算机视觉的核心&#xff0c;是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础。图像分类在许多领域都有着广泛的应用&#xff0c;如&#xff1a;安防领域的人脸识别…...

shopee运营新手入门教程!Shopee运营技巧!

​随着跨境电商行业的蓬勃发展&#xff0c;越来越多的人开始关注Shopee这个平台。短视频等渠道也成为了人们了解Shopee的途径。因此&#xff0c;对于许多新手来说&#xff0c;在Shopee上开店成为了一种吸引人的选择。为了帮助这些新手更好地入门&#xff0c;下面将介绍一下Shop…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...