[手写系列]Go手写db — — 第五版(实现数据库操作模块)

[手写系列]Go手写db --- --- 第五版(实现数据库操作模块)

第一版文章:[手写系列]Go手写db --- --- 完整教程_go手写数据库-CSDN博客

第二版文章:[手写系列]Go手写db --- --- 第二版-CSDN博客

第三版文章:[手写系列]Go手写db --- --- 第三版(实现分组、排序、聚合函数等)-CSDN博客

第四版文章:[手写系列]Go手写db --- --- 第四版(实现事务、网络模块)

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

本文主要介绍如何在 ZiyiDB 第四版的基础上,实现数据库层面的操作,包括创建、删除、切换数据库以及数据库与表的关联管理等,通过这些功能,将使得ZiyiDB成为一个更完整的数据库系统。

一、功能列表

  1. 新增对数据库的创建(CREATE DATABASE)支持
  2. 新增对数据库的删除(DROP DATABASE)支持
  3. 新增对数据库列表的展示(SHOW DATABASES)支持
  4. 新增对表列表的展示(SHOW TABELS)支持
  5. 新增对数据库切换(USE DATABASE)支持
  6. 实现数据库与表的关联管理。存储引擎操作数据库,然后由数据库结构体间接操作表

二、实现细节

功能点一:实现数据库的创建、删除

实现思路

  1. internal/lexer/token.go新增DATABASE关键字,因为drop和create关键字之前已经有了
  2. internal/lexer/lexer.go中的lookupIdentifier方法新增一个case返回,用于将用户输入的字符转换为TokenType
  3. internal/ast/ast.go语法树中新增create database和drop database语法树结构
  4. internal/parser/parser.go语法解析器中的create和drop case中新增对database的处理


    然后分别实现parseCreateDatabaseStatement、parseDropDatabaseStatement方法,方便后续构建抽象语法树

  5. internal/storage/memory.go存储引擎
  • 新增数据库结构体的定义,同时存储引擎直接管理数据库,数据表交给Database结构体管理
  • 实现底层对数据库创建、删除的方法
  1. cmd/main.go中executor方法新增case处理对用户对数据库创建、删除的操作

PS:network/server.go网络服务端修改同理,这里不做赘述

代码实现

1. 词法分析器调整

internal/lexer/token.go:

go 复制代码
// internal/lexer/token.go
package lexer

// TokenType 表示词法单元类型
type TokenType string

const (
    ...
    DATABASE  TokenType = "DATABASE"
    ...
)

// Token 词法单元
// Type:标记的类型(如 SELECT、IDENT 等)
// Literal:标记的实际值(如具体的列名、数字等)
type Token struct {
    Type    TokenType // 标记类型
    Literal string    // 标记的实际值
}

internal/lexer/lexer.go:

go 复制代码
// internal/lexer/lexer.go
package lexer

import (
    "bufio"
    "bytes"
    "io"
    "strings"
    "time"
    "unicode"
)
    ...
    
// lookupIdentifier 查找标识符类型
// 将标识符转换为对应的标记类型
// 识别 SQL 关键字
func (l *Lexer) lookupIdentifier(ident string) TokenType {
    switch strings.ToUpper(ident) {
    ...
    case "DATABASE":
       return DATABASE
    ...
    default:
       return IDENT
    }
}
    ...
2. 抽象语法树调整

internal/ast/ast.go:

go 复制代码
...

    // CreateDatabaseStatement 表示CREATE DATABASE语句
    type CreateDatabaseStatement struct {
        Token lexer.Token
        Name  string
    }
    
    func (cds *CreateDatabaseStatement) statementNode()       {}
    func (cds *CreateDatabaseStatement) TokenLiteral() string { return cds.Token.Literal }
    
    // DropDatabaseStatement
    type DropDatabaseStatement struct {
        Token lexer.Token
        Name  string
    }
    
    func (dds *DropDatabaseStatement) statementNode()       {}
    func (dds *DropDatabaseStatement) TokenLiteral() string { return dds.Token.Literal }

