代码传送门:
2.1.4 参考代码(解析)
2.2.3 参考代码(执行)
解析 sql 语句需要了解一些分词器和解析器
分词器(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"
};
解析器作用主要是获取 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
以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 fields; //set fields 被更新的字段列表,保存上面update语句的sage
vector 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
中的数据(调试用)
#include
#include
/**
* 在现有实现基础上,实现update from子句
*
* 支持的update语法:
*
* UPDATE SET = [, , ..]
* WHERE
*
* 解析获得 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 fields; //set fields 被更新的字段列表,保存上面update语句的sage
vector 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;
};
首先创建一个表扫描计划,然后用 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 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);
};
函数的具体实现在 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
得到表达式的类型。//
// Created by sam on 2018/9/18.
//
#include
#include
#include
#include
#include
/*执行 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; ifields.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;
};
指导文档由山东科技大学各位老师完成。