Go语言从零构建SQL数据库(4)-解析器

157 阅读4分钟

SQL解析器:数据库的"翻译官"图解与代码详解

图解SQL解析过程

SQL解析器就像是人类语言与计算机之间的翻译官,将我们书写的SQL语句转换成数据库能够理解和执行的结构。

flowchart LR
    A[SQL文本] --> B[词法分析]
    B --> C[语法分析]
    C --> D[抽象语法树]
    D --> E[查询执行]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style D fill:#bbf,stroke:#333,stroke-width:2px
    style E fill:#bfb,stroke:#333,stroke-width:2px

1. 词法分析:从文本到标记

词法分析器的工作就像是将一段完整的句子拆分成单词和标点符号。对于SQL语句 SELECT id, name FROM users WHERE age > 18

flowchart LR
    A["SELECT id, name FROM users WHERE age > 18"] --> B["SELECT"]
    A --> C["id"]
    A --> D[","]
    A --> E["name"]
    A --> F["FROM"]
    A --> G["users"]
    A --> H["WHERE"]
    A --> I["age"]
    A --> J[">"]
    A --> K["18"]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px

词法分析器核心代码示例

词法分析器读取SQL文本,按字符处理,输出标记序列:

// 词法分析器核心代码 - 简化展示
func (l *Lexer) NextToken() Token {
    // 跳过空白字符
    l.skipWhitespace()
  
    // 根据当前字符判断Token类型
    switch l.ch {
    case '=':                                  // 识别等号
        return Token{Type: EQUAL, Literal: "="}
    case ',':                                  // 识别逗号
        return Token{Type: COMMA, Literal: ","}
    case '>':                                  // 识别大于号
        return Token{Type: GREATER, Literal: ">"}
    // ... 其他特殊字符处理
  
    default:
        if isLetter(l.ch) {                    // 识别关键字或标识符
            literal := l.readIdentifier()
            tokenType := lookupKeyword(literal) // 判断是否是关键字
            return Token{Type: tokenType, Literal: literal}
        } else if isDigit(l.ch) {              // 识别数字
            return Token{Type: NUMBER, Literal: l.readNumber()}
        }
    }
}

这段代码展示了词法分析器如何一个字符一个字符地读取SQL文本,并根据字符类型创建不同的标记。

2. 语法分析:构建有意义的结构

语法分析器接收标记流,根据SQL语法规则构建语句结构:

graph TD
    A[SELECT语句] --> B[选择什么]
    A --> C[从哪里选]
    A --> D[满足什么条件]
    B --> E[id, name字段]
    C --> F[users表]
    D --> G[age > 18]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style G fill:#bbf,stroke:#333,stroke-width:2px

语法分析的入口代码

语法分析器根据第一个标记判断SQL语句类型,并分派给相应的处理函数:

// 语法分析入口 - 判断SQL语句类型
func (p *Parser) Parse() (ast.Statement, error) {
    switch p.currToken.Type {
    case lexer.SELECT:                         // 处理SELECT语句
        return p.parseSelectStatement()
    case lexer.INSERT:                         // 处理INSERT语句
        return p.parseInsertStatement()
    case lexer.UPDATE:                         // 处理UPDATE语句
        return p.parseUpdateStatement()
    case lexer.DELETE:                         // 处理DELETE语句
        return p.parseDeleteStatement()
    case lexer.CREATE:                         // 处理CREATE语句
        if p.peekTokenIs(lexer.TABLE) {
            return p.parseCreateTableStatement()
        }
        return nil, fmt.Errorf("不支持的CREATE语句")
    case lexer.DROP:                           // 处理DROP语句
        if p.peekTokenIs(lexer.TABLE) {
            return p.parseDropTableStatement()
        }
        return nil, fmt.Errorf("不支持的DROP语句")
    default:
        return nil, fmt.Errorf("不支持的SQL语句类型: %s", p.currToken.Literal)
    }
}

SELECT语句的解析流程

解析SELECT语句的代码展示了如何逐步构建语句结构:

// SELECT语句解析 - 关键步骤
func (p *Parser) parseSelectStatement() (*ast.SelectStatement, error) {
    stmt := &ast.SelectStatement{}           // 初始化空的SELECT语句节点
  
    p.nextToken()                            // 跳过SELECT关键字
  
    // 1. 解析列名列表
    columns, err := p.parseExpressionList(lexer.COMMA)
    if err != nil {
        return nil, err
    }
    stmt.Columns = columns                   // 设置选择的列
  
    // 2. 解析FROM子句和表名
    if !p.expectPeek(lexer.FROM) {           // 期望下一个标记是FROM
        return nil, fmt.Errorf("期望FROM,但得到%s", p.peekToken.Literal)
    }
  
    p.nextToken()                            // 跳过FROM
    if !p.currTokenIs(lexer.IDENTIFIER) {    // 期望当前标记是标识符(表名)
        return nil, fmt.Errorf("期望表名,但得到%s", p.currToken.Literal)
    }
    stmt.TableName = p.currToken.Literal     // 设置表名
  
    // 3. 解析WHERE子句(可选)
    p.nextToken()
    if p.currTokenIs(lexer.WHERE) {
        p.nextToken()                        // 跳过WHERE
        expr, err := p.parseExpression(LOWEST) // 解析条件表达式
        if err != nil {
            return nil, err
        }
        stmt.Where = expr                    // 设置WHERE条件
    }
  
    return stmt, nil                         // 返回完整的SELECT语句节点
}

3. 抽象语法树(AST):SQL的结构化表示

抽象语法树是SQL语句的树状结构表示,每个节点代表语句的一个组成部分。

AST的基本节点类型

// AST核心接口定义
type Node interface {                        // 所有AST节点的基础接口
    TokenLiteral() string                    // 返回节点对应的词法单元字面值
    String() string                          // 返回节点的字符串表示
}

type Statement interface {                   // SQL语句节点
    Node
    statementNode()                          // 标记方法,表明这是语句节点
}

type Expression interface {                  // 表达式节点
    Node
    expressionNode()                         // 标记方法,表明这是表达式节点
}

示例:SELECT语句的AST图解

对于 SELECT id, name FROM users WHERE age > 18

graph TD
    A[SelectStatement] --> B[Columns]
    A --> C[TableName: users]
    A --> D[Where条件]
    B --> E[Identifier: id]
    B --> F[Identifier: name]
    D --> G[BinaryExpression]
    G --> H[Left: Identifier age]
    G --> I[Operator: >]
    G --> J[Right: NumberLiteral 18]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style G fill:#bbf,stroke:#333,stroke-width:4px

AST节点的定义

AST节点类型展示了如何用代码表示SQL的各个组成部分:

// SELECT语句节点定义
type SelectStatement struct {
    Columns     []Expression        // 选择的列列表,如 id, name
    TableName   string              // 查询的表名,如 users
    TableAlias  string              // 表别名,如 u
    Where       Expression          // WHERE条件,如 age > 18
}

// 二元表达式节点(用于WHERE条件等)
type BinaryExpression struct {
    Left      Expression            // 左操作数
    Operator  TokenType             // 操作符(如 >, =, AND)
    Right     Expression            // 右操作数
}

// 标识符节点(列名、表名等)
type Identifier struct {
    Value    string                 // 标识符的名称,如 "id", "users"
}

小结与实际应用

SQL解析器看似复杂,但每个部分都有明确的功能:

  1. 词法分析:将SQL文本拆分成标记
  2. 语法分析:将标记组织成有意义的语句结构
  3. 表达式解析:处理运算符优先级和嵌套表达式
  4. 抽象语法树:提供SQL的结构化表示

这些组件共同工作,将人类可读的SQL转换为数据库可处理的结构,为执行引擎、优化器和查询计划生成器提供基础。

在后续章节中,我们将进一步探索如何基于这个解析器实现更多高级功能,包括嵌套查询,join方法等等。