...
3. 语法解析器调整
go 复制代码
// parseStatement 解析语句
// 根据当前标记类型选择相应的解析方法
func (p *Parser) parseStatement() (ast.Statement, error) {
    // 跳过注释
    for p.curToken.Type == lexer.COMMENT {
       p.nextToken()
    }
    switch p.curToken.Type {
    //需要区分是创建表还是创建数据库
    case lexer.CREATE:
       if p.peekTokenIs(lexer.TABLE) {
          return p.parseCreateTableStatement()
       } else if p.peekTokenIs(lexer.DATABASE) {
          return p.parseCreateDatabaseStatement()
       }
       return nil, fmt.Errorf("expected TABLE or DATABASE after CREATE")
       ...
    case lexer.DROP:
       if p.peekTokenIs(lexer.TABLE) {
          return p.parseDropTableStatement()
       } else if p.peekTokenIs(lexer.DATABASE) {
          return p.parseDropDatabaseStatement()
       }
       return nil, fmt.Errorf("expected TABLE or DATABASE after DROP")
    case lexer.SEMI:
       return nil, nil
    default:
       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.Type)
    }
}

// parseDropDatabaseStatement 解析DROP DATABASE语句
func (p *Parser) parseDropDatabaseStatement() (*ast.DropDatabaseStatement, error) {
    stmt := &ast.DropDatabaseStatement{Token: p.curToken}

    if !p.expectPeek(lexer.DATABASE) {
       return nil, fmt.Errorf("expected DATABASE keyword")
    }

    if !p.expectPeek(lexer.IDENT) {
       return nil, fmt.Errorf("expected database name")
    }

    stmt.Name = p.curToken.Literal
    return stmt, nil
}


// parseCreateDatabaseStatement 解析CREATE DATABASE语句
func (p *Parser) parseCreateDatabaseStatement() (*ast.CreateDatabaseStatement, error) {
    stmt := &ast.CreateDatabaseStatement{Token: p.curToken}

    if !p.expectPeek(lexer.DATABASE) {
       return nil, fmt.Errorf("expected DATABASE keyword")
    }

    if !p.expectPeek(lexer.IDENT) {
       return nil, fmt.Errorf("expected database name")
    }

    stmt.Name = p.curToken.Literal
    return stmt, nil
}
4. 存储引擎实现操作
go 复制代码
// Database 表示数据库
type Database struct {
    Name   string
    Tables map[string]*Table
    mu     sync.RWMutex
}

// MemoryBackend 内存存储引擎,管理所有数据库
type MemoryBackend struct {
    Databases map[string]*Database
    txnMgr    *TransactionManager
    Mu        sync.RWMutex
}

// Table 数据表,包含列定义、数据行和索引
type Table struct {
    Name     string
    Columns  []ast.ColumnDefinition
    Rows     [][]VersionedCell // 保持为 VersionedCell
    Indexes  map[string]*Index
    RowLocks map[int]*sync.RWMutex // 行级锁
    mu       sync.RWMutex
}
    ...
    
// CreateDatabase 创建数据库
func (b *MemoryBackend) CreateDatabase(stmt *ast.CreateDatabaseStatement) error {
    b.Mu.Lock()
    defer b.Mu.Unlock()

    if _, exists := b.Databases[stmt.Name]; exists {
       return fmt.Errorf("database '%s' already exists", stmt.Name)
    }

    b.Databases[stmt.Name] = &Database{
       Name:   stmt.Name,
       Tables: make(map[string]*Table),
    }

    return nil
}

// DropDatabase 删除数据库
func (b *MemoryBackend) DropDatabase(stmt *ast.DropDatabaseStatement) error {
    b.Mu.Lock()
    defer b.Mu.Unlock()

    if _, exists := b.Databases[stmt.Name]; !exists {
       return fmt.Errorf("database '%s' does not exist", stmt.Name)
    }

    delete(b.Databases, stmt.Name)
    return nil
}
5. 程序入口处调整

需要调整两个地方,一个是本地命令行版(cmd/main.go),一个是网络版(network/server.go)

  • cmd/main.go:
go 复制代码
func executor(t string) {
    // 分割多个SQL语句(用分号分隔)
    statements := strings.Split(t, ";")

    for _, stmt := range statements {
        ...

       // 创建词法分析器
       l := lexer.NewLexer(strings.NewReader(stmt))

       // 创建语法分析器
       p := parser.NewParser(l)

       // 解析SQL语句
       parsedStmt, err := p.ParseProgram()
       if err != nil {
          fmt.Printf("Parse error: %v\n", err)
          continue
       }

       // 执行SQL语句
       for _, statement := range parsedStmt.Statements {
          if currentDatabase == "" {
             // 检查是否是非数据库操作语句
             _, isCreateDB := statement.(*ast.CreateDatabaseStatement)
             _, isDropDB := statement.(*ast.DropDatabaseStatement)
             // 如果不是允许的语句类型,则提示需要选择数据库
             if !isCreateDB && !isDropDB {
                fmt.Println("No database selected. Use 'USE database_name' to select a database.")
                continue
             }
          }
          switch s := statement.(type) {
          case *ast.CreateDatabaseStatement:
             if err := backend.CreateDatabase(s); err != nil {
                fmt.Printf("Error: %v\n", err)
             } else {
                fmt.Println("Database created successfully")
             }
          case *ast.DropDatabaseStatement:
             if err := backend.DropDatabase(s); err != nil {
                fmt.Printf("Error: %v\n", err)
             } else {
                fmt.Println("Database dropped successfully")
             }
             ...
          default:
             fmt.Printf("Unsupported statement type: %T\n", s)
          }
       }
    }
}
  • network/server.go:
go 复制代码
// network/server.go
package network

import (
    "bufio"
    "fmt"
    "net"
    "strings"
    "sync"
    "ziyi.db.com/internal/ast"
    "ziyi.db.com/internal/lexer"
    "ziyi.db.com/internal/parser"
    "ziyi.db.com/internal/storage"
)

const DefaultPort = "3118"

    ...

func (s *Server) executeCommand(conn net.Conn, command string) string {
    ...

    var result string
    for _, statement := range parsedStmt.Statements {
       //检查是否选择了数据库
       if connCtx.GetDBName() == "" {
          // 检查是否是非数据库操作语句
          _, isCreateDB := statement.(*ast.CreateDatabaseStatement)
          _, isDropDB := statement.(*ast.DropDatabaseStatement)
          // 如果不是允许的语句类型,则提示需要选择数据库
          if !isCreateDB && !isDropDB {
             result += "No database selected. Use 'USE database_name' to select a database."
             continue
          }
       }
       switch stmt := statement.(type) {
       case *ast.CreateDatabaseStatement:
          if err := s.backend.CreateDatabase(stmt); err != nil {
             result += fmt.Sprintf("Error: %v\n", err)
          } else {
             result += "Database created successfully\n"
          }
       case *ast.DropDatabaseStatement:
          if err := s.backend.DropDatabase(stmt); err != nil {
             result += fmt.Sprintf("Error: %v\n", err)
          } else {
             result += "Database dropped successfully\n"
          }
       case *ast.DropTableStatement:
          if err := s.backend.DropTable(connCtx.db, stmt); err != nil {
             result += fmt.Sprintf("Error: %v\n", err)
          } else {
             result += "Table dropped successfully\n"
          }
       default:
          result += fmt.Sprintf("Unsupported statement type: %T\n", stmt)
       }
    }

    return strings.TrimSpace(result)
}

测试

测试命令:

sql 复制代码
-- 创建test数据库
create database test;
-- 删除test数据库
drop database test;

效果:

功能点二:use选择数据库

实现思路

  1. 新增internal/context/context.go文件,定义DBContext接口

  2. internal/lexer/token.go新增USE关键字:

  3. internal/lexer/lexer.go中lookupIdentifier新增case:

  4. 抽象语法树internal/ast/ast.go新增UseDatabaseStatement

  5. 语法解析器internal/parser/parser.go的parseStatement新增case,并实现对应case逻辑

  6. 存储引擎实现UseDatabase选择数据库逻辑

  7. 网络服务端、程序入口调用UseDatabase方法

