SQL解析器:数据库的"翻译官"
1. SQL解析器原理与流程
SQL解析器是数据库系统的核心组件,负责将文本形式的SQL语句转换为系统内部可执行的结构。整个解析过程可以通过下图来表示:
sql
+---------------+ +---------------+ +---------------+ +---------------+
| | | 词法分析器 | | 语法分析器 | | |
| SQL文本输入 | --> | (Lexer) | --> | (Parser) | --> | 抽象语法树 |
| | | | | | | (AST) |
+---------------+ +---------------+ +---------------+ +---------------+
| |
v v
+---------------+ +---------------+
| Token序列 | | 解析表达式 |
| 识别关键字 | | 构建节点 |
| 识别标识符 | | 处理优先级 |
| 识别操作符 | | 错误处理 |
+---------------+ +---------------+
实际解析流程示例
以一个简单的查询为例:SELECT id, name FROM users WHERE age > 18;
第一步:词法分析(分词)
SQL文本首先经过词法分析器处理,被拆分成一系列Token:
sql
Token序列:
SELECT → id → , → name → FROM → users → WHERE → age → > → 18 → ;
核心代码:词法分析器如何识别Token
go
// 创建词法分析器
lexer := lexer.NewLexer("SELECT id, name FROM users WHERE age > 18;")
// 词法分析器核心方法
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()}
}
}
}
第二步:语法分析(构建语法树)
Token序列传递给语法分析器,根据SQL语法规则构建抽象语法树:
核心代码:语法分析器如何分派处理不同语句
go
// 解析入口
func (p *Parser) Parse() (ast.Statement, error) {
// 根据第一个Token判断SQL语句类型
switch p.currToken.Type {
case lexer.SELECT:
return p.parseSelectStatement()
case lexer.INSERT:
return p.parseInsertStatement()
case lexer.UPDATE:
return p.parseUpdateStatement()
case lexer.CREATE:
if p.peekTokenIs(lexer.TABLE) {
return p.parseCreateTableStatement()
}
return nil, fmt.Errorf("不支持的CREATE语句")
default:
return nil, fmt.Errorf("不支持的SQL语句类型: %s", p.currToken.Literal)
}
}
解析SELECT语句的关键代码:
go
func (p *Parser) parseSelectStatement() (*ast.SelectStatement, error) {
stmt := &ast.SelectStatement{}
p.nextToken() // 跳过SELECT关键字
// 1. 解析列名列表
columns, err := p.parseExpressionList(lexer.COMMA)
if err != nil {
return nil, err
}
stmt.Columns = columns
// 2. 解析FROM子句和表名
p.nextToken()
if !p.currTokenIs(lexer.FROM) {
return nil, fmt.Errorf("期望FROM,但得到%s", p.currToken.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
}
// 4. 解析其他可选子句(ORDER BY, LIMIT等)
return stmt, nil
}
2. 抽象语法树(AST)详解
抽象语法树是SQL语句的树状结构表示,每个节点代表SQL语句的一个组成部分。
AST的基本节点类型
go
// 所有AST节点的基础接口
type Node interface {
TokenLiteral() string // 返回节点对应的词法单元字面值
String() string // 返回节点的字符串表示
}
// SQL语句节点
type Statement interface {
Node
statementNode()
}
// 表达式节点
type Expression interface {
Node
expressionNode()
}
直观理解AST结构
对于查询语句 SELECT id, name FROM users WHERE age > 18;
,最终构建的AST如下:
css
SelectStatement
├── Columns: [
│ ├── Identifier{Value: "id"}
│ └── Identifier{Value: "name"}
│ ]
├── TableName: "users"
└── Where: BinaryExpression{
├── Left: Identifier{Value: "age"}
├── Operator: GREATER
└── Right: LiteralExpression{Value: "18", Type: NUMBER}
}
这个树状结构直观地展示了SQL语句的各个组成部分和它们之间的关系。
AST构建的渐进过程
AST不是一次性构建完成的,而是随着解析过程逐步构建:
css
1. 初始化空的SelectStatement节点
SelectStatement{} → 空结构
2. 解析列名列表
SelectStatement{
Columns: [
Identifier{Value: "id"},
Identifier{Value: "name"}
]
}
3. 添加表名
SelectStatement{
Columns: [...],
TableName: "users"
}
4. 添加WHERE条件
SelectStatement{
Columns: [...],
TableName: "users",
Where: BinaryExpression{...}
}
3. 表达式解析的关键技术
表达式解析是SQL解析器最复杂的部分,尤其是处理运算符优先级和嵌套表达式。我们使用Pratt解析技术来高效处理这些问题。
Pratt解析的核心代码
go
// 表达式解析的核心函数
func (p *Parser) parseExpression(precedence int) (ast.Expression, error) {
// 1. 获取前缀解析函数(处理标识符、字面量等)
prefix := p.prefixParseFns[p.currToken.Type]
if prefix == nil {
return nil, fmt.Errorf("找不到%s的前缀解析函数", p.currToken.Literal)
}
// 2. 解析最左侧表达式
leftExp, err := prefix()
if err != nil {
return nil, err
}
// 3. 根据优先级处理中缀表达式(处理运算符如>、=、AND等)
for !p.peekTokenIs(lexer.SEMICOLON) && precedence < p.peekPrecedence() {
infix := p.infixParseFns[p.peekToken.Type]
if infix == nil {
return leftExp, nil
}
p.nextToken() // 移动到运算符
// 构建二元表达式,保证运算符优先级正确
leftExp, err = infix(leftExp)
if err != nil {
return nil, err
}
}
return leftExp, nil
}
运算符优先级处理
定义清晰的优先级确保表达式按照预期顺序解析:
go
// 优先级常量
const (
LOWEST = 1 // 最低优先级
AND_OR = 2 // AND OR
EQUALS = 3 // == !=
LESSGREATER = 4 // > < >= <=
SUM = 5 // + -
PRODUCT = 6 // * /
PREFIX = 7 // -X 或 !X
)
// 运算符优先级映射
var precedences = map[TokenType]int{
EQUAL: EQUALS,
NOT_EQUAL: EQUALS,
LESS: LESSGREATER,
GREATER: LESSGREATER,
AND: AND_OR,
OR: AND_OR,
// ...其他运算符
}
4. 实际案例解析
让我们通过一个完整示例,来展示SQL语句从文本到AST的完整转换过程:
输入SQL:
sql
SELECT id, name FROM users WHERE age > 18 AND role = 'admin';
转换过程:
- 词法分析:将SQL文本拆分为Token序列
- 语法分析:识别SELECT语句的基本结构
- 解析列列表 :识别
id
和name
两个列 - 解析表名 :识别表名
users
- 解析WHERE子句 :
- 解析
age > 18
为一个二元表达式 - 遇到
AND
运算符,创建新的二元表达式 - 解析
role = 'admin'
作为右侧表达式 - 最终WHERE子句表示为嵌套的二元表达式
- 解析
最终AST结构:
css
SelectStatement
├── Columns: [
│ ├── Identifier{Value: "id"}
│ └── Identifier{Value: "name"}
│ ]
├── TableName: "users"
└── Where: BinaryExpression{
├── Left: BinaryExpression{
│ ├── Left: Identifier{Value: "age"}
│ ├── Operator: GREATER
│ └── Right: LiteralExpression{Value: "18", Type: NUMBER}
│ }
├── Operator: AND
└── Right: BinaryExpression{
├── Left: Identifier{Value: "role"}
├── Operator: EQUAL
└── Right: LiteralExpression{Value: "admin", Type: STRING}
}
}
小结
通过以上解析过程,我们实现了从SQL文本到内部数据结构的转换,这个结构可以被数据库引擎进一步处理。SQL解析器的质量直接影响数据库系统的稳定性和性能,一个好的解析器应当:
- 能够正确识别各种SQL语法
- 提供清晰的错误信息
- 构建结构良好的AST
- 为后续的查询计划和优化提供基础
在接下来的章节中,我们将完善这个解析器实现SQL语句更为全面的解析,包括drop关键字,xxx join,delete,还有嵌套查询这些功能的解析。