2021SC@SDUSC SQLite源码分析(十三)————SQL 命令编译过程梳理

  • 一、 sqlite3_exec()函数
  • 二、SQL 语句编译的调用层次
  • 一 sqlite3_prepare()
  • 二 sqlite3LockAndPrepare()
  • 三 sqlite3Prepare()
  • 四 sqlite3RunParser()
  • 五 sqlite3Parser()



根据以往博客与小组讨论与资料查找,对命令的编译过程进行梳理,以对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权威指南(第二版)》