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

[手写系列]Go手写db — — 第二版

[手写系列]Go手写db — — 第二版

第一版文章:[手写系列]Go手写db — — 完整教程

  • 整体项目Github地址:https://github.com/ziyifast/ZiyiDB
  • 请大家多多支持,也欢迎大家star⭐️和共同维护这个项目~

本文主要介绍如何在 ZiyiDB 第一版的基础上,实现更多新功能,给大家提供更多的实现思路,以及实现的流程,后续更多关键字以及字段类型的支持,大家可以参考着实现。

一、功能列表

  1. 新增对 FLOAT 和 DATETIME 字段类型的支持
  2. 新增对 BETWEEN AND 关键字的支持
  3. 新增对注释符的支持
  4. 新增对 >=、<=、!= 操作符的支持

二、实现细节

1. 新增对 FLOAT 和 DATETIME 字段类型的支持

实现思路

为了支持 FLOAT 和 DATETIME 类型,我们需要在词法分析器、语法分析器和存储引擎中进行相应修改。

token.go新增常量 -> lexer.go词法分析器新增对Float、时间的解析 -> ast.go抽象语法树新增FloatLiteral、DatetimeLiteral -> parser.go语法解析器中新增对Float、时间类型数据的解析,并构建为语法树 -> memory.go存储引擎新增对Float、时间类型的insert、update操作(解析语法树)

  1. lexer/token.go中新增FLOAT、DATETIME const常量
    在这里插入图片描述
  2. lexer/lexer.go的NextToken中新增对时间、数字的解析
  • 对时间的解析:是字符串,且能被转换为时间格式
    在这里插入图片描述
  • 对浮点数数字的解析:是数字,且包含.
    在这里插入图片描述
  1. internal/lexer/lexer.go的lookupIdentifier方法中新增标识符类型,用于解析关键字
    在这里插入图片描述
  2. internal/ast/ast.go中为后续构建抽象语法树,新增FloatLiteral、DateTimeLiteral以及在Cell结构体中新增FloatValue和TimeValue存储该类型值
    在这里插入图片描述
    在这里插入图片描述
  3. internal/parser/parser.go解析器中新增对Float浮点数、DATETIME时间的解析,将解析出来的数据构建为抽象语法树的结构
    在这里插入图片描述
  4. internal/storage/memory.go存储引擎中新增对*ast.FloatLiteral、*ast.DateTimeLiteral的解析,将其转换为原始类型
  • 更新evaluateExpression函数:
    在这里插入图片描述
  • 更新Update函数:将抽象语法树中的datetime、float Literal字面量解析至原始类型并更新至Cell中
    在这里插入图片描述
  • 更新Insert函数:将抽象语法树中的datetime、float Literal字面量解析至原始类型并添加至Cell中。
    在这里插入图片描述

PS:
①ast.Literal 是用户输入的表达,ast.Cell 是实际存储的数据,将Cell struct放在ast.go中不合理,后续会进行优化
②各类Literal,比如:FloatLiteral这里的Value可以存储原本的值,而非统一用String存储,避免重复转换,增加开销

  • 大家可以自己尝试在原代码的基础上进行优化,当做练手

代码实现

①在词法分析器中添加新类型
// internal/lexer/token.go
const (// ... 其他标记类型FLOAT    TokenType = "FLOAT"DATETIME TokenType = "DATETIME"// ... 其他标记类型
)// internal/lexer/lexer.go
func (l *Lexer) NextToken() Token {var tok Tokenl.skipWhitespace()// ... 其他代码switch l.ch {// ... 其他casecase '\'':tok.Type = STRINGtok.Literal = l.readString()// 检查是否为时间格式if _, err := time.Parse("2006-01-02 15:04:05", tok.Literal); err == nil {tok.Type = DATETIME}return tok// ... 其他case}// 处理数字(包括浮点数)if isDigit(l.ch) || l.ch == '.' {num := l.readNumber()if strings.Contains(num, ".") {tok.Type = FLOAT} else {tok.Type = INT}tok.Literal = numreturn tok}// ... 其他代码
}// internal/lexer/lexer.go
func (l *Lexer) readNumber() string {var num bytes.BufferhasDecimal := falsefor (isDigit(l.ch) || (l.ch == '.' && !hasDecimal)) && l.ch != 0 {if l.ch == '.' {hasDecimal = true}num.WriteRune(l.ch)l.readChar()}return num.String()
}// internal/lexer/lexer.go
func (l *Lexer) lookupIdentifier(ident string) TokenType {switch strings.ToUpper(ident) {// ... 其他casecase "FLOAT":return FLOATcase "DATETIME":return DATETIME// ... 其他case}return IDENT
}
②在 AST 中添加新表达式类型
// internal/ast/ast.go
// 新增FloatLiteral表达式类型
type FloatLiteral struct {BaseExpressionToken lexer.TokenValue string
}// 新增DateTimeLiteral表达式类型
type DateTimeLiteral struct {BaseExpressionToken lexer.TokenValue string
}// 在 Cell 结构体中添加新字段
type Cell struct {Type       CellTypeIntValue   int32TextValue  stringFloatValue float32TimeValue  string
}const (CellTypeInt CellType = iotaCellTypeTextCellTypeFloatCellTypeDateTime
)
③在解析器中处理新类型
// internal/parser/parser.go
func (p *Parser) parseExpression() (ast.Expression, error) {switch p.curToken.Type {// ... 其他casecase lexer.FLOAT:return &ast.FloatLiteral{Token: p.curToken,Value: p.curToken.Literal,}, nilcase lexer.DATETIME:return &ast.DateTimeLiteral{Token: p.curToken,Value: p.curToken.Literal,}, nil// ... 其他case}
}
④在存储引擎中处理新数据类型
// internal/storage/memory.go
// 修改 evaluateExpression 函数以处理新类型
func evaluateExpression(expr ast.Expression) (interface{}, error) {switch e := expr.(type) {// ... 其他casecase *ast.FloatLiteral:val, err := strconv.ParseFloat(e.Value, 32)if err != nil {return nil, fmt.Errorf("Incorrect float value: '%s'", e.Value)}return float32(val), nilcase *ast.DateTimeLiteral:t, err := time.Parse("2006-01-02 15:04:05", e.Value)if err != nil {return nil, fmt.Errorf("Incorrect datetime value: '%s'", e.Value)}return t, nil// ... 其他case}
}// 在 Insert 方法中处理新类型
func (b *MemoryBackend) Insert(stmt *ast.InsertStatement) error {// ... 其他代码// 类型转换(保持原有逻辑)switch v := value.(type) {// ... 其他casecase float32:row[tableColIdx] = ast.Cell{Type: ast.CellTypeFloat, FloatValue: v}case time.Time:row[tableColIdx] = ast.Cell{Type: ast.CellTypeDateTime, TimeValue: v.Format("2006-01-02 15:04:05")}// ... 其他case}// ... 其他代码
}// 在 getColumnValue 中处理新类型
func getColumnValue(expr ast.Expression, row []ast.Cell, columns []ast.ColumnDefinition) (interface{}, error) {switch e := expr.(type) {// ... 其他casecase *ast.FloatLiteral:val, err := strconv.ParseFloat(e.Value, 32)if err != nil {return nil, fmt.Errorf("Incorrect float value: '%s'", e.Value)}return float32(val), nilcase *ast.DateTimeLiteral:val, err := time.Parse("2006-01-02 15:04:05", e.Value)if err != nil {return nil, fmt.Errorf("Incorrect datetime value: '%s'", e.Value)}return val, nil// ... 其他case}
}

测试

测试SQL:

CREATE TABLE users (id INT PRIMARY KEY,name text,age INT,score FLOAT, ctime DATETIME ); 
INSERT INTO users VALUES (1, 'Alice', 20,89.0, '2023-07-01 12:00:00');
INSERT INTO users VALUES (2, 'Bob', 25,98.3, '2023-07-04 12:00:00');select * from users where score < 90.0;
select * from users where ctime >  '2023-07-02 12:00:00';
drop table users;

效果:
在这里插入图片描述

2. 新增对 BETWEEN AND 关键字的支持

实现思路

BETWEEN AND 是一个用于范围查询的操作符。我们需要在词法分析器中识别这两个关键字,在 AST 中添加新的表达式类型,在解析器中处理这种语法结构,并在存储引擎中实现相应的查询逻辑。

  1. lexer/token.go中新增BETWEEN、AND的TokenType常量
    在这里插入图片描述
  2. lexer/lexer.go中的lookupIdentifier方法新增对BETWEEN、AND关键字的读取,将用户的字符输入转换为TokenType
    在这里插入图片描述
  3. ast/ast.go抽象语法树中新增BetweenExpression struct,用于将between and内容构建到语法树中
    在这里插入图片描述
  4. parser/parser.go语法解析器:
  • 新增parseBetweenExpression函数,用于将用户输入的between and部分解析为ast.BetweenExpression
    在这里插入图片描述
  • 各语法的Statement部分新增对between and的处理(比如select、delete、update语句…)
    例:parseSelectStatement部分(构建select的抽象语法树)新增对between and关键字的处理,将其转换为where条件部分
    在这里插入图片描述
  1. storage/memory.go中新增对*ast.BetweenExpression的处理,判断当前数据是否满足between and过滤条件
    在这里插入图片描述

代码实现

①在词法分析器中添加关键字
// internal/lexer/token.go
const (// ... 其他标记类型BETWEEN TokenType = "BETWEEN"AND     TokenType = "AND"// ... 其他标记类型
)// internal/lexer/lexer.go
func (l *Lexer) lookupIdentifier(ident string) TokenType {switch strings.ToUpper(ident) {// ... 其他casecase "BETWEEN":return BETWEENcase "AND":return AND// ... 其他case}return IDENT
}
②在 AST 中添加 BETWEEN 表达式
// internal/ast/ast.go
// BetweenExpression 表示 BETWEEN AND 表达式
type BetweenExpression struct {Token lexer.Token // BETWEEN 标记Left  Expression  // 左操作数(列名或表达式)Low   Expression  // 下限值High  Expression  // 上限值
}func (be *BetweenExpression) expressionNode()      {}
func (be *BetweenExpression) TokenLiteral() string { return be.Token.Literal }
③在解析器中处理 BETWEEN 语法
// internal/parser/parser.go
// 在 parseSelectStatement、parseUpdateStatement 和 parseDeleteStatement 中添加处理逻辑
func (p *Parser) parseSelectStatement() (*ast.SelectStatement, error) {// ... 其他代码// 解析WHERE子句if p.peekTokenIs(lexer.WHERE) {p.nextToken()p.nextToken()// 解析左操作数(列名)if !p.curTokenIs(lexer.IDENT) {return nil, fmt.Errorf("You have an error in your SQL syntax; check the manual that corresponds to your db server version for the right syntax to use near '%s'", p.curToken.Literal)}left := &ast.Identifier{Token: p.curToken,Value: p.curToken.Literal,}// 解析操作符p.nextToken()operator := p.curToken// 处理BETWEEN操作符if p.curTokenIs(lexer.BETWEEN) {expr, err := p.parseBetweenExpression(left)if err != nil {return nil, err}stmt.Where = exprreturn stmt, nil}// ... 其他代码}// ... 其他代码
}// 解析BETWEEN表达式
func (p *Parser) parseBetweenExpression(left ast.Expression) (ast.Expression, error) {expr := &ast.BetweenExpression{Token: p.curToken,Left:  left,}p.nextToken() // 跳过BETWEEN// 解析下限值lower, err := p.parseExpression()if err != nil {return nil, err}expr.Low = lowerif !p.expectPeek(lexer.AND) {return nil, fmt.Errorf("expected AND after BETWEEN expression")}p.nextToken() // 跳过AND// 解析上限值upper, err := p.parseExpression()if err != nil {return nil, err}expr.High = upperreturn expr, nil
}
④在存储引擎中实现 BETWEEN 查询逻辑
// internal/storage/memory.go
// 在 evaluateWhereCondition 中处理 BETWEEN 表达式
func evaluateWhereCondition(expr ast.Expression, row []ast.Cell, columns []ast.ColumnDefinition) (bool, error) {switch e := expr.(type) {// ... 其他casecase *ast.BetweenExpression:// 获取列值colIndex, err := getColumnIndex(e.Left.(*ast.Identifier).Value, columns)if err != nil {return false, err}left := row[colIndex]lower, err := evaluateExpression(e.Low)if err != nil {return false, err}upper, err := evaluateExpression(e.High)if err != nil {return false, err}switch left.Type {case ast.CellTypeInt:leftVal := left.IntValuelowerVal, lok := lower.(int32)upperVal, uok := upper.(int32)if !lok || !uok {return false, fmt.Errorf("type mismatch in BETWEEN expression")}return leftVal >= lowerVal && leftVal <= upperVal, nilcase ast.CellTypeFloat:leftVal := left.FloatValuelowerVal, lok := lower.(float32)upperVal, uok := upper.(float32)if !lok || !uok {return false, fmt.Errorf("type mismatch in BETWEEN expression")}return leftVal >= lowerVal && leftVal <= upperVal, nilcase ast.CellTypeDateTime:val := left.TimeValueleftVal, err := time.Parse("2006-01-02 15:04:05", val)if err != nil {return false, err}lowerVal, lok := lower.(time.Time)upperVal, uok := upper.(time.Time)if !lok || !uok {return false, fmt.Errorf("type mismatch in BETWEEN expression")}return (leftVal.After(lowerVal) || leftVal.Equal(lowerVal)) &&(leftVal.Before(upperVal) || leftVal.Equal(upperVal)), nildefault:return false, fmt.Errorf("unsupported type in BETWEEN expression")}// ... 其他case}
}

测试

测试SQL:

CREATE TABLE users (id INT PRIMARY KEY,name text,age INT,score FLOAT, ctime DATETIME ); INSERT INTO users VALUES (1, 'Alice', 20,89.0, '2023-07-01 12:00:00');
INSERT INTO users VALUES (2, 'Bob', 25,98.3, '2023-07-04 12:00:00');select * from users where age between 21 and 28;
select * from users where ctime between '2021-07-02 12:00:00' and '2023-07-05 12:00:00';
select * from users where ctime between '2023-07-02 12:00:00' and '2023-07-05 12:00:00';
drop table users;

效果:
在这里插入图片描述

3. 新增对注释符的支持

实现思路

SQL 注释以--开头,直到行尾。我们需要在词法分析器中识别注释并跳过它们,在解析器中忽略注释标记。

  1. lexer/token.go中新增注释符标识--,用于匹配用户输入。
    在这里插入图片描述
  2. lexer/lexer.go词法分析器中,读取注释即注释后的内容
    在这里插入图片描述
    在这里插入图片描述
    效果:
    在这里插入图片描述
  3. parser/parser.go语法解析器中的ParseProgram、parseStatement忽略注释符之后的内容,遇到注释符即跳过
    在这里插入图片描述
    在这里插入图片描述

代码实现

①在词法分析器中添加注释支持
// internal/lexer/token.go
const (// ... 其他标记类型COMMENT TokenType = "--"// ... 其他标记类型
)// internal/lexer/lexer.go
func (l *Lexer) NextToken() Token {var tok Tokenl.skipWhitespace()// 检查是否为注释if l.ch == '-' && l.peekChar() == '-' {return l.readComment()}// ... 其他代码
}// 读取注释内容
func (l *Lexer) readComment() Token {var comment bytes.Bufferl.readChar() // 跳过第一个 '-'l.readChar() // 跳过第二个 '-'// 读取直到行尾for l.ch != '\n' && l.ch != 0 && l.ch != '\r' {comment.WriteRune(l.ch)l.readChar()}return Token{Type: COMMENT, Literal: comment.String()}
}
②在解析器中忽略注释
// internal/parser/parser.go
func (p *Parser) ParseProgram() (*ast.Program, error) {program := &ast.Program{Statements: []ast.Statement{},}for p.curToken.Type != lexer.EOF {// 跳过注释if p.curToken.Type == lexer.COMMENT {p.nextToken()continue}stmt, err := p.parseStatement()if err != nil {return nil, err}if stmt != nil {program.Statements = append(program.Statements, stmt)}p.nextToken()}return program, nil
}// parseStatement 中也跳过注释
func (p *Parser) parseStatement() (ast.Statement, error) {// 跳过注释for p.curToken.Type == lexer.COMMENT {p.nextToken()}// ... 其他代码
}

测试

测试SQL:

CREATE TABLE users (id INT PRIMARY KEY,name TEXT,age INT); -- 建表
INSERT INTO users VALUES (1, 'Alice', 20); -- 插入用户数据
SELECT * FROM users; -- 查询数据
drop table users; -- 删除表

效果:
在这里插入图片描述

4. 新增对 >=、<=、!= 操作符的支持

实现思路

我们需要在词法分析器中识别这些新的操作符,在解析器中处理它们,并在存储引擎中实现相应的比较逻辑。

  1. lexer/token.go中新增>=、<=、!=
    在这里插入图片描述
  2. lexer/lexer.go词法分析器中新增对!=、>=、<=操作符的识别,将字符映射为对应的token
    在这里插入图片描述
  3. parser/parser.go语法解析器中isBasicOperator操作符判断中新增<=、>=、!=,用于检查是否属于SQL操作符
    在这里插入图片描述
  4. storage/memory.go存储引擎中的evaluateWhereCondition、compareValues方法中实现对>=、<=、!=的底层逻辑
    在这里插入图片描述
    在这里插入图片描述

代码实现

①在词法分析器中添加新操作符
// internal/lexer/token.go
const (// ... 其他标记类型GTE TokenType = ">="  // >=LTE TokenType = "<="  // <=NEQ TokenType = "!="  // !=// ... 其他标记类型
)// internal/lexer/lexer.go
var operatorMap = map[string]TokenType{"=":  EQ,">":  GT,"<":  LT,">=": GTE,"<=": LTE,"!=": NEQ,
}func (l *Lexer) readOperator() Token {var op bytes.Bufferop.WriteRune(l.ch)// 检查是否为双字符操作符if l.peekChar() == '=' {op.WriteRune(l.peekChar())l.readChar() // 消费 '='}literal := op.String()if tokenType, exists := operatorMap[literal]; exists {l.readChar() // 消费当前字符return Token{Type: tokenType, Literal: literal}}// 如果不是有效的操作符,回退并返回错误l.readChar()return Token{Type: ERROR, Literal: literal}
}
②在解析器中处理新操作符
// internal/parser/parser.go
// isBasicOperator 检查是否为操作符,比如 =、>、<、>=、<=、!=、like等
func (p *Parser) isBasicOperator() bool {return p.curTokenIs(lexer.EQ) ||p.curTokenIs(lexer.GT) ||p.curTokenIs(lexer.LT) ||p.curTokenIs(lexer.GTE) ||p.curTokenIs(lexer.LTE) ||p.curTokenIs(lexer.NEQ) ||p.curTokenIs(lexer.LIKE)
}
③在存储引擎中实现新操作符的比较逻辑
// internal/storage/memory.go
// 在 evaluateWhereCondition 中处理新操作符
func evaluateWhereCondition(expr ast.Expression, row []ast.Cell, columns []ast.ColumnDefinition) (bool, error) {switch e := expr.(type) {case *ast.BinaryExpression:// 获取左操作数的值leftValue, err := getColumnValue(e.Left, row, columns)if err != nil {return false, err}// 获取右操作数的值rightValue, err := getColumnValue(e.Right, row, columns)if err != nil {return false, err}// 根据操作符比较值switch e.Operator {case "=":return compareValues(leftValue, rightValue, "=")case ">":return compareValues(leftValue, rightValue, ">")case "<":return compareValues(leftValue, rightValue, "<")case ">=":return compareValues(leftValue, rightValue, ">=")case "<=":return compareValues(leftValue, rightValue, "<=")case "!=":result, err := compareValues(leftValue, rightValue, "=")if err != nil {return false, err}return !result, nil // 返回相反的结果default:return false, fmt.Errorf("Unknown operator: '%s'", e.Operator)}// ... 其他case}
}// 在 compareValues 中处理新操作符
func compareValues(left, right interface{}, operator string) (bool, error) {// ... 其他代码switch operator {case "=":return isEqual(left, right)case ">":return isGreater(left, right)case "<":return isLess(left, right)case ">=":equal, _ := isEqual(left, right)greater, _ := isGreater(left, right)return equal || greater, nilcase "<=":equal, _ := isEqual(left, right)less, _ := isLess(left, right)return equal || less, nilcase "!=":equal, err := isEqual(left, right)if err != nil {return false, err}return !equal, nildefault:return false, fmt.Errorf("Unknown operator: '%s'", operator)}
}

测试

测试SQL:

CREATE TABLE users (id INT PRIMARY KEY,name TEXT,age INT);INSERT INTO users VALUES (1, 'Alice', 20);
INSERT INTO users VALUES (2, 'Bob', 25);
INSERT INTO users VALUES (3, 'Charlie', 30);select name, age from users where age >= 24;
select name, age from users where age <= 20;
select name, age from users where name != 'Charlie';drop table users;

效果:
在这里插入图片描述

http://www.dtcms.com/a/354155.html

相关文章:

  • spring-boot-test与 spring-boot-starter-test 区别
  • 前端架构设计模式与AI驱动的智能化演进
  • 嵌入式学习日志————USART串口协议
  • 【开发便利】让远程Linux服务器能够访问内网git仓库
  • 目标检测基础
  • [系统架构设计师]论文(二十三)
  • 控制系统仿真之时域分析(二)
  • 计算机组成原理(13) 第二章 - DRAM SRAM SDRAM ROM
  • 通信原理(005)——带宽、宽带、传输速率、流量
  • 农业物联网:科技赋能现代农业新篇章
  • uC/OS-III 队列相关接口
  • Linux 命令浏览文件内容
  • 机器视觉的车载触摸屏玻璃盖板贴合应用
  • 【Bluetooth】【调试工具篇】第九章 实时抓取工具 btsnoop
  • [vcpkg] Windows入门使用介绍
  • 致远OA新闻公告讨论调查信息查询SQL
  • 模拟电路中什么时候适合使用电流传递信号,什么时候合适使用电压传递信号
  • 世界的接口:数学、心智与未知的协作
  • 【前端】jsmpeg 介绍及使用
  • Libvio 访问异常排查指南:从现象到根源的深度剖析
  • 专项智能练习(关系数据库)
  • 风锐统计——让数据像风一样自由!(九)——回归分析
  • FreeRTOS内部机制理解(任务调度机制)(三)
  • opencv学习笔记
  • 基于 Docker Compose 的若依多服务一键部署java项目实践
  • 【深度学习-Day 44】GRU详解:LSTM的优雅继任者?门控循环单元原理与PyTorch实战
  • sparksql的transform如何使用
  • 8.27 网格memo
  • HTTP 头
  • Go 1.25新特性之容器感知功能详解