[手写系列]Go手写db --- --- 第二版
第一版文章:[手写系列]Go手写db --- --- 完整教程
整体项目Github地址:https://github.com/ziyifast/ZiyiDB
- 请大家多多支持,也欢迎大家star⭐️和共同维护这个项目~
本文主要介绍如何在 ZiyiDB 第一版的基础上,实现更多新功能,给大家提供更多的实现思路,以及实现的流程,后续更多关键字以及字段类型的支持,大家可以参考着实现。
一、功能列表
- 新增对 FLOAT 和 DATETIME 字段类型的支持
- 新增对 BETWEEN AND 关键字的支持
- 新增对注释符的支持
- 新增对 >=、<=、!= 操作符的支持
二、实现细节
1. 新增对 FLOAT 和 DATETIME 字段类型的支持
实现思路
为了支持 FLOAT 和 DATETIME 类型,我们需要在词法分析器、语法分析器和存储引擎中进行相应修改。
token.go新增常量 -> lexer.go词法分析器新增对Float、时间的解析 -> ast.go抽象语法树新增FloatLiteral、DatetimeLiteral -> parser.go语法解析器中新增对Float、时间类型数据的解析,并构建为语法树 -> memory.go存储引擎新增对Float、时间类型的insert、update操作(解析语法树)
- lexer/token.go中新增FLOAT、DATETIME const常量
- lexer/lexer.go的NextToken中新增对时间、数字的解析
- 对时间的解析:是字符串,且能被转换为时间格式
- 对浮点数数字的解析:是数字,且包含
.
- internal/lexer/lexer.go的lookupIdentifier方法中新增标识符类型,用于解析关键字
- internal/ast/ast.go中为后续构建抽象语法树,新增FloatLiteral、DateTimeLiteral以及在Cell结构体中新增FloatValue和TimeValue存储该类型值
- internal/parser/parser.go解析器中新增对Float浮点数、DATETIME时间的解析,将解析出来的数据构建为抽象语法树的结构
- 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存储,避免重复转换,增加开销
- 大家可以自己尝试在原代码的基础上进行优化,当做练手
代码实现
①在词法分析器中添加新类型
go
// internal/lexer/token.go
const (
// ... 其他标记类型
FLOAT TokenType = "FLOAT"
DATETIME TokenType = "DATETIME"
// ... 其他标记类型
)
// internal/lexer/lexer.go
func (l *Lexer) NextToken() Token {
var tok Token
l.skipWhitespace()
// ... 其他代码
switch l.ch {
// ... 其他case
case '\'':
tok.Type = STRING
tok.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 = num
return tok
}
// ... 其他代码
}
// internal/lexer/lexer.go
func (l *Lexer) readNumber() string {
var num bytes.Buffer
hasDecimal := false
for (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) {
// ... 其他case
case "FLOAT":
return FLOAT
case "DATETIME":
return DATETIME
// ... 其他case
}
return IDENT
}
②在 AST 中添加新表达式类型
go
// internal/ast/ast.go
// 新增FloatLiteral表达式类型
type FloatLiteral struct {
BaseExpression
Token lexer.Token
Value string
}
// 新增DateTimeLiteral表达式类型
type DateTimeLiteral struct {
BaseExpression
Token lexer.Token
Value string
}
// 在 Cell 结构体中添加新字段
type Cell struct {
Type CellType
IntValue int32
TextValue string
FloatValue float32
TimeValue string
}
const (
CellTypeInt CellType = iota
CellTypeText
CellTypeFloat
CellTypeDateTime
)
③在解析器中处理新类型
go
// internal/parser/parser.go
func (p *Parser) parseExpression() (ast.Expression, error) {
switch p.curToken.Type {
// ... 其他case
case lexer.FLOAT:
return &ast.FloatLiteral{
Token: p.curToken,
Value: p.curToken.Literal,
}, nil
case lexer.DATETIME:
return &ast.DateTimeLiteral{
Token: p.curToken,
Value: p.curToken.Literal,
}, nil
// ... 其他case
}
}
④在存储引擎中处理新数据类型
go
// internal/storage/memory.go
// 修改 evaluateExpression 函数以处理新类型
func evaluateExpression(expr ast.Expression) (interface{}, error) {
switch e := expr.(type) {
// ... 其他case
case *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), nil
case *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) {
// ... 其他case
case 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) {
// ... 其他case
case *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), nil
case *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:
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 中添加新的表达式类型,在解析器中处理这种语法结构,并在存储引擎中实现相应的查询逻辑。
- lexer/token.go中新增BETWEEN、AND的TokenType常量
- lexer/lexer.go中的lookupIdentifier方法新增对BETWEEN、AND关键字的读取,将用户的字符输入转换为TokenType
- ast/ast.go抽象语法树中新增BetweenExpression struct,用于将between and内容构建到语法树中
- parser/parser.go语法解析器:
- 新增parseBetweenExpression函数,用于将用户输入的between and部分解析为ast.BetweenExpression
- 各语法的Statement部分新增对between and的处理(比如select、delete、update语句...)
例:parseSelectStatement部分(构建select的抽象语法树)新增对between and关键字的处理,将其转换为where条件部分
- storage/memory.go中新增对*ast.BetweenExpression的处理,判断当前数据是否满足between and过滤条件
代码实现
①在词法分析器中添加关键字
go
// 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) {
// ... 其他case
case "BETWEEN":
return BETWEEN
case "AND":
return AND
// ... 其他case
}
return IDENT
}
②在 AST 中添加 BETWEEN 表达式
go
// 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 语法
go
// 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 = expr
return 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 = lower
if !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 = upper
return expr, nil
}
④在存储引擎中实现 BETWEEN 查询逻辑
go
// internal/storage/memory.go
// 在 evaluateWhereCondition 中处理 BETWEEN 表达式
func evaluateWhereCondition(expr ast.Expression, row []ast.Cell, columns []ast.ColumnDefinition) (bool, error) {
switch e := expr.(type) {
// ... 其他case
case *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.IntValue
lowerVal, lok := lower.(int32)
upperVal, uok := upper.(int32)
if !lok || !uok {
return false, fmt.Errorf("type mismatch in BETWEEN expression")
}
return leftVal >= lowerVal && leftVal <= upperVal, nil
case ast.CellTypeFloat:
leftVal := left.FloatValue
lowerVal, lok := lower.(float32)
upperVal, uok := upper.(float32)
if !lok || !uok {
return false, fmt.Errorf("type mismatch in BETWEEN expression")
}
return leftVal >= lowerVal && leftVal <= upperVal, nil
case ast.CellTypeDateTime:
val := left.TimeValue
leftVal, 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)), nil
default:
return false, fmt.Errorf("unsupported type in BETWEEN expression")
}
// ... 其他case
}
}
测试
测试SQL:
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 注释以--
开头,直到行尾。我们需要在词法分析器中识别注释并跳过它们,在解析器中忽略注释标记。
- lexer/token.go中新增注释符标识
--
,用于匹配用户输入。
- lexer/lexer.go词法分析器中,读取注释即注释后的内容
效果:
- parser/parser.go语法解析器中的ParseProgram、parseStatement忽略注释符之后的内容,遇到注释符即跳过
代码实现
①在词法分析器中添加注释支持
go
// internal/lexer/token.go
const (
// ... 其他标记类型
COMMENT TokenType = "--"
// ... 其他标记类型
)
// internal/lexer/lexer.go
func (l *Lexer) NextToken() Token {
var tok Token
l.skipWhitespace()
// 检查是否为注释
if l.ch == '-' && l.peekChar() == '-' {
return l.readComment()
}
// ... 其他代码
}
// 读取注释内容
func (l *Lexer) readComment() Token {
var comment bytes.Buffer
l.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()}
}
②在解析器中忽略注释
go
// 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:
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. 新增对 >=、<=、!= 操作符的支持
实现思路
我们需要在词法分析器中识别这些新的操作符,在解析器中处理它们,并在存储引擎中实现相应的比较逻辑。
- lexer/token.go中新增>=、<=、!=
- lexer/lexer.go词法分析器中新增对!=、>=、<=操作符的识别,将字符映射为对应的token
- parser/parser.go语法解析器中isBasicOperator操作符判断中新增<=、>=、!=,用于检查是否属于SQL操作符
- storage/memory.go存储引擎中的evaluateWhereCondition、compareValues方法中实现对>=、<=、!=的底层逻辑
代码实现
①在词法分析器中添加新操作符
go
// 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.Buffer
op.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}
}
②在解析器中处理新操作符
go
// 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)
}
③在存储引擎中实现新操作符的比较逻辑
go
// 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, nil
case "<=":
equal, _ := isEqual(left, right)
less, _ := isLess(left, right)
return equal || less, nil
case "!=":
equal, err := isEqual(left, right)
if err != nil {
return false, err
}
return !equal, nil
default:
return false, fmt.Errorf("Unknown operator: '%s'", operator)
}
}
测试
测试SQL:
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;
效果: