SQL解析器系列:实现ALTER TABLE语句

SQL解析器系列:实现ALTER TABLE语句

在SQL解析器系列的最后一篇文章中,我们将聚焦于ALTER TABLE语句的实现。ALTER TABLE是数据定义语言(DDL)中的重要组成部分,允许在不丢失数据的情况下修改表结构。实现这个功能将为我们的SQL解析器画上圆满的句号,也为下一阶段的查询执行引擎开发奠定基础。

ALTER TABLE语法与复杂性

ALTER TABLE语句的语法相对复杂,支持多种操作类型:

sql 复制代码
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的实现面临以下挑战:

  1. 多操作支持:一条ALTER语句可以包含多个子操作,每个操作有不同的语法结构
  2. 复杂的类型定义 :列类型可能包含参数,如 DECIMAL(10,2)
  3. 多种约束类型:需要支持PRIMARY KEY、UNIQUE、FOREIGN KEY等不同约束
  4. 默认值表达式:默认值可以是简单字面量或复杂表达式

各种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操作类型通过枚举定义:

go 复制代码
// 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]

解析的核心代码如下:

go 复制代码
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[继续解析]

具体实现代码:

go 复制代码
// 处理类型后可能的括号参数,如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[返回结果]
go 复制代码
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)

go 复制代码
// 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操作:

go 复制代码
{
    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,
},

对于每个测试用例,我们验证:

  1. 是否正确解析表名
  2. 操作数量是否正确
  3. 每个操作的类型是否正确
  4. 操作参数(列定义、约束等)是否正确

错误处理机制

强大的错误处理对于解析器至关重要。我们采用详细的错误消息,帮助用户快速定位问题:

flowchart LR A[遇到错误情况] --> B[构建详细错误信息] B --> C[包含当前Token信息] C --> D[包含期望的Token] D --> E[包含位置信息] E --> F[返回错误]

错误处理示例:

go 复制代码
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

我们的实现确保正确处理操作序列,即使它们跨越多行:

go 复制代码
// 如果有更多的操作(以逗号分隔),继续解析
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[继续解析]

代码实现通过跟踪嵌套级别,确保只有在所有括号闭合后才结束解析:

go 复制代码
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
  1. 词法分析:将SQL文本分解为Token序列
  2. 递归下降解析:采用自顶向下的方式构建语法树
  3. Pratt解析算法:处理不同优先级的运算符
  4. 抽象语法树:构建SQL语句的结构化表示

下一步:查询执行引擎

完成SQL解析器后,我们将开发查询执行引擎,这包括以下主要组件:

flowchart LR A[SQL解析器] --> B[查询执行引擎] B --> C[AST转换器] C --> D[执行计划生成器] D --> E[存储引擎接口] E --> F[结果处理器]
  1. AST转换为执行计划:将抽象语法树转换为可执行的操作序列
  2. 数据存储接口:设计与底层存储引擎的交互方式
  3. 执行器实现:执行各种SQL操作的具体逻辑
  4. 结果集处理:处理查询结果并返回给用户

这些组件将构成一个完整的查询执行系统,使我们能够实际运行SQL查询并获取结果。


SQL解析器是数据库系统的关键组件,它将用户输入的SQL语句转换为系统可理解的结构。通过实现ALTER TABLE语句,我们完成了SQL解析器的所有核心功能,为后续开发查询执行引擎奠定了坚实基础。

在下一阶段,我们将开始构建查询执行引擎,使我们的系统能够真正执行SQL操作并返回结果。敬请期待!

相关推荐
红尘散仙3 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记5 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
_codemonster5 小时前
30分钟快速搭建 Spring Cloud Alibaba 微服务实战(一)
微服务·架构·毕业设计·课程设计
会编程的土豆5 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
Cosolar5 小时前
从零写一个 Attention Is All You Need
人工智能·面试·架构
喵个咪5 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6166 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364576 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao6 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
qcx237 小时前
【系统学AI】09 Multi-Agent架构(2026版):从学术理论到工业级实践
java·人工智能·架构·multi-agent·claude agent