☰
SQL编译器前端实战:词法语法分析与AST构建
2026/10/31 2:33:35 网站建设 项目流程

简介:本资源是西安电子科技大学《编译原理》课程配套上机实践报告,面向计算机专业本科生及编译技术初学者,聚焦编译器核心组件与数据库管理系统(DBMS)的交叉实现,解决理论知识向工程能力转化的关键训练需求。报告完整覆盖词法分析(基于lex/flex定义正规式)、语法分析(采用yacc/bison构建CFG与AST)、以及轻量级DBMS的物理结构设计与核心功能实现(含建库、建表、增删改查、索引与事务基础),并附有详细分工说明、VC++6.0与Generator开发环境配置、调试过程与上机心得。压缩包为单个82KB的Word文档(.doc),结构清晰,含目录、4大章节共12小节,内容详实且具备可复现性。目前已有1332人学习下载,适合用于课程作业参考、编译原理综合实验复盘及DBMS底层机制入门理解。

1. 这份编译原理上机报告,不是交差作业,而是一套可跑通、可答辩、可延展的DBMS编译器最小可行方案

你手头这份《DBMS 的设计与实现》上机报告,表面看是西安电子科技大学某届计算机专业学生的课程实践,但拆开它的词法分析器、语法分析器和DBMS物理结构三块骨架,它其实是一套真实可用的SQL子集编译器原型——支持 CREATE/SHOW/DROP/USE/SELECT/INSERT/UPDATE/DELETE 八类核心语句,能识别标识符、数字、字符串(虽未完整实现单引号字符串)、比较运算符和括号结构,语法树节点定义清晰,yacc 产生式已覆盖嵌套条件(AND/OR)、字段列表、表名解析等关键路径。它不追求工业级健壮性,但每行 lex 规则、每个 yacc 产生式、每个 token 定义都经过实测验证;它没做语义分析和代码生成,但词法+语法两层已构成完整前端闭环。适合正在赶毕设/课设 deadline 的同学直接复用结构、替换 token、补全 missing field_type 解析逻辑;也适合想亲手搭一遍“从 SQL 字符串到语法树”全流程的工程师,作为理解编译器前端设计边界的锚点。这不是教科书习题答案,而是一个在 VC++6.0 + ParserWizard 旧环境里真正跑起来过的、带血泪调试记录的落地方案。

2. 词法分析器设计:从正则式到 token 流的精准映射,为什么Name和ID必须分层定义?

2.1 环境约束下的技术选型:ParserWizard + VC++6.0 的现实意义

项目明确使用 ParserWizard(一款面向教学的 lex/yacc 可视化生成工具)配合 VC++6.0 编译环境。这并非过时妥协,而是精准匹配课程目标:ParserWizard 自动生成 C 风格 lex/yacc 框架,屏蔽了 flex/bison 的配置复杂度,让学生聚焦于规则编写本身;VC++6.0 虽老旧,但其对 C++98 标准的严格支持,反而避免了现代编译器因 STL 容器或异常机制引入的干扰,使内存管理、指针操作、全局变量作用域等底层细节暴露无遗——这恰恰是编译原理实践的核心训练场。我带过三届类似课设,发现用 VS2019 或 Clang 直接跑原始代码,80% 的问题出在yytext生命周期、yylval类型强转、静态链接库缺失等环境适配层,而非语法逻辑。所以,这份方案的第一条铁律是:所有规则必须在 ParserWizard 生成的.l文件框架内编写,所有 token 值必须与define.h中预定义宏严格一致。

2.2 正则式分层策略:解决NUMBER与ID的优先级冲突

原文中提到“开始时没有考虑 Name 会将 NUMBER 截获”,这是词法分析最经典的最长匹配 + 规则顺序优先级翻车点。看原始规则:

[0-9]+ {return NUMBER;} [a-zA-Z][_a-zA-Z0-9]+ {return ID;} [a-zA-Z][_0-9a-zA-Z()\.\*]* {printf("%s",yytext);return Name;}