代码实现

1. 词法分析器调整
  • internal/lexer/token.go:
go 复制代码
const(
    USE       TokenType = "USE"
    ...
)
...
  • internal/lexer/lexer.go:
go 复制代码
...
// lookupIdentifier 查找标识符类型
// 将标识符转换为对应的标记类型
// 识别 SQL 关键字
func (l *Lexer) lookupIdentifier(ident string) TokenType {
    switch strings.ToUpper(ident) {
        ...
    case "USE":
       return USE
    default:
       return IDENT
    }
}
2. 抽象语法树调整

internal/ast/ast.go:

go 复制代码
...
type UseDatabaseStatement struct {
    Token lexer.Token
    Name  string
}

func (uds *UseDatabaseStatement) statementNode()       {}
func (uds *UseDatabaseStatement) TokenLiteral() string { return uds.Token.Literal }
3. 语法解析器调整

internal/parser/parser.go:

go 复制代码
// parseStatement 解析语句
// 根据当前标记类型选择相应的解析方法
func (p *Parser) parseStatement() (ast.Statement, error) {
    // 跳过注释
    for p.curToken.Type == lexer.COMMENT {
       p.nextToken()
    }
    switch p.curToken.Type {
        ...
    case lexer.USE:
       return p.parseUseDatabaseStatement()
       ...
    default:
       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.Type)
    }
}


func (p *Parser) parseUseDatabaseStatement() (*ast.UseDatabaseStatement, error) {
    stmt := &ast.UseDatabaseStatement{Token: p.curToken}

    if !p.expectPeek(lexer.IDENT) {
       return nil, fmt.Errorf("expected database name")
    }

    stmt.Name = p.curToken.Literal
    return stmt, nil
}

    ...
4. 存储引擎实现操作

internal/storage/memory.go:

go 复制代码
// UseDatabase 使用数据库
func (b *MemoryBackend) UseDatabase(stmt *ast.UseDatabaseStatement, connCtx context.DBContext) error {
    b.Mu.RLock()
    defer b.Mu.RUnlock()

    if _, exists := b.Databases[stmt.Name]; !exists {
       return fmt.Errorf("database '%s' does not exist", stmt.Name)
    }

    // 更新连接上下文中的当前数据库
    connCtx.SetDBName(stmt.Name)
    return nil
}
5. 程序入口处调整

需要调整两个地方,一个是本地命令行版(cmd/main.go),一个是网络版(network/server.go)

  • cmd/main.go:
go 复制代码
func executor(t string) {
    // 分割多个SQL语句(用分号分隔)
    statements := strings.Split(t, ";")

    for _, stmt := range statements {
        ...

       // 创建词法分析器
       l := lexer.NewLexer(strings.NewReader(stmt))

       // 创建语法分析器
       p := parser.NewParser(l)

       // 解析SQL语句
       parsedStmt, err := p.ParseProgram()
       if err != nil {
          fmt.Printf("Parse error: %v\n", err)
          continue
       }

       // 执行SQL语句
       for _, statement := range parsedStmt.Statements {
          if currentDatabase == "" {
             // 检查是否是非数据库操作语句
             _, isCreateDB := statement.(*ast.CreateDatabaseStatement)
             _, isDropDB := statement.(*ast.DropDatabaseStatement)
             // 如果不是允许的语句类型,则提示需要选择数据库
             if !isCreateDB && !isDropDB {
                fmt.Println("No database selected. Use 'USE database_name' to select a database.")
                continue
             }
          }
          switch s := statement.(type) {
              ...
        case *ast.UseDatabaseStatement:
            if err := backend.UseDatabase(s, &dbContextAdapter{&currentDatabase}); err != nil {
               fmt.Printf("Error: %v\n", err)
            } else {
               fmt.Printf("Database changed to '%s'\n", currentDatabase)
            }
             ...
          default:
             fmt.Printf("Unsupported statement type: %T\n", s)
          }
       }
    }
}
  • network/server.go:
go 复制代码
// network/server.go
package network

