Go语言从零构建SQL数据库(5)-Pratt解析算法:SQL表达式解析的核心引擎

162 阅读2分钟

Pratt解析算法:SQL表达式解析的核心引擎

1. 算法概述与工作原理

Pratt解析算法(自顶向下运算符优先级解析)是一种优雅的表达式解析方法,特别适合处理具有不同优先级运算符的复杂表达式。在我们的SQL解析器中,它负责解析WHERE子句条件、JOIN条件等表达式。

flowchart TD
    A[输入表达式] --> B[解析第一个标记<br>标识符/字面量]
    B --> C[检查下一个标记]
    C --> D{是运算符?}
    D -->|否| E[返回已解析表达式]
    D -->|是| F{运算符优先级<br>高于当前上下文?}
    F -->|否| E
    F -->|是| G[消费运算符标记]
    G --> H[递归解析右侧表达式]
    H --> I[构建二元表达式]
    I --> C
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#bfb,stroke:#333,stroke-width:2px
    style I fill:#bbf,stroke:#333,stroke-width:2px

核心思想:

  1. 双重解析函数:每个token可以有两种解析函数

    • 前缀函数:处理标识符、字面量或前缀运算符(如-x, !x)
    • 中缀函数:处理二元运算符(如x + y, a = b)
  2. 优先级驱动解析:通过比较运算符优先级决定解析顺序

2. 运算符优先级体系

在SQL中,不同运算符具有不同的优先级,这决定了表达式的解析和计算顺序:

graph TD
    A[运算符优先级层次] --> B[前缀运算符 -x, !x<br>优先级: 7]
    A --> C[乘除运算符 *, /<br>优先级: 6]
    A --> D[加减运算符 +, -<br>优先级: 5]
    A --> E[比较运算符 >, <, >=, <=<br>优先级: 4]
    A --> F[相等运算符 =, !=<br>优先级: 3]
    A --> G[逻辑运算符 AND, OR<br>优先级: 2]
    A --> H[最低优先级<br>优先级: 1]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:4px
    style C fill:#bbf,stroke:#333,stroke-width:4px
    style G fill:#bbf,stroke:#333,stroke-width:4px

3. 算法执行过程示例

示例1:解析 a + b * c

如何正确处理运算符优先级:

sequenceDiagram
    participant Main as 主解析器
    participant Exp as parseExpression
    participant Prefix as 前缀解析函数
    participant Infix as 中缀解析函数
  
    Main->>Exp: 解析表达式 (优先级=LOWEST)
    Exp->>Prefix: 解析标识符 'a'
    Prefix-->>Exp: 返回'a'
    Note over Exp: 检测到'+', 优先级=5
    Exp->>Infix: 解析中缀表达式(左侧='a')
    Infix->>Exp: 递归调用(优先级=5)
    Exp->>Prefix: 解析标识符 'b'
    Prefix-->>Exp: 返回'b'
    Note over Exp: 检测到'*', 优先级=6
    Note over Exp: 6 > 5, 先处理'*'
    Exp->>Infix: 解析中缀表达式(左侧='b')
    Infix->>Exp: 递归调用(优先级=6)
    Exp->>Prefix: 解析标识符 'c'
    Prefix-->>Exp: 返回'c'
    Infix-->>Exp: 返回(b * c)
    Exp-->>Infix: 完成递归, 返回(b * c)
    Infix-->>Exp: 返回(a + (b * c))
    Exp-->>Main: 返回完整表达式树

算法如何区分优先级的示意图:

graph TD
    A["a + b * c"] --> B["解析 'a'"]
    B --> C["检测到 '+'"]
    C --> D["开始处理 'a + ...'"]
    D --> E["解析右侧 'b'"]
    E --> F["检测到 '*'"]
    F --> G["* 优先级 > + 优先级"]
    G --> H["暂停处理加法<br>开始处理 'b * c'"]
    H --> I["解析 'c'"]
    I --> J["完成 'b * c'"]
    J --> K["继续处理 'a + (b * c)'"]
    K --> L["完成表达式解析"]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style L fill:#bfb,stroke:#333,stroke-width:2px
    style G fill:#bbf,stroke:#333,stroke-width:4px

示例2:复杂SQL WHERE条件