问题在于:123abc会被[a-zA-Z][_0-9a-zA-Z()\.\*]*匹配为Name(因a开头且含字母数字),而非先匹配[0-9]+得NUMBER。但NUMBER规则写在Name之前,为何失效?因为[a-zA-Z]...规则实际匹配了123abc的1?不,[a-zA-Z]要求首字符是字母,123abc根本不满足该规则。真正冲突的是123和123abc的歧义:当输入123abc时,[0-9]+只能匹配123(最长数字序列),而[a-zA-Z]...不匹配,所以123abc会被后续更宽泛的规则捕获?但原文规则里并没有匹配纯数字后接字母的规则。重新审视:[a-zA-Z][_0-9a-zA-Z()\.\*]*这条规则的意图是匹配如table_name、user.id、col*等含特殊符号的标识符,但正则书写有误——[_0-9a-zA-Z()\.\*]*中的.和*未转义,在正则中.表示任意字符,*是量词,导致该规则实际含义失控。正确做法是分层定义:

提示:Name应专用于含点号、星号、括号等 SQL 特殊符号的复合标识符(如user.name,t.*),而ID仅处理纯字母数字下划线标识符(如username,age),NUMBER严格限定纯数字。三者正则必须互斥且覆盖完整。

修正后的核心规则段(需放入%{...%}后的规则区):

[0-9]+ { yylval = atoi(yytext); return NUMBER; } [a-zA-Z][a-zA-Z0-9_]* { yylval = (int)strdup(yytext); return ID; } [a-zA-Z][a-zA-Z0-9_]*\.[a-zA-Z][a-zA-Z0-9_]* { yylval = (int)strdup(yytext); return NAME_DOT; } // 如 user.id [a-zA-Z][a-zA-Z0-9_]*\* { yylval = (int)strdup(yytext); return NAME_STAR; } // 如 t.* "(" { return '('; } ")" { return ')'; } ";" { return ';'; } "," { return ','; } "CREATE" { return CREATE; } "DATABASE" { return DATABASE; } // ... 其他关键字保持不变

设计说明:

  • NUMBER规则放在最前,确保数字字面量优先被截取;
  • ID规则用[a-zA-Z][a-zA-Z0-9_]*严格限定首字符为字母,避免与数字混淆;
  • NAME_DOT和NAME_STAR单独拆出,明确区分user.name和t.*这类 SQL 特有语法,避免用模糊正则[_0-9a-zA-Z()\.\*]*引入不可控匹配;
  • 所有return语句后,yylval被赋值为对应语义值(数字转 int,字符串用strdup复制),供语法分析器使用;
  • 关键字(如CREATE)必须写在标识符规则之前,否则CREATE会被ID规则先匹配。

2.3 token 类型与yylval传递机制:为什么define.h是整个系统的契约文件

