update 语句解析与执行

mac2026-04-17  0

代码传送门:

2.1.4 参考代码(解析)

2.2.3 参考代码(执行)


2.1 update 语句的解析

解析 sql 语句需要了解一些分词器和解析器

2.1.1 分词器(tokenizer)

分词器(tokenizer)的主要作用就是将 sql 语句拆分为 token(词),这样我们就可以逐词进行解析。分词器不仅会将 sql 语句拆分成词,还会判断词的属性( TokenType ): 词的全部属性( TokenType )定义在 /include/parser/tokenizer.h 中

typedef enum TokenType_ { TOKEN_OPEN_PAREN = 0, //左括号 TOKEN_CLOSE_PAREN, //右括号 TOKEN_POWER, // TOKEN_PLUS, //加号 TOKEN_MINUS, //减号 TOKEN_DIVIDE, //除 TOKEN_MULTIPLY, //乘 TOKEN_LT, //less-than operator 小于 TOKEN_GT, //大于 TOKEN_EQ, //等于 TOKEN_NOT_EQUAL, //不等于 TOKEN_LE, //小于等于 less-than-or-equal-to operator" TOKEN_GE, //大于等于 TOKEN_IN, //in运算 TOKEN_LIKE, //like运算 TOKEN_AND, TOKEN_OR, TOKEN_NOT, //匹配not TOKEN_ASSIGNMENT, //赋值 TOKEN_FUN, //函数 TOKEN_COMMA, /*以上是操作符,在表达式解析时使用*/ //逗号 TOKEN_INVALID, //"invalid token" TOKEN_RESERVED_WORD, //sql中的保留字,比如create,set,where 等等 TOKEN_WORD, //非保留字的词 TOKEN_UNENDED_SRING, //没有结尾的字符串 TOKEN_STRING, //字符串 TOKEN_MOD, //模 TOKEN_INCOMPLETE_CHAR,//不完整的字符 TOKEN_CHAR, //字符 TOKEN_INVALID_CHAR, //非法字符 TOKEN_SEMICOLON, //分号 TOKEN_EXP_FLOAT, TOKEN_FLOAT, TOKEN_OCTAL, //"octal integer" TOKEN_HEX, //"hexadecimal integer" TOKEN_DECIMAL, //"decimal integer" TOKEN_ZERO, //"zero integer" TOKEN_NULL //匹配null } TokenType; //sql中的保留字 const char *reservedWords[] = { "select", "from", "where", "order", "by", "group", "create", "table", "index", "and", "not", "or", "null", "like", "in", "grant", "integer", "int", "char", "values", "insert", "into", "update", "delete", "set", "on", "user", "view", "rule", "default", "check", "between", "trigger", "primary", "key", "foreign" };

2.1.2 解析器(parser)

解析器作用主要是获取 token(词),然后对 token(词)进行解析,主要的函数在 src/parser/parser.cpp 中实现:

int Parser::matchToken( TokenType type, char *text)//常用函数 对解析器中 token(词)的TokenType和text进行检查,如果匹配成功,就直接获取下一个词放入解析器中,否则输出错误信息,返回 NULL,不对解析器中的词修改

Token *Parser::parseNextToken() 首先判断当前解析器中的词是否为 NULL,如果是,就获取下一个 token;否则就返回当前词

Token *Parser::parseEatToken() 在解析完当前词之后,将当前解析器中的 token(词)置为 NULL

Token *Parser::parseEatAndNextToken() 直接获取下一个词,不对当前解析器中的词进行判断

void *Parser::parseError( char *message) 将解析状态修改为解析失败( PARSER_WRONG ),返回 NULL

2.1.3 解析 update 语句(parse_sql_stmt_update)

以update student set sage=20[,fieldname=expr] where sage=22为例,其中 update set where 均为保留字类型

class sql_stmt_update : public SQLStatement { public: char *tableName; //保存上面update语句的student vector<char*> fields; //set fields 被更新的字段列表,保存上面update语句的sage vector<Expression*> fieldsExpr; //set fields expression 新值(或表达式)列表,保存上面update语句的20 SRA_t *where; //保存上面update语句的 sage=22 } ;

其中, where 是一个 SRA_t (即 SRA_s ) 结构体的指针,该结构体定义在 include/relationalalgebra/sra.h 中,这个文件存放 SRA(sugar relation algebra)的定义和操作,这是一种关系代数的表示方法。

struct SRA_s { enum SRAType t; // Type of SRA_s union { SRA_Table_t table; SRA_Select_t select; // others... }; }; // Constructors SRA_t *SRATable(TableReference_t *ref); SRA_t *SRASelect(SRA_t *sra, Expression *cond); // others...

where 语句中,会用到两种 SRA_t : SRA_TABLE 和 SRA_SELECT ,其构造函数已在上面列出。

简要流程:

首先匹配 update :使用 matchToken(TOKEN_RESERVED_WORD, "update") 匹配获取表名 tableName :使用 if 判断是否是 TOKEN_WORD 类型,如果是,获得表名,并将表名包裹在 SRA_t 中( SRA_TABLE 类型)作为 sql_stmt_update->where 的值;给字符串指针开空间的时候可以使用 new_id_name() 函数匹配 set :使用 matchToken(TOKEN_RESERVED_WORD,"set")循环获取更新的表达式:获取字段名( TOKEN_WORD 类型),存入 fields 中;识别并跳过 = ( TOKEN_EQ 类型);使用 parseExpressionRD() 函数获得新值(或表达式),存入 fieldsExpr 中即可获得一个完整的表达式;请注意设置循环停止条件匹配 where : matchToken(TOKEN_RESERVED_WORD,"where") ,注意不是所有 update 语句都伴随一个 where 表达式使用 parseExpressionRD() 函数获取值(或者表达式),并包裹在 SRA_t 中( SRA_SELECT 类型),作为 sql_stmt_update->where 的值。(语句若不伴随 where 则仅需构造 SRA_TABLE ,具体实现可参考 exp_01_stmt_parser/exp_01_03_select.cpp )创建 sql_stmt_update 类型的指针,分配内存空间并对各字段进行赋值,返回该指针

常用函数:

push_back(elem); //向 vector 尾部加入数据Expression *Parser::parseExpressionRD(); //递归下降法解析表达式 详情看 src/parser/parser.cpp 中SRA_t *SRATable(TableReference_t *ref); // SRA_TABLE 构造函数SRA_t *SRASelect(SRA_t *sra, Expression *cond); // SRA_SELECT 构造函数Expression *expression_print(Expression *expr, char *desc); //输出 Expression 对象的数据(调试用)void SRA_print(SRA_t *sra); //输出 SRA_t 中的数据(调试用) 

2.1.4 参考代码: 

#include <dongmensql/sqlstatement.h> #include <parser/StatementParser.h> /** * 在现有实现基础上,实现update from子句 * * 支持的update语法: * * UPDATE <table_name> SET <field1> = <expr1>[, <field2 = <expr2>, ..] * WHERE <logical_expr> * * 解析获得 sql_stmt_update 结构 */ // update student set sname = "tom" where sno = "001" /*TODO: parse_sql_stmt_update, update语句解析*/ sql_stmt_update *UpdateParser::parse_sql_stmt_update() { // fprintf(stderr, "TODO: update is not implemented yet. in parse_sql_stmt_update \n"); char *tableName; //保存表名 vector<char*> fields; //set fields 被更新的字段列表,保存上面update语句的sage vector<Expression*> fieldsExpr; //set fields expression 新值(或表达式)列表,保存上面update语句的20 SRA_t *where; //保存上面update语句的 sage=22 // "uptade" 关键字 Token *token = this->parseNextToken(); if (!this->matchToken( TOKEN_RESERVED_WORD, "update")) { return NULL; } // 表名 token = this->parseNextToken(); if (token->type == TOKEN_WORD) { tableName = new_id_name(); strcpy(tableName, token->text); } else { strcpy(this->parserMessage, "invalid sql: missing table name."); return NULL; } // "set" 关键字 token = this->parseEatAndNextToken(); if(!this->matchToken(TOKEN_RESERVED_WORD, "set")){ strcpy(this->parserMessage, "语法错误."); return NULL; } // set内容 token = this->parseNextToken(); if (token != nullptr && (token->type == TOKEN_WORD || token->type == TOKEN_COMMA)) { while(token != nullptr && (token->type == TOKEN_WORD || token->type == TOKEN_COMMA)) { if(token->type == TOKEN_COMMA) { token = parseEatAndNextToken(); } if (token->type == TOKEN_WORD) { char *fieldName = new_id_name(); strcpy(fieldName, token->text); fields.push_back(fieldName); } else { strcpy(this->parserMessage, "error: file name. \n"); return NULL; } this->parseEatAndNextToken(); if (matchToken(TOKEN_EQ, "=") == 0) { strcpy(this->parserMessage, "error: '=' \n"); return NULL; } // 匹配值 Expression *exp = parseExpressionRD(); fieldsExpr.push_back(exp); token = parseNextToken(); } } else { strcpy(this->parserMessage, "invalid sql: missing field name."); return NULL; } // where 语句 where = SRATable(TableReference_make(tableName, nullptr)); if(this->matchToken(TOKEN_RESERVED_WORD, "where")){ /*解析where子句中的条件表达式*/ Expression *whereExpr = this->parseExpressionRD(); if (this->parserStateType == PARSER_WRONG) { return NULL; } where = SRASelect(where, whereExpr); } //进行赋值 sql_stmt_update *sqlStmtUpdate = (sql_stmt_update *)calloc(1, sizeof(sql_stmt_update)); sqlStmtUpdate->tableName = tableName; sqlStmtUpdate->fields = fields; sqlStmtUpdate->fieldsExpr = fieldsExpr; sqlStmtUpdate->where = where; return sqlStmtUpdate; };

 

2.2 update 语句执行

2.2.1 物理操作

首先创建一个表扫描计划,然后用 Scan 类中的 next() 函数迭代数据库数据条目,对满足条件的条目进行修改。

Scan 类中有针对不同的物理计划的虚拟函数:

class Scan { public: DongmenDB *m_db; Transaction *m_tx; virtual int beforeFirst()=0; virtual int next()=0; virtual int close()=0; virtual variant *getValueByIndex(int index)=0; virtual int getIntByIndex(int index)=0; virtual string getStringByIndex(int index)=0; virtual int getInt(string tableName, string fieldName) = 0; virtual variant* getValue(string fieldName)=0; virtual string getString(string tableName, string fieldName)=0; virtual int hasField(string tableName,string fieldName)=0; virtual FieldInfo * getField(string tableName, string fieldName)=0; virtual vector<char*> getFieldsName(string tableName)=0; virtual int setInt(string tableName, string fieldName, int value)=0; virtual int setString(string tableName, string fieldName, string value)=0; virtual int deleteRecord()=0; virtual int insertRecord()=0; virtual int getRID(RecordID *recordID)=0; virtual int moveTo(RecordID *recordID)=0; Expression * evaluateExpression(Expression *expr, Scan* scan, variant *var); };

2.2.2 实现

函数的具体实现在 src/physicalplan/ 下 Scan 的多个子类中

主要思路:

使用 SRA_t 类型的 where 构造执行计划( Scan 对象); (使用 generateScan(DongmenDB *db, SRA_t *sra, Transaction *tx) 函数构造, 在 src\physicalplan\ExecutionPlan.cpp 中实现)遍历计划在遍历计划过程的循环中,计算表达式的值,计算需要修改字段的偏移量 (使用 Expression * evaluateExpression(Expression *expr, Scan* scan, variant *var) 函数,在 src\physicalplan\Scan.cpp 中实现)使用 setInt(string tableName, string fieldName, int value) 或 setString(string tableName, string fieldName, string value) 修改;在循环中更新修改的记录条数,作为最终的返回值。

针对 Integer 和 String 类型的字段修改数据主要是用两个函数 setInt() 、 setString() ,你可以

通过 scan->getField(string tableName, string fieldName)->type 得到字段类型。执行 evaluateExpression(Expression *expr, Scan* scan, variant *var) 后,通过 var->type 得到表达式的类型。

2.2.3 参考代码 

// // Created by sam on 2018/9/18. // #include <physicalplan/ExecutionPlan.h> #include <physicalplan/TableScan.h> #include <physicalplan/Select.h> #include <physicalplan/Project.h> #include <physicalplan/Join.h> /*执行 update 语句的物理计划,返回修改的记录条数 * 返回大于等于0的值,表示修改的记录条数; * 返回小于0的值,表示修改过程中出现错误。 * */ /*TODO: plan_execute_update, update语句执行*/ int ExecutionPlan::executeUpdate(DongmenDB *db, sql_stmt_update *sqlStmtUpdate, Transaction *tx){ /*删除语句以select的物理操作为基础实现。 * 1. 使用 sql_stmt_update 的条件参数,调用 physical_scan_select_create 创建select的物理计划并初始化; * 2. 执行 select 的物理计划,完成update操作 * */ int count = 0; Scan* scan = generateScan(db, sqlStmtUpdate->where, tx); scan->beforeFirst(); char *tableName = sqlStmtUpdate->tableName; while(scan->next()) { for(size_t i=0; i<sqlStmtUpdate->fields.size(); i++) { char *currentFieldName = sqlStmtUpdate->fields[i]; variant *var = (variant*)calloc(1, sizeof(variant)); enum data_type fieldType =scan->getField(tableName, currentFieldName)->type; Expression * expression = scan->evaluateExpression(sqlStmtUpdate->fieldsExpr[i], scan, var); if(var->type != fieldType) { printf("error!\n"); return -1; } else if (var->type == DATA_TYPE_INT) { scan->setInt(tableName, currentFieldName, var->intValue); } else if (var->type == DATA_TYPE_CHAR) { scan->setString(tableName, currentFieldName, var->strValue); } else { printf("error!\n"); } } count++; } scan->close(); return count; };

 

 指导文档由山东科技大学各位老师完成。

最新回复(0)