SELECT * FROM users WHERE age > 18 AND (status = 'active' OR role = 'admin')

这个WHERE条件的解析过程:

graph TD
    A["age > 18 AND (status = 'active' OR role = 'admin')"] --> B["解析左侧: age > 18"]
    B --> B1["解析标识符 'age'"]
    B1 --> B2["解析运算符 '>'"]
    B2 --> B3["解析数字 18"]
    B3 --> B4["完成 'age > 18'"]
  
    A --> C["检测到 AND"]
    C --> D["开始解析右侧"]
    D --> E["检测到左括号"]
    E --> F["递归解析括号内表达式"]
  
    F --> G1["解析 'status'"]
    G1 --> G2["解析 '='"]
    G2 --> G3["解析 'active'"]
    G3 --> G4["完成 status = 'active'"]
  
    F --> H["检测到 OR"]
    H --> I["解析右侧"]
    I --> I1["解析 'role'"]
    I1 --> I2["解析 '='"]
    I2 --> I3["解析 'admin'"]
    I3 --> I4["完成 role = 'admin'"]
  
    G4 --> J["构建OR表达式: status = 'active' OR role = 'admin'"]
    J --> K["完成括号内表达式"]
    K --> L["构建AND表达式: age > 18 AND (...)"]
    L --> M["完成WHERE条件解析"]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style M fill:#bfb,stroke:#333,stroke-width:2px