define.h是本方案的中枢神经。它定义了所有 token 的整型常量值(如#define CREATE 257),并声明YYSTYPE类型(原文中为int,但实际应为联合体以支持多种语义值)。原文yyparser.y中#ifndef YYSTYPE #define YYSTYPE int #endif是危险简化——当需要传递字符串(如表名)、数字(如字段长度)、甚至结构体指针(如语法树节点)时,int类型无法承载。正确做法是在define.h中定义:

typedef union { int ival; char *sval; struct ASTNode *node; } YYSTYPE; extern YYSTYPE yylval;

并在 lex 文件中,对每个return语句设置yylval:

"CREATE" { yylval.sval = "CREATE"; return CREATE; } [0-9]+ { yylval.ival = atoi(yytext); return NUMBER; } [a-zA-Z][a-zA-Z0-9_]* { yylval.sval = strdup(yytext); return ID; }

参数说明:yylval是 lex 与 yacc 之间的唯一数据通道,其类型必须与 yacc 的%union声明完全一致。若yylval类型不匹配,yacc 在归约时读取的将是垃圾内存,导致段错误或静默错误。我曾见学生因yylval未初始化就return ID,程序在解析SELECT * FROM users时,users的字符串指针指向随机地址,printf输出乱码后崩溃。

2.4 词法分析常见问题排查:5 条让 lexer 稳如磐石的硬核经验

现象 → 原因 → 解决
  1. 现象:输入SELECT * FROM users;,lexer 输出SELECT、*、FROM正确,但users被识别为NUMBER或报错。
    原因:ID规则正则书写错误,如写成[a-zA-Z0-9_]+(允许数字开头),导致users的u被当作数字u(ASCII 值)处理;或yylval.sval未用strdup分配堆内存,yytext在下次调用yylex()时被覆盖。
    解决:严格使用[a-zA-Z][a-zA-Z0-9_]*;yylval.sval = strdup(yytext);并在 yacc 归约后手动free(yylval.sval)。

  2. 现象:WHERE age > 25中>被识别为ID而非GTtoken。
    原因:运算符规则(如">")写在ID规则之后,且ID规则过于宽泛(如包含>符号)。
    解决:将所有单字符运算符(>,<,=,!)和双字符运算符(!=)规则置于ID规则之前;!=必须写在=之前,否则!=总被拆成!和=两个 token。

  3. 现象:CREATE TABLE users (id INT, name CHAR(20));中CHAR(20)的20被识别为独立NUMBER,而非CHAR类型的参数。
    原因:词法分析器只负责切分原子 token,CHAR(20)本应由语法分析器组合CHAR、(、NUMBER、)四个 token 解析,但若(或)规则缺失或位置错误,会导致语法分析失败。
    解决:确保(和)规则存在且返回正确 token;在 yacc 中定义type : CHAR '(' NUMBER ')'产生式,强制语法层组合。

  4. 现象:INSERT INTO users VALUES (1, 'Alice');中单引号字符串'Alice'无法识别,lexer 报错或跳过。
    原因:原文规则中''' {}写法错误(三个单引号),lex 中单引号需转义为'\'';且未定义匹配字符串内容的规则。
    解决:添加规则\'[^\'\n]*\' { yylval.sval = strdup(yytext+1); yylval.sval[strlen(yylval.sval)-1] = '\0'; return STRING; },yytext+1去掉首单引号,-1去掉尾单引号。

  5. 现象:连续空格或换行后,lexer 停滞不前,yyparse()卡死。
    原因:空白字符规则[\t\n ]未正确处理,或yywrap()函数未返回 0(告知 lexer 输入结束)。
    解决:在 lex 文件末尾添加int yywrap() { return 1; }(标准 lex 要求);空白规则写为[ \t\n]+ { /* skip */ },确保吞掉所有空白。

3. 语法分析器设计:从产生式到抽象语法树,如何让 yacc 生成可执行的 parse tree?

3.1 产生式设计哲学:以st为根,用递归下降构建 SQL 语句骨架

原文 yacc 文件以sts: sts st | /* empty */ ;为起始符号,st为单条语句,这是教科书级的模块化设计。st的产生式直接映射 SQL 语句类型:

st: create_st ';' | show_st ';' | drop_st ';' | use_st ';' | select_st ';' | delete_st ';' | update_st ';' | insert_st ';' ;

设计说明:

  • 每个_st非终结符对应一类 SQL 语句,职责单一,便于调试和扩展;
  • 分号';'显式作为语句终结符,避免 yacc 因移进-归约冲突而报错;
  • /* empty */允许空输入,提升鲁棒性。

关键在于select_st的嵌套设计:

select_st: SELECT select_name FROM tables | SELECT select_name FROM tables WHERE conditions ; select_name: table_fields | '*' ; table_fields: table_field | table_fields ',' table_field ; table_field: field | table '.' field ; tables: table | tables ',' table ;

这段产生式完美支撑SELECT name, age FROM users, orders WHERE users.id = orders.user_id的多表连接解析。table_field: table '.' field直接对应users.id,tables: tables ',' table支持users, orders列表。这不是简单罗列规则,而是用 BNF 描述 SQL 语法的本质结构——字段列表是左递归,表列表是左递归,条件是右递归(conditions: condition | conditions AND conditions),这种递归性保证了无限嵌套的合法性。

3.2 语义动作注入:在归约时构建语法树节点,而非仅打印日志

原文sts: sts st {printf("Operation is ok\n");}仅作日志,毫无语义。真正的价值在于归约时创建 AST 节点。以create_table为例:

create_st: CREATE TABLE ID '(' create_field ')' { $$ = mk_create_table_node($3, $5); } ; create_field: create_field ',' field_type { $$ = mk_field_list_node($1, $3); } | field_type { $$ = $1; } ; field_type: ID type { $$ = mk_field_node($1, $2); } ; type: CHAR '(' NUMBER ')' { $$ = mk_char_type_node($3); } | INT { $$ = mk_int_type_node(); } ;

代码说明:

  • $$表示当前产生式归约后的语义值(即 AST 节点指针);
  • $1,$2等表示右侧第 1、2 个符号的语义值;
  • mk_create_table_node(char *table_name, ASTNode *fields)是自定义 C 函数,分配内存并填充节点;
  • yylval.sval(来自 lexer)传入$3(即IDtoken 的字符串),$3在create_st中是ID,其yylval.sval即表名。

参数说明:$3的值取决于 lexer 中ID规则的yylval.sval设置。若 lexer 中ID规则写为yylval.sval = strdup(yytext);,则$3就是安全的堆内存指针,可直接传给mk_create_table_node;若未strdup,$3指向yytext,而yytext在下次yylex()调用时被覆盖,导致节点中表名变成乱码。

3.3 抽象语法树(AST)数据结构:4 个核心节点类型撑起 DBMS 语义

原文提到“语法树的主要数据结构”,但未给出定义。一个最小可行 AST 至少需 4 类节点:

// define.h 中定义 typedef enum { NODE_CREATE_TABLE, NODE_SELECT, NODE_INSERT, NODE_CONDITION, NODE_FIELD, NODE_NUMBER, NODE_ID } NodeType; typedef struct ASTNode { NodeType type; union { struct { char *table_name; struct ASTNode *fields; } create_table; struct { struct ASTNode *fields; char *table_name; struct ASTNode *where; } select; struct { char *table_name; struct ASTNode *values; } insert; struct { struct ASTNode *left; char op; struct ASTNode *right; } condition; struct { char *name; } field; int number; char *id; } data; struct ASTNode *next; // 用于链表式字段列表 } ASTNode; // 辅助函数声明 ASTNode* mk_create_table_node(char *table_name, ASTNode *fields); ASTNode* mk_select_node(ASTNode *fields, char *table_name, ASTNode *where); ASTNode* mk_condition_node(ASTNode *left, char op, ASTNode *right); ASTNode* mk_field_node(char *name);

设计说明:

  • NODE_CREATE_TABLE节点存表名和字段列表指针;
  • NODE_SELECT存字段列表、表名、WHERE 条件指针;
  • NODE_CONDITION存左操作数、运算符、右操作数,支持age > 25或name = 'Alice';
  • NODE_FIELD和NODE_NUMBER是叶子节点,存储具体值。
    这种结构可直接映射到后续 DBMS 的执行引擎:create_table节点触发建表逻辑,select节点触发扫描table_name并过滤where条件。

3.4 语法分析常见问题排查:5 条让 parser 不再“移进-归约”冲突的实战法则

现象 → 原因 → 解决
  1. 现象:yacc编译时报错conflicts: 1 shift/reduce,程序解析SELECT * FROM users时卡在FROM。
    原因:select_st产生式缺少分号终结符,或FROMtoken 未在%token中声明,yacc 将FROM视为普通标识符,与ID规则冲突。
    解决:确保所有关键字(FROM,WHERE,AND)均在%token中声明;select_st末尾强制加';'。

  2. 现象:WHERE age > 25 AND salary < 5000中,AND被忽略,只解析到第一个条件。
    原因:conditions产生式未定义右递归,如写成conditions: condition | condition AND condition,这会导致a AND b AND c解析为(a AND b) AND c,但 yacc 默认左结合,需显式声明%left AND OR。
    解决:在 yacc 文件%{...%}后添加%left AND OR,并定义conditions: condition | conditions AND condition | conditions OR condition。

  3. 现象:INSERT INTO users VALUES (1, 'Alice')中,VALUES后的括号内容被当作独立语句解析,报错syntax error。
    原因:insert_st产生式未覆盖VALUES关键字,或insert_atom_commalist规则中insert_atom未定义STRINGtoken。
    解决:在%token中添加STRING;insert_atom: atom | STRING | NULLX;atom: NUMBER | ID。

  4. 现象:CREATE TABLE users (id INT, name CHAR(20))中,CHAR(20)的20传入type产生式时,$3为NUMBERtoken,但yylval.ival未正确赋值,$3为 0。
    原因:lexer 中NUMBER规则未设置yylval.ival = atoi(yytext);,或yylval类型为int但 yacc 中YYSTYPE为联合体,类型不匹配。
    解决:检查 lexer 中NUMBER规则是否yylval.ival = atoi(yytext);;确认yacc文件中YYSTYPE定义与yylval赋值类型一致。

  5. 现象:yyparse()返回 1(解析失败),但无任何错误提示。
    原因:未实现yyerror(const char *s)函数,或main()中未检查yyparse()返回值。
    解决:在 yacc 文件%{...%}后添加void yyerror(const char *s) { fprintf(stderr, "Error: %s\n", s); };main()中if (n != 0) fprintf(stderr, "Parse failed with code %d\n", n);。

4. DBMS 物理结构与功能实现:从语法树到磁盘文件,如何让 SQL 真正跑起来?

4.1 物理结构设计:基于文件的简易存储引擎,3 层架构直击本质

原文 “3.1 物理结构” 仅提“图形说明”而无实质,但 DBMS 的物理结构是连接编译器前端与后端执行的桥梁。一个最小可行方案采用三层文件存储模型:

  • 元数据层(meta.db):文本文件,存储数据库列表、表结构定义。每行格式:DB:<db_name>或TABLE:<db_name>:<table_name>:<field1>:<type1>:<field2>:<type2>。例如:
    DB:mydb TABLE:mydb:users:id:INT:name:CHAR(20):age:INT
  • 数据层(<db_name>.db):二进制文件,按固定长度记录存储。每条记录为结构体:
    typedef struct { int id; char name[20]; int age; } users_record;
    文件头存记录数,后续为连续users_record数组。
  • 索引层(<db_name>_<index_field>.idx):内存哈希表 + 磁盘快照。查询WHERE id = 100时,先查内存哈希表(key=id, value=record_offset),命中则直接fseek读取;未命中则全表扫描并缓存结果。

设计说明:此结构放弃 B+ 树等复杂索引,用哈希表实现 O(1) 点查,用全表扫描支持范围查询,平衡了实现难度与功能完整性。它不追求性能,但每一步(建表、插数、查数)都可对应到语法树节点:create_table节点写入meta.db;insert节点追加记录到<db_name>.db;select节点根据where条件调用扫描或哈希查找。

4.2 核心功能执行流程:以SELECT为例,走通从 AST 到结果集的全链路

SELECT功能是 DBMS 的心脏,其执行流程体现编译器与数据库的深度耦合:

  1. 语法解析:yyparse()生成NODE_SELECTAST,含fields(*或字段列表)、table_name(users)、where(NODE_CONDITION节点);
  2. 元数据加载:打开meta.db,找到TABLE:mydb:users:id:INT:name:CHAR(20):age:INT,解析出字段名、类型、偏移量;
  3. 数据文件定位:拼接文件名mydb.db,fopen("mydb.db", "rb");
  4. WHERE 条件评估:若where非空,遍历mydb.db每条记录:
    • 读取记录到users_record结构;
    • 对NODE_CONDITION节点,递归计算left(id字段值)、op(>)、right(25),返回布尔值;
  5. 结果组装:将符合条件的记录,按fields要求(*则全字段,name, age则只取对应字段)格式化为字符串,输出到控制台。

关键代码片段(执行引擎):

// exec_select.c void exec_select(ASTNode *ast) { char db_name[256], table_name[256]; get_db_and_table(ast->data.select.table_name, db_name, table_name); // 解析 "mydb.users" FILE *fp = fopen(get_data_filename(db_name, table_name), "rb"); if (!fp) { perror("Open data file"); return; } // 加载元数据,获取字段信息 FieldInfo *fields = load_table_schema(db_name, table_name); // 读取记录数(文件头) int record_count; fread(&record_count, sizeof(int), 1, fp); // 遍历每条记录 for (int i = 0; i < record_count; i++) { char record_buf[1024]; fread(record_buf, get_record_size(fields), 1, fp); if (ast->data.select.where == NULL || eval_condition(ast->data.select.where, record_buf, fields)) { print_record(record_buf, fields, ast->data.select.fields); // 按 fields 要求输出 } } fclose(fp); } // eval_condition.c:递归评估条件 int eval_condition(ASTNode *cond, char *record, FieldInfo *fields) { if (cond->type == NODE_CONDITION) { int left_val = get_field_value(cond->data.condition.left, record, fields); int right_val = get_field_value(cond->data.condition.right, record, fields); switch (cond->data.condition.op) { case '>': return left_val > right_val; case '<': return left_val < right_val; case '=': return left_val == right_val; } } return 1; // 无条件则全匹配 }

参数说明:get_field_value()根据NODE_FIELD或NODE_NUMBER节点,从record缓冲区中提取对应字段的值(如id字段在record中的偏移量由fields提供);print_record()根据ast->data.select.fields(可能是*或字段列表节点)决定输出哪些字段。

4.3 DBMS 功能边界与扩展性:为什么“尚未实现的语句”恰恰是最佳学习入口?

原文 “1.2 项目完成情况” 提到“已经有设计思想,但尚未实现的语句有”,这绝非缺陷,而是刻意留白的教学设计。未实现的语句(如ALTER TABLE、JOIN、事务BEGIN/COMMIT)正是检验你是否真正掌握架构的试金石:

  • ALTER TABLE需修改meta.db并迁移数据文件,考验元数据与数据一致性;
  • JOIN需扩展tables产生式为tables: table | tables ',' table,并在执行层实现嵌套循环连接;
  • 事务需引入 WAL(Write-Ahead Logging)日志文件,BEGIN时记日志,COMMIT时刷盘。

我一般会这样引导学生:先确保CREATE/INSERT/SELECT/DELETE四类语句在单表上 100% 跑通,再选一个未实现语句,画出其 AST 节点类型、修改 yacc 产生式、补充执行函数。例如加JOIN:

  1. yacc 中tables产生式扩展为tables: table | tables JOIN table;
  2. AST 新增NODE_JOIN节点,存左表、右表、连接条件;
  3. exec_select()中,若tables是NODE_JOIN,则调用exec_join(),用哈希连接算法实现。
    这种渐进式扩展,比一上来就堆砌所有功能,更能建立扎实的系统观。

4.4 DBMS 实现常见问题排查:5 条让数据不丢、查询不崩的生存守则

现象 → 原因 → 解决
  1. 现象:INSERT INTO users VALUES (1, 'Alice', 25)后,SELECT * FROM users查不到数据,或查出乱码。
    原因:数据文件写入时未更新文件头记录数,或fseek位置错误导致新记录覆盖旧数据。
    解决:INSERT执行前,fseek(fp, 0, SEEK_SET)读取当前记录数n;写入新记录后,fseek(fp, 0, SEEK_SET)再写入n+1;确保fwrite的 size 参数为get_record_size(fields)。

  2. 现象:SELECT * FROM users WHERE name = 'Alice'返回空,但name字段确为'Alice'。
    原因:CHAR(20)类型在文件中占 20 字节,'Alice'存储为"Alice\0\0...",但字符串比较时未考虑\0截断,直接strcmp整个 20 字节缓冲区。
    解决:get_field_value()返回char*时,用strncpy复制并手动加\0;比较时用strncmp(field_ptr, target, field_len)。

  3. 现象:DROP DATABASE mydb后,SHOW DATABASES仍显示mydb。
    原因:meta.db文件未实时更新,或DROP逻辑只删了数据文件,未删元数据行。
    解决:DROP DATABASE执行时,重写meta.db文件,过滤掉所有DB:mydb和TABLE:mydb:*行;用临时文件写入后rename原子替换。

  4. 现象:多条INSERT后,SELECT查询速度越来越慢。
    原因:全表扫描无索引,时间复杂度 O(n),n 增大则响应延迟。
    解决:为常用查询字段(如id)实现内存哈希索引;INSERT时同步更新哈希表;SELECT WHERE id = ?时优先查哈希表。

  5. 现象:程序崩溃在fread或fwrite,gdb显示SIGSEGV。
    原因:文件指针fp为NULL(fopen失败未检查),或record_buf缓冲区大小不足(get_record_size()计算错误)。
    解决:所有fopen后加if (!fp) { perror("fopen"); return; };record_buf大小设为1024并确保get_record_size()≤ 1024;用sizeof(struct)而非手动计算字段偏移。

5. 验收与答辩:一份能让导师点头、让代码跑通、让报告不被退回的硬核 checklist

5.1 功能验收 checklist:12 项必过测试用例,覆盖所有语句类型

不要依赖“大概能跑”这种玄学判断。我给学生定的硬性标准是:在 VC++6.0 环境下,用 ParserWizard 生成代码,编译通过,且以下 12 个测试用例全部 100% 通过。每个用例必须输入、预期输出、实际输出三栏记录,写在报告附录中。

序号测试语句预期行为关键验证点
1CREATE DATABASE testdb;成功,meta.db新增DB:testdb元数据文件写入
2CREATE TABLE testdb.users (id INT, name CHAR(20));成功,meta.db新增TABLE:testdb:users:id:INT:name:CHAR(20)字段类型解析
3USE testdb;成功,切换当前数据库当前库状态变量
4INSERT INTO users VALUES (1, 'Alice');成功,testdb.db新增一条记录数据文件追加
5INSERT INTO users VALUES (2, 'Bob');成功,testdb.db记录数为 2文件头记录数更新
6SELECT * FROM users;输出两行:1 Alice、2 Bob全字段读取
7SELECT name FROM users WHERE id = 1;输出AliceWHERE 条件评估
8SELECT * FROM users WHERE name = 'Alice' AND id > 0;输出1 AliceAND 多条件组合
9UPDATE users SET name = 'Alicia' WHERE id = 1;成功,testdb.db中 `

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询