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
核心思想:
-
双重解析函数:每个token可以有两种解析函数
- 前缀函数:处理标识符、字面量或前缀运算符(如-x, !x)
- 中缀函数:处理二元运算符(如x + y, a = b)
-
优先级驱动解析:通过比较运算符优先级决定解析顺序
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
这个例子展示了如何处理:
- 比较运算符(
>) - 逻辑运算符(
AND、OR) - 括号分组
- 字符串字面量
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解析与执行系统奠定了坚实基础。