这个例子展示了如何处理:

  • 比较运算符(>
  • 逻辑运算符(ANDOR
  • 括号分组
  • 字符串字面量

4. 解析不同类型表达式的方法

classDiagram
    class Parser {
        +prefixParseFns
        +infixParseFns
        +parseExpression()
        +parseIdentifier()
        +parseNumberLiteral()
        +parseStringLiteral()
        +parseGroupedExpression()
        +parseInfixExpression()
    }

    class AST {
        <<interface>>
    }

    class Identifier {
        +Value string
    }

    class BinaryExpression {
        +Left Expression
        +Operator TokenType
        +Right Expression
    }
  
    class SubqueryExpression {
        +Query Statement
    }
  
    AST <|-- Identifier
    AST <|-- BinaryExpression
    AST <|-- SubqueryExpression
    Parser ..> AST : creates

标识符解析

处理普通标识符和表限定标识符(如 users.id):

flowchart TD
    A[解析标识符] --> B["读取标识符名称: 'users'"]
    B --> C{下一个token是点号?}
    C -->|是| D["消费点号"]
    D --> E["读取列名: 'id'"]
    E --> F["创建标识符: 'users.id'"]
    C -->|否| G["创建普通标识符"]
    F --> H[返回标识符]
    G --> H
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style H fill:#bfb,stroke:#333,stroke-width:2px

子查询解析

在解析括号表达式时发现子查询:

flowchart TD
    A[解析括号表达式] --> B["消费左括号'('"]
    B --> C{当前token是SELECT?}
    C -->|是| D["递归调用SELECT解析"]
    C -->|否| E["解析普通括号表达式"]
    D --> F["检查右括号"]
    E --> F
    F --> G["消费右括号"]
    G --> H["返回解析结果"]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style H fill:#bfb,stroke:#333,stroke-width:2px

5. 实际SQL用例解析示例

示例3:复杂JOIN条件

SELECT u.name, o.order_id 
FROM users u 
JOIN orders o ON u.id = o.user_id 
WHERE u.status = 'active' AND o.total > 100

JOIN条件和WHERE条件的解析流程:

flowchart TD
    A[解析SQL语句] --> B[解析SELECT字段]
    B --> C[解析FROM子句]
    C --> D[解析JOIN子句]
    D --> E[识别JOIN类型: INNER JOIN]
    E --> F[解析表名和别名: orders o]
    F --> G[解析ON条件: u.id = o.user_id]
    G --> G1[解析左侧: u.id]
    G1 --> G2[解析等号]
    G2 --> G3[解析右侧: o.user_id]
    G --> H[构建JOIN条件AST节点]
    H --> I[解析WHERE子句]
    I --> J1[解析条件: u.status = 'active']
    J1 --> J2[解析AND]
    J2 --> J3[解析条件: o.total > 100]
    I --> K[构建WHERE条件AST节点]
    K --> L[完成SQL语句解析]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style L fill:#bfb,stroke:#333,stroke-width:2px

示例4:嵌套子查询

SELECT t.name 
FROM (
  SELECT u.name 
  FROM (
    SELECT name FROM users WHERE age > 25
  ) AS u
) AS t
WHERE t.name LIKE 'A%'

多层嵌套子查询的解析过程:

flowchart TD
    A[解析最外层SELECT] --> B[解析FROM子句]
    B --> C[检测左括号]
    C --> D[递归解析第一层子查询]
    D --> E[解析子查询FROM子句]
    E --> F[检测左括号]
    F --> G[递归解析第二层子查询]
    G --> H[解析最内层SELECT: name FROM users WHERE ...]
    H --> I[完成最内层子查询]
    I --> J[返回到第一层]
    J --> K[处理AS u别名]
    K --> L[完成第一层子查询]
    L --> M[返回到外层]
    M --> N[处理AS t别名]
    N --> O[解析WHERE t.name LIKE 'A%']
    O --> P[完成整个SQL语句解析]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style P fill:#bfb,stroke:#333,stroke-width:2px

这个例子展示了Pratt算法如何处理递归嵌套结构,每次遇到新的子查询,都会递归调用SELECT语句解析器,然后回到上一层继续解析。

6. 与传统递归下降解析的比较

graph TB
    subgraph "传统递归下降解析"
    A1[为每个文法规则<br>创建解析函数] --> B1[通过硬编码处理<br>操作符优先级]
    B1 --> C1[文法规则越多<br>解析函数越复杂]
    end
  
    subgraph "Pratt解析算法"
    A2[将解析函数与<br>token直接关联] --> B2[通过优先级值<br>动态处理优先级]
    B2 --> C2[添加新操作符<br>只需注册解析函数]
    end
  
    style A1 fill:#f99,stroke:#333,stroke-width:2px
    style A2 fill:#9f9,stroke:#333,stroke-width:2px
    style C1 fill:#f99,stroke:#333,stroke-width:2px
    style C2 fill:#9f9,stroke:#333,stroke-width:2px

7. 应用场景与优势

Pratt算法在SQL解析器中的应用场景:

graph TD
    A[Pratt算法应用场景] --> B[WHERE子句条件解析]
    A --> C[JOIN条件解析]
    A --> D[ORDER BY表达式解析]
    A --> E[HAVING子句条件解析]
    A --> F[嵌套子查询表达式]

    B --> B1["age > 18 AND status = 'active'"]
    C --> C1["users.id = orders.user_id"]
    D --> D1["price * discount DESC"]
    E --> E1["COUNT(*) > 5 OR AVG(score) < 50"]
    F --> F1["id IN (SELECT user_id FROM orders)"]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:4px
    style C fill:#bbf,stroke:#333,stroke-width:4px
    style F fill:#bbf,stroke:#333,stroke-width:4px

Pratt算法的主要优势:

graph LR
    A[Pratt算法优势] --> B[优雅处理运算符优先级]
    A --> C[易于扩展新运算符]
    A --> D[代码结构清晰简洁]
    A --> E[自然支持左结合性]
    A --> F[高效的表达式解析]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bfb,stroke:#333,stroke-width:2px
    style C fill:#bfb,stroke:#333,stroke-width:2px
    style F fill:#bfb,stroke:#333,stroke-width:2px

8. 总结

Pratt解析算法是我们SQL解析器的核心组成部分,专门负责处理表达式解析:

flowchart LR
    A[SQL文本] --> B[词法分析器]
    B --> C[语法分析器]
    C --> D{表达式解析?}
    D -->|是| E[Pratt解析算法]
    E --> F[表达式AST]
    F --> G[回到主解析器]
    D -->|否| G
    G --> H[完整SQL AST]
  
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#bbf,stroke:#333,stroke-width:4px
    style H fill:#bfb,stroke:#333,stroke-width:2px

通过这种算法设计,我们的SQL解析器能够处理各种复杂的SQL表达式,包括多层嵌套的逻辑条件、各种运算符组合以及子查询等高级特性,为实现一个功能完整的SQL解析与执行系统奠定了坚实基础。