SQL解析器:数据库的"翻译官"图解与代码详解
图解SQL解析过程
SQL解析器就像是人类语言与计算机之间的翻译官,将我们书写的SQL语句转换成数据库能够理解和执行的结构。
1. 词法分析:从文本到标记
词法分析器的工作就像是将一段完整的句子拆分成单词和标点符号。对于SQL语句 SELECT id, name FROM users WHERE age > 18
:
词法分析器核心代码示例
词法分析器读取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语法规则构建语句结构:
语法分析的入口代码
语法分析器根据第一个标记判断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
:
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解析器看似复杂,但每个部分都有明确的功能:
- 词法分析:将SQL文本拆分成标记
- 语法分析:将标记组织成有意义的语句结构
- 表达式解析:处理运算符优先级和嵌套表达式
- 抽象语法树:提供SQL的结构化表示
这些组件共同工作,将人类可读的SQL转换为数据库可处理的结构,为执行引擎、优化器和查询计划生成器提供基础。
在后续章节中,我们将进一步探索如何基于这个解析器实现更多高级功能,包括嵌套查询,join方法等等。