2021SC@SDUSC SQLite源码分析(十三)————SQL 命令编译过程梳理
根据以往博客与小组讨论与资料查找,对命令的编译过程进行梳理,以对SQLite项目有个整体的理解。
一、 sqlite3_exec()函数
位于legacy.c文件中
int sqlite3_exec(
sqlite3 *db, /* 一个打开的数据库连接 */
const char *zSql, /* 要执行的 SQL语句 */
sqlite3_callback xCallback, /* 回叫函数 */
void *pArg, /* 传递给 xCallback()的第一个参数 */
char **pzErrMsg /* 将错误信息写到*pzErrMsg中 */
){
int rc = SQLITE_OK; /* 返回码 */
const char *zLeftover; /* 未处理的 SQL串尾部。zSql中可能包含多个 SQL
语句,一次处理一个,此变量为剩下的还未处理的
语句。 */
sqlite3_stmt *pStmt = 0; /* 当前 SQL语句(对象) */
char **azCols = 0; /* 结果字段(s)的名称 */
int nRetry = 0; /* 重试的次数 */
int callbackIsInit; /* 如果初始化了回叫函数,为 true */
if( zSql==0 ) zSql = "";
sqlite3Error(db, SQLITE_OK, 0); /* 清除 db中的错误信息 */
while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){
int nCol;
char **azVals = 0;
pStmt = 0;
rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover);/* 编译一条语句 */
if( rc!=SQLITE_OK ){
continue;
}
if( !pStmt ){
/* 遇到注释时会执行此分支 */
zSql = zLeftover;
continue;
}
callbackIsInit = 0;
nCol = sqlite3_column_count(pStmt); /* 取字段数 */
while( 1 ){
int i;
rc = sqlite3_step(pStmt); /* 执行语句 */
/* 如果有回叫函数并且需要,则调用回叫函数 */
if( xCallback && (SQLITE_ROW==rc ||
(SQLITE_DONE==rc && !callbackIsInit
&& db->flags&SQLITE_NullCallback)) ){
/* 1-如果回叫函数未初始化,则初始化之 */
if( !callbackIsInit ){
/* 此分支只执行一次 */
azCols = sqlite3DbMallocZero(db, 2*nCol*sizeof(const char*) + 1);
if( azCols==0 ){
goto exec_out;
}
for(i=0; i<nCol; i++){
/* 取各字段的名称 */
azCols[i] = (char *)sqlite3_column_name(pStmt, i);
}
callbackIsInit = 1;
}
/* 2-如果返回的是记录 */
if( rc==SQLITE_ROW ){
azVals = &azCols[nCol];
for(i=0; i<nCol; i++){
/* 取各字段的值 */
azVals[i] = (char *)sqlite3_column_text(pStmt, i);
if( !azVals[i] && sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
db->mallocFailed = 1;
goto exec_out;
}
}
}
/* 3-调用回叫函数对返回的记录进行处理 */
if( xCallback(pArg, nCol, azVals, azCols) ){
rc = SQLITE_ABORT;
sqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
sqlite3Error(db, SQLITE_ABORT, 0);
goto exec_out;
}
}
/*
如果返回的不是记录,有两种情况:一种是到达结果记录集的结尾,
第二种是执行 create table一类的不返回记录集的命令。
无论哪种情况,此处都需要“定案”。
*/
if( rc!=SQLITE_ROW ){
rc = sqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
if( rc!=SQLITE_SCHEMA ){
nRetry = 0;
zSql = zLeftover;
while( sqlite3Isspace(zSql[0]) ) zSql++;
}
break;
}
}
sqlite3DbFree(db, azCols);
azCols = 0;
}
exec_out:
if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt);
sqlite3DbFree(db, azCols);
rc = sqlite3ApiExit(db, rc);
/* 对出错信息进行处理 */
if( rc!=SQLITE_OK && ALWAYS(rc==sqlite3_errcode(db)) && pzErrMsg ){
int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db));
*pzErrMsg = sqlite3Malloc(nErrMsg);
if( *pzErrMsg ){
memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg);
}else{
rc = SQLITE_NOMEM;
sqlite3Error(db, SQLITE_NOMEM, 0);
}
}else if( pzErrMsg ){
*pzErrMsg = 0;
}
return rc;
}
sqlite3_exec()函数的实现最能体现 SQL 语句处理过程。
函数一次可以执行多条SQL命令。执行完成后返回一个SQLITE_ success/failure
代码,将错误信息写到*pzErrMsg 中。
如果 SQL 是查询,查询结果中的每一行都会调用 xCallback()函数。pArg 为传递给 xCallback()的第一个参数。如果 xCallback==NULL,即使对查询命令也没有回叫调用。
二、SQL 语句编译的调用层次
调用 sqlite3_prepare()函数时,编译一条 SQL 语句。编译过程的调用层次如下:
一 sqlite3_prepare()
(位于prepare.c )
sqlite3_prepare()函数中其实只包含一条对 sqlite3LockAndPrepare()的调用语句:
rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,ppStmt,pzTail);
其中第 4 个参数为 0,表示不将 SQL 文本复制到 ppStmt 中。
二 sqlite3LockAndPrepare()
也位于prepare.c
结合注释进行了分析
static int sqlite3LockAndPrepare(
sqlite3 *db, /* 数据库句柄 */
const char *zSql, /* UTF-8编码的 SQL语句 */
int nBytes, /* zSql的字节数 */
int saveSqlFlag, /* 如果为 True,将 SQL文本复制到 sqlite3_stmt中。 */
sqlite3_stmt **ppStmt, /* OUT: 指向语句句柄 */
const char **pzTail /* OUT: 未处理的 SQL串 */
){
int rc;
*ppStmt = 0;
if( !sqlite3SafetyCheckOk(db) ){ /* 确定 db指针的合法性。 */
return SQLITE_MISUSE;
}
/* 将 UTF-8编码的 SQL语句 zSql编译成。 */
rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, ppStmt, pzTail);
if( rc==SQLITE_SCHEMA ){ /* 如果遇到 SCHEMA改变,定案,再编译 */
sqlite3_finalize(*ppStmt);
rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, ppStmt, pzTail);
}
return rc;
}
三 sqlite3Prepare()
在 prepare.c 中
参数部分代码如下:
static int sqlite3Prepare(
sqlite3 *db, /* Database handle. */
const char *zSql, /* UTF-8 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
Vdbe *pReprepare, /* VM being reprepared */
sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const char **pzTail /* OUT: End of parsed string */
);
其调用 sqlite3RunParser()函数,在给定的 SQL 字符串上执行分析器。
函数中,先创建 Parse 结构、加锁等等,并调用 sqlite3RunParser()函数:
sqlite3RunParser(pParse, zSql, &zErrMsg);
此处 zSql 是一个完整的 SQL 语句串。
调用返回后还要做一系列处理。
四 sqlite3RunParser()
位于tokenize.c 中
功能:在给定的 SQL 字符串上执行分析器。传入一个 parser 结构。返回一个 SQLITE_状态
码。如果有错误发生,将错误信息写入*pzErrMsg。
本函数内部是一个循环语句,每次循环处理一个词,根据词的类型做出不同的处理。如果是
词合法,都会调用 sqlite3Parser()函数对其进行分析。
五 sqlite3Parser()
位于 parse.c
是分析器主程序。
代码晦涩难懂,经查找资料发现是根据语法文件parse.y和语法分析器模板lempar.c自动生成的,分析器核心函数是sqlite3Parser,它不断的接受词法分析器输入的分词结果,并根据输入值查询此时接受该单词后是移进还是规约,如果移进,内部保持相关状态,等待下一步的输入。
每一次规约的动作代码都会产生一些机器指令,记录在stmt结构中,等完整的输入一个SQL后,该SQL的执行指令全部被生成出来。此时调用虚拟机的核心函数即可以完成具体的动作。
参考资料:《sqlite权威指南(第二版)》