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

这篇具有很好参考价值的文章主要介绍了自己动手写数据库系统:实现一个小型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.Lexeme
	tok, 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.Lexeme
		fldLen, 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 parser

import (
	"record_manager"
)

type CreateTableData struct {
	tblName string
	sch     *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.Lexeme
	p.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.Lexeme
	p.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 parser

import "fmt"

type ViewData struct {
	viewName  string
	queryData *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.Lexeme
	p.checkWordTag(lexer.ON)
	p.checkWordTag(lexer.ID)
	tableName := p.sqlLexer.Lexeme
	p.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 parser

import "fmt"

type IndexData struct {
	idxName string
	tblName string
	fldName 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 迪斯尼文章来源地址https://www.toymoban.com/news/detail-660319.html

到了这里,关于自己动手写数据库系统:实现一个小型SQL解释器(中)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 详解自己动手添加一个函数实现任意字段调用

    是否遇到过想调用某些自定义字段,却发现dedecms的标签底层模板字段不包括这个字段呢?这就大大限制了灵活性,但dede也不可能让所有字段都允许调用的,那样就会大大降低系统效率,所以今天分享的是一个比较完美解决这个问题的方法,配合dede标签,几乎可以说没有什么

    2024年02月02日
    浏览(91)
  • 自己动手搭网站(六):javaweb搭建一个简单的个人博客系统

    这篇博主会介绍下我用javaweb搭建的个人博客系统,源码也会打包放到gitee上,需要的朋友可以自取,大家互相学习,请不要直接CV。 tip:本篇承上篇,许多基本内容在上篇谈到,建议看之前先浏览下上篇博客。 上篇:自己动手搭网站(五):javaweb基础:登录功能 系列总目录

    2024年02月07日
    浏览(48)
  • 自己动手实现一个深度学习算法——三、神经网络的学习

    这里所说的“学习”是指从训练数据中自动获取最优权重参数的过程 。为了使神经网络能进行学习,将导入 损失函数 这一指标。而学习的目的就是以该损失函数为基准,找出能使它的值达到最小的权重参数。为了找出尽可能小的损失函数的值,利用了 函数斜率的梯度法 。

    2024年02月05日
    浏览(50)
  • 【cfengDB】自己实现数据库第0节 ---整体介绍及事务管理层实现

    LearnProj 本文作为数工底层的项目CfengDB开始篇章,介绍开发缘由和实现思路 cfeng之前对数据库研究不深入,之前只是能够做到基本的SQL查询和基本的慢SQL优化,之前拿到数据库系统工程师证书还是只在业务上对于DB系统使用更深入,但是cfeng基于work的理解,当作为一个优秀的产

    2024年02月16日
    浏览(40)
  • 【学习笔记】pandas提取excel数据形成三元组,采用neo4j数据库构建小型知识图谱

    前言     代码来自github项目 neo4j-python-pandas-py2neo-v3,项目作者为Skyelbin。我记录一下运行该项目的一些过程文字以及遇到的问题和解决办法。 invoice_data 如下: node_list_key 如下: node_list_value 如下: df_data 如下: 构建的知识图谱如下所示: 压缩包里其他文件说明(个人理

    2024年01月16日
    浏览(49)
  • 如何在一个系统中同时访问异构的多种数据库

    如何在一个系统中同时访问异构的多种数据库 比如在一个系统中,要同时访问MySQL,H2, MsAccess, Mongodb. 要是使用Hibernate, MyBatis这些ORM,难度简直不敢想像。 要是MySQL还使用了分库分表,那更加不得了,一大堆的组件都要配合着上,一时间整个系统的难度,复杂度就上来了。 但如

    2024年01月19日
    浏览(42)
  • 从零实现一个数据库(DataBase) Go语言实现版 0.介绍

    英文源地址 我们为什么需要数据库?为什么不是直接把数据dump进文件中. 第一个话题就是持久化. 我们将讨论如果写入文件的过程中程序崩溃了, 或者电源断电了, 文件的状态会是什么样的呢? 文件是否只是丢失了最后一次写操作? 或者以写了一半的文件结束 或者是以更差的状态

    2024年02月06日
    浏览(38)
  • 案例:创建一个学生管理系统(PXSCJ1)的数据库(SQL)

    1、新建数据库:PXSCJ1 2、创建并确认属性:XSB、KCB、CJB 代码见上! 3、设计每个表的实体完整性:键、索引 代码见上!    4、设计每个表的域完整性:CHECK语句 代码见上!      5、建立表与表之间的参照完整性:XSB与CJB,KCB与CJB 代码见上! 6、输入表数据:增加、删除、修改

    2024年02月10日
    浏览(40)
  • 编写一个微信小程序,实现表单提交数据到云数据库

    好的。 首先,你需要在微信公众平台中创建一个小程序,并在小程序的后台获取到云开发的相关权限。然后你就可以使用微信小程序开发工具进行开发了。 在页面中创建一个表单,并在表单中添加输入框和提交按钮。 在小程序的 app.js 文件中初始化云开发环境。 在表单的提

    2024年02月07日
    浏览(48)
  • 从零手搓一个【消息队列】创建核心类, 数据库设计与实现

    创建 Spring Boot 项目, Spring Boot 2 系列版本, Java 8 , 引入 MyBatis, Lombok 依赖 提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货! 整体目录结构 : 本文主要实现 server 包 上篇文章 分析了项目需求, 介绍了项目中重要的核心概念

    2024年02月07日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包