自己动手写数据库系统:实现一个小型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 迪斯尼

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

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

相关文章

后端项目打包上传服务器记录

后端项目打包上传服务器记录 文章目录 后端项目打包上传服务器记录1、项目打包2、jar包上传服务器 本文记录打包一个后端项目,上传公司服务器的过程。 1、项目打包 通过IDEA的插件进行打包: 打成一个jar包,jar包的位置在控制台可以看到。 2、…

ssm蜀都天香酒楼网站设计与实现

ssm蜀都天香酒楼的网站设计与实现028 开发工具:idea 数据库mysql5.7 数据库链接工具:navcat,小海豚等 技术:ssm 摘要 近年来,信息化管理行业的不断兴起,使得人们的日常生活越来越离不开计算机和互联网技术。首…

机器学习基础(六)

贝叶斯分析 介绍 “贝叶斯”是指托马斯贝叶斯(1702–1761),他证明了一个特例,也就是现在的贝叶斯定理的特例。 贝叶斯定理(英语:Bayes theorem)是概率论中的一个定理,描述在已知一些条件下,某事件的发生概率。比如,如果已知某种健康问题与寿命有关,使用贝叶斯定理则…

selenium语法进阶+常用API

目录 浏览器操作 浏览器回退,前进 与刷新 浏览器窗口设置大小 浏览器设置宽高 浏览器窗口最大化 浏览器控制滚动条 信息打印 打印页面的标题和当前页面的URL 定位一组元素 鼠标和键盘事件 键盘 鼠标 下拉框操作 通过索引定位(se…

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

【BASH】回顾与知识点梳理 三十二 三十二. SELinux 初探32.1 什么是 SELinux当初设计的目标:避免资源的误用传统的文件权限与账号关系:自主式访问控制, DAC以政策规则订定特定进程读取特定文件:委任式访问控制, MAC 32.2 SELinux 的运作模式安…

安科瑞变电所运维平台在电力系统中应用分析

摘要:现代居民生活、工作对电力资源的需求量相对较多,给我国的电力产业带来了良好的发展机遇与挑战。探索电力系统基本构成, 将变电运维安全管理以及相应的设备维护工作系统性开展,能够根据项目实践工作要求,将满足要求…

C语言暑假刷题冲刺篇——day2

目录 一、选择题 二、编程题 🎈个人主页:库库的里昂 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评论⭐收藏✨收录专栏:C语言每日一练 ✨其他专栏:代码小游戏C语言初阶🤝希望作者的文章能对你…

最小生成树,prim算法

Prim算法和Kruskal算法都是用于解决最小生成树问题的经典算法,它们在不同情况下有不同的适用性和特点。 Prim算法: Prim算法是一种贪心算法,用于构建一个无向图的最小生成树。算法从一个初始节点开始,逐步添加与当前树连接且具有…

【自动电压调节器】无功功率控制的终端电压控制研究(Simulink)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

小白的Node.js学习笔记大全---不定期更新

let、const、var的区别 (1)块级作用域: 块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题: 内层变量可能覆盖外层变量 用来计数的循环变量泄露为全局…

【加强管理】《别输在不懂管理上》学习记录,黄金41条

成功有时是很难效法的,但失败是可以避免的,从失败中吸取经验和教训才是管理者的必修课。释义: 图形含义🌲一级重要🍀二级重要🌿三级主要🍁存在问题🌼解决办法 1 不能从头管到脚 不…

【讨论】视频监控集中存储方案如何做?

视频监控集中存储是指将多个视频监控摄像头所捕捉到的视频信号集中存储于一个中央设备,这个中央设备可以是服务器、网络存储设备或其他专用设备。通过集中存储,可以避免因为存储设备分散而导致的管理不便和难以有效地管理和检索视频数据,同时…

RTT(RT-Thread)ADC设备(RTT保姆级介绍)

目录 ADC设备 前言 ADC相关参数说明 访问ADC设备 配置ADC设备 ADC实例 硬件设计 软件设计 ADC设备 前言 ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。 对于ADC的详细介绍和在STM32中的裸机应用可参考以下…

pandas数据分析38——数据框表格拓展以及缩回对齐

案例背景 需求是这个样的: 把这个表格进行拓展。 代码实现: df pd.DataFrame(np.array([[1, 2, 3,4], [a,b, c,d], [小明,小红, 小马,小天]])) df 方法一:自定义函数: def expand_dataframe(df):m, n df.shapenew_df pd.Dat…

linux系统中设置服务开机自启动

1:背景描述 最近根据工作需要,需要服务实现开机自启动的效果,因为平时只使用过nohup的后台挂起操作,很少接触开机,镜像装机服务自启动的功能,因此,这里简单记录一下。 注意,开机自…

解锁数据潜力:信息抽取、数据增强与UIE的完美融合

解锁数据潜力:信息抽取、数据增强与UIE的完美融合 1.信息抽取(Information Extraction) 1.1 IE简介 信息抽取是 NLP 任务中非常常见的一种任务,其目的在于从一段自然文本中提取出我们想要的关键信息结构。 举例来讲&#xff0…

从NLP到聊天机器人

一、说明 今天,当打电话给银行或其他公司时,听到电话另一端的机器人向你打招呼是很常见的:“你好,我是你的数字助理。请问你的问题。是的,机器人现在不仅可以说人类语言,还可以用人类语言与用户互动。这是由…

windows权限维持—黄金白银票据隐藏用户远控RustDeskGotoHttp

windows权限维持—黄金白银票据&隐藏用户&远控&RustDesk&GotoHttp 1. 前置1.1. 初始问题1.1.1. 解决办法 2. 隐藏用户2.1. 工具原理2.2. 案例操作2.2.1. 单机添加用户2.2.1.1. 工具添加用户2.2.1.2. 工具查看隐藏用户2.2.1.3. 本地查看隐藏用户 2.2.2. 域内添加…

CentOS系统环境搭建(二)——Centos7设置时间为网络时间

centos系统环境搭建专栏🔗点击跳转 Centos7设置时间为网络时间 安装ntpdate工具 yum -y install ntp ntpdate关闭ntpd service ntpd stop设置系统时间与网络时间同步 ntpdate 0.asia.pool.ntp.org将系统时间写入硬件时间 hwclock --systohc查看和设置时区 使…

NeuralNLP-NeuralClassifier的使用记录(二),训练预测自己的【中文文本多分类】

NeuralNLP-NeuralClassifier的使用记录,训练预测自己的【中文文本多分类】 数据准备: ​ 与英文的训练预测一致,都使用相同的数据格式,将数据通过代码处理为JSON格式,以下是我使用的一种,不同的原数据情况…