SQL解析器系列:实现ALTER TABLE语句
在SQL解析器系列的最后一篇文章中,我们将聚焦于ALTER TABLE语句的实现。ALTER TABLE是数据定义语言(DDL)中的重要组成部分,允许在不丢失数据的情况下修改表结构。实现这个功能将为我们的SQL解析器画上圆满的句号,也为下一阶段的查询执行引擎开发奠定基础。
ALTER TABLE语法与复杂性
ALTER TABLE语句的语法相对复杂,支持多种操作类型:
ALTER TABLE users
ADD COLUMN email VARCHAR(100) NOT NULL,
MODIFY COLUMN name VARCHAR(50) DEFAULT 'unknown',
DROP COLUMN age,
ADD CONSTRAINT uk_email UNIQUE (email);
ALTER TABLE语句的基本语法结构如下图所示:
flowchart LR
A[ALTER TABLE] --> B[表名]
B --> C[操作1]
C --> D{更多操作?}
D -->|是| E[逗号]
E --> F[操作2]
F --> D
D -->|否| G[分号可选]
与其他SQL语句相比,ALTER TABLE的实现面临以下挑战:
- 多操作支持:一条ALTER语句可以包含多个子操作,每个操作有不同的语法结构
- 复杂的类型定义:列类型可能包含参数,如
DECIMAL(10,2) - 多种约束类型:需要支持PRIMARY KEY、UNIQUE、FOREIGN KEY等不同约束
- 默认值表达式:默认值可以是简单字面量或复杂表达式
各种ALTER操作类型的语法结构如下:
flowchart TD
A[ALTER操作] --> B{操作类型}
B -->|ADD COLUMN| C[列名 + 类型 + 属性]
B -->|MODIFY COLUMN| D[列名 + 新类型 + 属性]
B -->|CHANGE COLUMN| E[旧列名 + 新列名 + 类型 + 属性]
B -->|DROP COLUMN| F[列名]
B -->|ADD CONSTRAINT| G[约束名 + 约束类型 + 参数]
B -->|DROP CONSTRAINT| H[约束名]
C --> I[列属性]
D --> I
E --> I
I --> I1[NOT NULL]
I --> I2[DEFAULT 值]
I --> I3[COMMENT '注释']
G --> J{约束类型}
J -->|UNIQUE| K[列名列表]
J -->|PRIMARY KEY| L[列名列表]
J -->|FOREIGN KEY| M[列名列表 + REFERENCES + 表名 + 列名列表]
数据结构设计
为了表示ALTER TABLE语句,我们设计了一个层次化的AST结构:
classDiagram
class AlterTableStatement {
+String TableName
+[]AlterAction Actions
}
class AlterAction {
+AlterActionType Type
+*AlterColumnDef ColumnDef
+String ColumnName
+*Constraint Constraint
+String ConstraintName
}
class AlterColumnDef {
+String OldName
+String Name
+String Type
+bool NotNull
+*ColumnDefault Default
+*ColumnComment Comment
}
class Constraint {
+String Name
+ConstraintType Type
+[]String Columns
+String ReferencedTable
+[]String ReferencedCols
}
AlterTableStatement "1" --> "*" AlterAction : contains
AlterAction --> AlterColumnDef : for column operations
AlterAction --> Constraint : for constraint operations
这种设计允许我们在一个语句中表示多种ALTER操作,每种操作都有其特定的结构和属性。ALTER操作类型通过枚举定义:
// AlterActionType 表示ALTER TABLE操作的类型
type AlterActionType int
const (
AddColumn AlterActionType = iota
ModifyColumn
ChangeColumn
DropColumn
AddConstraint
DropConstraint
)
解析实现
主解析函数的流程
ALTER TABLE语句的解析过程可以表示为以下流程图:
flowchart TD
A[开始解析] --> B[跳过ALTER TABLE关键字]
B --> C[解析表名]
C --> D[解析第一个ALTER操作]
D --> E{当前是逗号?}
E -->|是| F[跳过逗号]
F --> G[解析下一个ALTER操作]
G --> E
E -->|否| H{当前是分号?}
H -->|是| I[跳过分号]
H -->|否| J[完成]
I --> J
J --> K[返回AlterTableStatement]
解析的核心代码如下:
func (p *Parser) parseAlterTableStatement() (*ast.AlterTableStatement, error) {
stmt := &ast.AlterTableStatement{}
// 跳过ALTER TABLE
p.nextToken() // 跳过ALTER
p.nextToken() // 跳过TABLE
// 解析表名
if !p.currTokenIs(lexer.IDENTIFIER) {
return nil, fmt.Errorf("期望表名,但得到%s", p.currToken.Literal)
}
stmt.TableName = p.currToken.Literal
p.nextToken()
// 解析第一个ALTER操作
action, err := p.parseAlterAction()
if err != nil {
return nil, err
}
stmt.Actions = append(stmt.Actions, action)
// 如果有更多的操作(以逗号分隔),继续解析
for p.currTokenIs(lexer.COMMA) {
p.nextToken() // 跳过逗号
action, err := p.parseAlterAction()
if err != nil {
return nil, err
}
stmt.Actions = append(stmt.Actions, action)
}
// 可选的分号
if p.currTokenIs(lexer.SEMICOLON) {
p.nextToken() // 跳过分号
}
return stmt, nil
}
操作类型解析详解
每种ALTER操作类型的解析逻辑都不同,以下是各种操作类型的解析流程:
flowchart TD
A[parseAlterAction] --> B{当前Token类型}
B -->|ADD| C[跳过ADD]
C --> D{下一个是?}
D -->|COLUMN| E[解析ADD COLUMN]
D -->|CONSTRAINT| F[解析ADD CONSTRAINT]
D -->|其他| G[错误]
B -->|MODIFY| H[解析MODIFY COLUMN]
B -->|CHANGE| I[解析CHANGE COLUMN]
B -->|DROP| J[跳过DROP]
J --> K{下一个是?}
K -->|COLUMN| L[解析DROP COLUMN]
K -->|CONSTRAINT| M[解析DROP CONSTRAINT]
K -->|其他| N[错误]
B -->|其他| O[错误]
E --> P[返回ALTER操作]
F --> P
H --> P
I --> P
L --> P
M --> P
特别值得注意的是ADD COLUMN操作的解析过程:
flowchart TD
A[开始解析ADD COLUMN] --> B[解析列名]
B --> C[解析列类型]
C --> D{有括号参数?}
D -->|是| E[解析类型参数]
D -->|否| F[继续]
E --> F
F --> G{有列属性?}
G -->|NOT NULL| H[设置NotNull=true]
G -->|DEFAULT| I[解析默认值表达式]
G -->|COMMENT| J[解析注释字符串]
G -->|否| K[结束]
H --> G
I --> G
J --> G
处理复杂类型参数
处理类型参数(如VARCHAR(100))时,我们需要特别注意嵌套括号的情况。我们使用一个计数器跟踪括号嵌套级别:
flowchart LR
A[遇到左括号] --> B[bracketLevel = 1]
B --> C[跳过左括号]
C --> D[循环]
D --> E{当前字符}
E -->|左括号| F[bracketLevel++]
E -->|右括号| G[bracketLevel--]
F --> H[跳过当前字符]
G --> I{bracketLevel > 0?}
I -->|是| H
I -->|否| J[跳过右括号]
H --> D
J --> K[继续解析]
具体实现代码:
// 处理类型后可能的括号参数,如VARCHAR(100)
if p.currTokenIs(lexer.LPAREN) {
bracketLevel := 1
p.nextToken() // 跳过左括号
// 跳过括号内的所有token直到匹配的右括号
for bracketLevel > 0 && !p.currTokenIs(lexer.EOF) {
if p.currTokenIs(lexer.LPAREN) {
bracketLevel++
} else if p.currTokenIs(lexer.RPAREN) {
bracketLevel--
}
if bracketLevel > 0 {
p.nextToken()
}
}
if p.currTokenIs(lexer.RPAREN) {
p.nextToken() // 跳过右括号
}
}
这种方法确保我们能正确处理嵌套括号,如 DECIMAL(12,2)或更复杂的类型定义。
表达式解析与默认值处理
默认值可以是各种表达式,例如数字、字符串甚至函数调用。我们利用之前实现的Pratt解析算法来处理这些表达式:
flowchart TD
A[解析DEFAULT] --> B[跳过DEFAULT关键字]
B --> C[调用parseExpression]
C --> D[创建ColumnDefault结构]
D --> E[返回结果]
if p.currTokenIs(lexer.DEFAULT) {
p.nextToken() // 跳过DEFAULT
// 解析默认值表达式
expr, err := p.parseExpression(LOWEST)
if err != nil {
return nil, err
}
colDef.Default = &ast.ColumnDefault{Value: expr}
}
表达式解析的优先级流程如下:
flowchart TD
A[parseExpression] --> B[获取前缀解析函数]
B --> C[解析左侧表达式]
C --> D{下一个Token优先级>当前优先级?}
D -->|是| E[获取中缀解析函数]
E --> F[解析二元表达式]
F --> D
D -->|否| G[返回表达式]
约束解析深入讲解
约束解析是ALTER TABLE中的另一个复杂部分,不同类型的约束有不同的语法结构:
flowchart TD
A[parseConstraint] --> B{约束类型}
B -->|UNIQUE| C[constraint.Type = UniqueConstraint]
C --> D[解析列名列表]
B -->|PRIMARY KEY| E[constraint.Type = PrimaryKeyConstraint]
E --> F[跳过KEY]
F --> D
B -->|FOREIGN KEY| G[constraint.Type = ForeignKeyConstraint]
G --> H[跳过KEY]
H --> I[解析列名列表]
I --> J[期望REFERENCES]
J --> K[解析引用表名]
K --> L[解析引用列名列表]
D --> M[返回约束]
L --> M
约束解析的一个关键部分是解析标识符列表,例如 (id, name, email):
// parseIdentifierList 解析标识符列表
func (p *Parser) parseIdentifierList() ([]string, error) {
identifiers := []string{}
// 跳过左括号
p.nextToken()
// 第一个标识符
if !p.currTokenIs(lexer.IDENTIFIER) {
return nil, fmt.Errorf("期望标识符,但得到%s", p.currToken.Literal)
}
identifiers = append(identifiers, p.currToken.Literal)
// 解析剩余标识符
for p.peekTokenIs(lexer.COMMA) {
p.nextToken() // 跳过当前标识符或逗号
p.nextToken() // 移动到下一个标识符
if !p.currTokenIs(lexer.IDENTIFIER) {
return nil, fmt.Errorf("期望标识符,但得到%s", p.currToken.Literal)
}
identifiers = append(identifiers, p.currToken.Literal)
}
// 期望下一个Token是右括号
if !p.expectPeek(lexer.RPAREN) {
return nil, fmt.Errorf("期望),但得到%s", p.peekToken.Literal)
}
return identifiers, nil
}
测试实现详解
测试是确保解析器正确性的关键。我们为ALTER TABLE设计了全面的测试套件:
flowchart TD
A[TestAlterTableStatement] --> B[定义测试用例]
B --> C[循环测试用例]
C --> D[创建Lexer]
D --> E[创建Parser]
E --> F[调用Parse方法]
F --> G{检查错误}
G -->|有错误| H{期望错误?}
H -->|是| I[测试通过]
H -->|否| J[测试失败]
G -->|无错误| K{期望无错误?}
K -->|是| L[验证解析结果]
K -->|否| M[测试失败]
L --> N{验证成功?}
N -->|是| I
N -->|否| J
测试用例覆盖了各种ALTER TABLE操作:
{
name: "Add Column",
input: "ALTER TABLE users ADD COLUMN email VARCHAR(100) NOT NULL;",
checkTableName: "users",
actionCount: 1,
expectError: false,
},
{
name: "Add Column with Default",
input: "ALTER TABLE users ADD COLUMN score INT DEFAULT 100;",
checkTableName: "users",
actionCount: 1,
expectError: false,
},
{
name: "Multiple Actions",
input: "ALTER TABLE orders ADD COLUMN created_at TIMESTAMP, ADD CONSTRAINT pk_order PRIMARY KEY (id);",
checkTableName: "orders",
actionCount: 2,
expectError: false,
},
{
name: "Drop Column",
input: "ALTER TABLE products DROP COLUMN description;",
checkTableName: "products",
actionCount: 1,
expectError: false,
},
对于每个测试用例,我们验证:
- 是否正确解析表名
- 操作数量是否正确
- 每个操作的类型是否正确
- 操作参数(列定义、约束等)是否正确
错误处理机制
强大的错误处理对于解析器至关重要。我们采用详细的错误消息,帮助用户快速定位问题:
flowchart LR
A[遇到错误情况] --> B[构建详细错误信息]
B --> C[包含当前Token信息]
C --> D[包含期望的Token]
D --> E[包含位置信息]
E --> F[返回错误]
错误处理示例:
if !p.currTokenIs(lexer.IDENTIFIER) {
return nil, fmt.Errorf("期望表名,但得到%s", p.currToken.Literal)
}
if !p.currTokenIs(lexer.LPAREN) {
return nil, fmt.Errorf("期望(,但得到%s", p.currToken.Literal)
}
这种详细的错误提示使得用户能够快速识别和修复SQL语法错误。
编译器设计模式应用
我们的SQL解析器实现展示了多种编译器设计模式:
flowchart TD
A[编译器设计模式] --> B[词法分析模式]
A --> C[递归下降解析]
A --> D[访问者模式]
A --> E[解释器模式]
B --> B1[Token分类]
B --> B2[状态转换]
C --> C1[自顶向下]
C --> C2[预测解析]
D --> D1[AST遍历]
E --> E1[AST解释执行]
特别值得一提的是Pratt解析算法的应用,它使我们能够轻松处理复杂表达式的优先级:
flowchart LR
A[表达式] --> B[运算符优先级表]
B --> C[前缀解析函数]
B --> D[中缀解析函数]
C --> E[parseExpression]
D --> E
E --> F[AST表达式节点]
技术难点与解决方案详解
1. 多操作处理
解析多个操作需要仔细处理Token序列,特别是在操作之间的逗号和语句结尾的分号:
flowchart TD
A[解析第一个操作] --> B{当前Token}
B -->|逗号| C[跳过逗号]
C --> D[解析下一个操作]
D --> B
B -->|分号| E[跳过分号]
B -->|其他| F[结束]
E --> F
我们的实现确保正确处理操作序列,即使它们跨越多行:
// 如果有更多的操作(以逗号分隔),继续解析
for p.currTokenIs(lexer.COMMA) {
p.nextToken() // 跳过逗号
action, err := p.parseAlterAction()
if err != nil {
return nil, err
}
stmt.Actions = append(stmt.Actions, action)
}
2. 嵌套括号处理
处理类型参数中的嵌套括号是一个挑战,我们通过维护括号嵌套级别解决这个问题:
flowchart LR
A["DECIMAL(10,2)"] --> B[bracketLevel=1]
B --> C[处理10]
C --> D[处理逗号]
D --> E[处理2]
E --> F[bracketLevel=0]
F --> G[继续解析]
代码实现通过跟踪嵌套级别,确保只有在所有括号闭合后才结束解析:
bracketLevel := 1
p.nextToken() // 跳过左括号
for bracketLevel > 0 && !p.currTokenIs(lexer.EOF) {
if p.currTokenIs(lexer.LPAREN) {
bracketLevel++
} else if p.currTokenIs(lexer.RPAREN) {
bracketLevel--
}
if bracketLevel > 0 {
p.nextToken()
}
}
3. 表达式解析集成
将表达式解析集成到ALTER TABLE实现中,我们重用了现有的表达式解析功能:
flowchart TD
A[解析DEFAULT] --> B[调用parseExpression]
B --> C[创建表达式AST]
C --> D[设置列默认值]
这种集成允许我们解析复杂的默认值表达式,如 DEFAULT 10 * 2 + 5。
SQL解析器系列总结
至此,我们的SQL解析器已经实现了所有核心功能,形成了一个完整的系统:
flowchart LR
SQL["SQL文本\n(SELECT * FROM users WHERE id > 10)"] --> Lexer["词法分析器"]
Lexer --> Tokens["Token流\n[SELECT, *, FROM, users, WHERE, id, >, 10]"]
Tokens --> Parser["语法分析器"]
Parser --> AST["抽象语法树(AST)"]
Lexer -- 步骤1 --> L1["读取字符流"]
L1 -- 步骤2 --> L2["识别关键字/标识符"]
L2 -- 步骤3 --> Tokens
Parser -- 步骤4 --> P1["识别语句类型\n(SELECT语句)"]
P1 -- 步骤5 --> P2["解析表达式\n(Pratt算法)"]
P2 -- 步骤6 --> AST
AST -- 步骤7 --> A1["SELECT节点"]
A1 -- 步骤8 --> A2["列: *\n表: users\n条件: id > 10"]
style SQL fill:#f5f5f5,stroke:#333,stroke-width:2px
style Lexer fill:#d4e6f1,stroke:#333,stroke-width:2px
style Tokens fill:#d4e6f1,stroke:#333,stroke-width:2px
style Parser fill:#d5e8d4,stroke:#333,stroke-width:2px
style AST fill:#d5e8d4,stroke:#333,stroke-width:2px
style L1 fill:#e1f5fe,stroke:#333,stroke-width:1px
style L2 fill:#e1f5fe,stroke:#333,stroke-width:1px
style P1 fill:#e8f5e9,stroke:#333,stroke-width:1px
style P2 fill:#e8f5e9,stroke:#333,stroke-width:1px
style A1 fill:#f9fbe7,stroke:#333,stroke-width:1px
style A2 fill:#f9fbe7,stroke:#333,stroke-width:1px
我们支持的SQL功能包括:
flowchart TD
SQL[SQL解析器] --> DML[DML]
SQL --> DDL[DDL]
SQL --> ADV[高级特性]
DML --> SELECT[SELECT]
DML --> INSERT[INSERT]
DML --> UPDATE[UPDATE]
DML --> DELETE[DELETE]
SELECT --> COL[列选择]
SELECT --> JOIN[表连接]
SELECT --> WHERE[条件过滤]
SELECT --> ORDER[排序]
DDL --> CREATE[CREATE TABLE]
DDL --> DROP[DROP TABLE]
DDL --> ALTER[ALTER TABLE]
ALTER --> ADD[ADD COLUMN]
ALTER --> MODIFY[MODIFY COLUMN]
ALTER --> DROP_COL[DROP COLUMN]
ALTER --> ADD_CONS[添加约束]
ALTER --> DROP_CONS[删除约束]
ADV --> NESTED[嵌套查询]
ADV --> EXPR[表达式]
ADV --> ALIAS[表别名]
关键技术点回顾
在整个SQL解析器实现过程中,我们应用了多种编译原理技术:
classDiagram
class Lexer {
+input string
+position int
+readPosition int
+ch byte
+NextToken() Token
}
class Parser {
+lexer *Lexer
+currToken Token
+peekToken Token
+Parse() Statement
}
class AST {
<<interface>>
+statementNode()
+TokenLiteral() string
}
Lexer --> Parser : provides tokens
Parser --> AST : builds
- 词法分析:将SQL文本分解为Token序列
- 递归下降解析:采用自顶向下的方式构建语法树
- Pratt解析算法:处理不同优先级的运算符
- 抽象语法树:构建SQL语句的结构化表示
下一步:查询执行引擎
完成SQL解析器后,我们将开发查询执行引擎,这包括以下主要组件:
flowchart LR
A[SQL解析器] --> B[查询执行引擎]
B --> C[AST转换器]
C --> D[执行计划生成器]
D --> E[存储引擎接口]
E --> F[结果处理器]
- AST转换为执行计划:将抽象语法树转换为可执行的操作序列
- 数据存储接口:设计与底层存储引擎的交互方式
- 执行器实现:执行各种SQL操作的具体逻辑
- 结果集处理:处理查询结果并返回给用户
这些组件将构成一个完整的查询执行系统,使我们能够实际运行SQL查询并获取结果。
SQL解析器是数据库系统的关键组件,它将用户输入的SQL语句转换为系统可理解的结构。通过实现ALTER TABLE语句,我们完成了SQL解析器的所有核心功能,为后续开发查询执行引擎奠定了坚实基础。
在下一阶段,我们将开始构建查询执行引擎,使我们的系统能够真正执行SQL操作并返回结果。敬请期待!