import (
    "bufio"
    "fmt"
    "net"
    "strings"
    "sync"
    "ziyi.db.com/internal/ast"
    "ziyi.db.com/internal/lexer"
    "ziyi.db.com/internal/parser"
    "ziyi.db.com/internal/storage"
)

const DefaultPort = "3118"

    ...

func (s *Server) executeCommand(conn net.Conn, command string) string {
    ...

    var result string
    for _, statement := range parsedStmt.Statements {
        ...
       switch stmt := statement.(type) {
        case *ast.UseDatabaseStatement:
            if err := s.backend.UseDatabase(stmt, connCtx); err != nil {
               result += fmt.Sprintf("Error: %v\n", err)
            } else {
               result += fmt.Sprintf("Database changed to '%s'\n", stmt.Name)
            }
            ...
       default:
          result += fmt.Sprintf("Unsupported statement type: %T\n", stmt)
       }
    }

    return strings.TrimSpace(result)
}

测试

测试命令:

sql 复制代码
-- 创建两个数据库
create database test;
create database test2;
-- 使用数据库test并创建表
use test;
create table users (id INT PRIMARY KEY,name text,age INT);
INSERT INTO users VALUES (1, 'Alice', 20);
select * from users;
-- 预期test2数据库中没有users表
use test2;
select * from users;

效果:

功能点三:实现数据库、表列表展示

实现思路

  1. internal/lexer/token.go新增关键字
  2. internal/lexer/lexer.go lookupIdentifier方法新增一个case返回,用于将用户输入的字符转换为TokenType
  3. internal/ast/ast.go抽象语法树新增ShowDatabasesStatement、ShowTablesStatement,方便后续语法解析器构建抽象语法树
  4. internal/parser/parser.go语法解析器中实现对ShowDatabasesStatement、ShowTablesStatement抽象语法树的构建

    同时在parseStatement方法中新增一个case,用于解析show相关的SQL语句
  5. internal/storage/memory.go存储引擎中实现show databases、show tables,底层其实就是range遍历,然后将结果放在一个切片中
  6. 程序入口处调整,新增对应的case,判断到对应Statement之后,通过存储引擎调用对应方法即可
  • cmd/main.go:
  • network/server.go:

代码实现

1. 词法分析器调整
  • internal/lexer/token.go:
go 复制代码
const(
    ...
    DATABASES TokenType = "DATABASES"
    SHOW      TokenType = "SHOW"
    TABLES    TokenType = "TABLES"
)
  • internal/lexer/lexer.go:
go 复制代码
// lookupIdentifier 查找标识符类型
// 将标识符转换为对应的标记类型
// 识别 SQL 关键字
func (l *Lexer) lookupIdentifier(ident string) TokenType {
    switch strings.ToUpper(ident) {
    ...
    case "DATABASES":
       return DATABASES
    case "SHOW":
       return SHOW
    case "TABLES":
       return TABLES
    default:
       return IDENT
    }
}
...
2. 抽象语法树调整

internal/ast/ast.go:

go 复制代码
// ShowDatabasesStatement
type ShowDatabasesStatement struct {
    Token lexer.Token
}

func (sds *ShowDatabasesStatement) statementNode()       {}
func (sds *ShowDatabasesStatement) TokenLiteral() string { return sds.Token.Literal }

// ShowTablesStatement
type ShowTablesStatement struct {
    Token lexer.Token
}

func (sds *ShowTablesStatement) statementNode()       {}
func (sds *ShowTablesStatement) TokenLiteral() string { return sds.Token.Literal }
...
3. 语法解析器调整

internal/parser/parser.go:

go 复制代码
// parseStatement 解析语句
// 根据当前标记类型选择相应的解析方法
func (p *Parser) parseStatement() (ast.Statement, error) {
    // 跳过注释
    for p.curToken.Type == lexer.COMMENT {
       p.nextToken()
    }
    switch p.curToken.Type {
    //需要区分是创建表还是创建数据库
    case lexer.CREATE:
       if p.peekTokenIs(lexer.TABLE) {
          return p.parseCreateTableStatement()
       } else if p.peekTokenIs(lexer.DATABASE) {
          return p.parseCreateDatabaseStatement()
       }
       return nil, fmt.Errorf("expected TABLE or DATABASE after CREATE")
    case lexer.SHOW:
       if p.peekTokenIs(lexer.DATABASES) {
          return p.parseShowDatabasesStatement()
       }
       if p.peekTokenIs(lexer.TABLES) {
          return p.parseShowTablesStatement()
       }
       return nil, fmt.Errorf("expected DATABASES after SHOW")
       ...
    default:
       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.Type)
    }
}

// parseShowDatabasesStatement 解析SHOW DATABASES语句
func (p *Parser) parseShowDatabasesStatement() (*ast.ShowDatabasesStatement, error) {
    stmt := &ast.ShowDatabasesStatement{Token: p.curToken}

    if !p.expectPeek(lexer.DATABASES) {
       return nil, fmt.Errorf("expected DATABASES keyword")
    }

    return stmt, nil
}

// parseShowTablesStatement 解析SHOW TABLES语句
func (p *Parser) parseShowTablesStatement() (*ast.ShowTablesStatement, error) {
    stmt := &ast.ShowTablesStatement{Token: p.curToken}

    if !p.expectPeek(lexer.TABLES) {
       return nil, fmt.Errorf("expected TABLES keyword")
    }

    return stmt, nil
}

...
4. 存储引擎实现操作

internal/storage/memory.go:

go 复制代码
// ShowDatabases 显示所有数据库
func (b *MemoryBackend) ShowDatabases() *Results {
    b.Mu.RLock()
    defer b.Mu.RUnlock()

    results := &Results{
       Columns: []ResultColumn{
          {Name: "Database", Type: "TEXT"},
       },
       Rows: make([][]Cell, 0),
    }

    for dbName := range b.Databases {
       results.Rows = append(results.Rows, []Cell{
          {Type: CellTypeText, TextValue: dbName},
       })
    }

    // 按名称排序
    sort.Slice(results.Rows, func(i, j int) bool {
       return results.Rows[i][0].TextValue < results.Rows[j][0].TextValue
    })
    return results
}

// ShowTables 显示数据库中的所有表
func (b *MemoryBackend) ShowTables(connCtx context.DBContext) *Results {
    b.Mu.RLock()
    defer b.Mu.RUnlock()
    results := &Results{
       Columns: []ResultColumn{
          {Name: "Tables", Type: "TEXT"},
       },
       Rows: make([][]Cell, 0),
    }
    dbName := connCtx.GetDBName()
    if dbName == "" {
       return results
    }
    database := b.Databases[dbName]

    for tableName := range database.Tables {
       results.Rows = append(results.Rows, []Cell{
          {Type: CellTypeText, TextValue: tableName},
       })
    }

    // 按名称排序
    sort.Slice(results.Rows, func(i, j int) bool {
       return results.Rows[i][0].TextValue < results.Rows[j][0].TextValue
    })
    return results
}

...
5. 程序入口处调整

需要调整两个地方,一个是本地命令行版(cmd/main.go),一个是网络版(network/server.go)

  • cmd/main.go:
go 复制代码
func executor(t string) {
    // 分割多个SQL语句(用分号分隔)
    statements := strings.Split(t, ";")

    for _, stmt := range statements {
        ...

       // 创建词法分析器
       l := lexer.NewLexer(strings.NewReader(stmt))

       // 创建语法分析器
       p := parser.NewParser(l)

       // 解析SQL语句
       parsedStmt, err := p.ParseProgram()
       if err != nil {
          fmt.Printf("Parse error: %v\n", err)
          continue
       }

        // 执行SQL语句
        for _, statement := range parsedStmt.Statements {
            if currentDatabase == "" {
               // 检查是否是非数据库操作语句
               _, isCreateDB := statement.(*ast.CreateDatabaseStatement)
               _, isShowDBs := statement.(*ast.ShowDatabasesStatement)
               _, isDropDB := statement.(*ast.DropDatabaseStatement)
               _, isUseDB := statement.(*ast.UseDatabaseStatement)
               _, isShowTables := statement.(*ast.ShowTablesStatement)
               // 如果不是允许的语句类型,则提示需要选择数据库
               if !isCreateDB && !isShowDBs && !isDropDB && !isUseDB && !isShowTables {
                  fmt.Println("No database selected. Use 'USE database_name' to select a database.")
                  continue
               }
            }
          switch s := statement.(type) {
            case *ast.ShowDatabasesStatement:
                result := backend.ShowDatabases()
                printResults(result)
            case *ast.ShowTablesStatement:
                result := backend.ShowTables(&dbContextAdapter{&currentDatabase})
                printResults(result)
             ...
          default:
             fmt.Printf("Unsupported statement type: %T\n", s)
          }
       }
    }
}
  • network/server.go:
go 复制代码
// network/server.go
package network

import (
    "bufio"
    "fmt"
    "net"
    "strings"
    "sync"
    "ziyi.db.com/internal/ast"
    "ziyi.db.com/internal/lexer"
    "ziyi.db.com/internal/parser"
    "ziyi.db.com/internal/storage"
)

const DefaultPort = "3118"

    ...

func (s *Server) executeCommand(conn net.Conn, command string) string {
    ...

    var result string
    for _, statement := range parsedStmt.Statements {
        //检查是否选择了数据库
        if connCtx.GetDBName() == "" {
           // 检查是否是非数据库操作语句
           _, isCreateDB := statement.(*ast.CreateDatabaseStatement)
           _, isShowDBs := statement.(*ast.ShowDatabasesStatement)
           _, isDropDB := statement.(*ast.DropDatabaseStatement)
           _, isUseDB := statement.(*ast.UseDatabaseStatement)
           _, isShowTables := statement.(*ast.ShowTablesStatement)
           // 如果不是允许的语句类型,则提示需要选择数据库
           if !isCreateDB && !isShowDBs && !isDropDB && !isUseDB && !isShowTables {
              result += "No database selected. Use 'USE database_name' to select a database."
              continue
           }
        }
       switch stmt := statement.(type) {
        case *ast.ShowDatabasesStatement:
            results := s.backend.ShowDatabases()
            if err != nil {
               result += fmt.Sprintf("Error: %v\n", err)
            } else {
               result += formatResults(results) + "\n"
            }
        case *ast.ShowTablesStatement:
            results := s.backend.ShowTables(connCtx)
            if err != nil {
               result += fmt.Sprintf("Error: %v\n", err)
            } else {
               result += formatResults(results) + "\n"
            }
            ...
       default:
          result += fmt.Sprintf("Unsupported statement type: %T\n", stmt)
       }
    }

    return strings.TrimSpace(result)
}

测试

测试命令:

sql 复制代码
-- 测试show databases:
create database test;
create database test2;
show databases;

-- 测试show tables:
use test2;
create table users (id INT PRIMARY KEY,name text,age INT);
create table products (id INT PRIMARY KEY,name text,price FLOAT);
show tables;

效果:

相关推荐
驰羽4 小时前
[GO]Go语言泛型详解
开发语言·golang·xcode
润 下4 小时前
C语言——深入解析C语言指针:从基础到实践从入门到精通(二)
c语言·开发语言·经验分享·笔记·学习·程序人生
程序员卷卷狗4 小时前
JVM实战:从内存模型到性能调优的全链路剖析
java·jvm·后端·性能优化·架构
王中阳Go4 小时前
Python 的 PyPy 能追上 Go 的性能吗?
后端·python·go
Goboy5 小时前
控制仙术流程 - 抉择与循环的艺术
后端·python
布伦鸽5 小时前
C# WPF DataGrid使用Observable<Observable<object>类型作为数据源
开发语言·c#·wpf
say_fall5 小时前
精通C语言(4.四种动态内存有关函数)
c语言·开发语言
暴力求解5 小时前
c++类和对象(下)
开发语言·c++·算法
有想法的py工程师5 小时前
PostgreSQL 测试磁盘性能
数据库·postgresql