1. 程式人生 > >sqlite3原始碼解析之sql解析(一)

sqlite3原始碼解析之sql解析(一)

一:sql準備過程

在前面的分析中我們知道,sqlite3_open()為我們打開了資料庫並準備了所要的記憶體空間,鎖,vfs等。

接下來就分析sql是如何被解析器一步一步解析的。


上圖是準備sql語句的過程分析圖。

  1.1:sqlite3_prepare_v2函式:

該函數是準備的入口函式。

傳入了5個引數:

sqlite3 *db :          sqlite3_open()返回的資料庫控制代碼

const char *zSql:     要準備的sql語句

int nBytes :          sql語句的 長度(strlen(sql))

sqlite3_stmt **ppStmt: 準備的語句儲存該結構體物件中,可以認為是預編譯語句的控制代碼,執行時需要傳入這個控制代碼

const char **pzTail:   指向被解析的字串的末尾

SQLite 現在提供兩個版本的編譯 API 函式:遺留的和現在使用的。

在遺留版本中,原始 SQL 文字沒有儲存在編譯後的語句(sqlite3_stmt 結構)中,因此,如

果 schema 發生改變,sqlite3_step()會返回 SQLITE_SCHEMA。在新版本中,編譯後的語句

中儲存原始 SQL 文字,當遇到schema 改變時自動重新編譯。sqlite3_prepare()函式中其實只包含一條對 sqlite3LockAndPrepare()的呼叫語句:

SQLITE_APIint sqlite3_prepare_v2(

sqlite3 *db,              /* Database handle. 成功開啟的資料庫控制代碼*/

constchar *zSql,         /* UTF-8 encoded SQL statement. UTF8編碼的 SQL 語句 */

intnBytes,               /* Length of zSql in bytes. 引數 sql 的位元組數, 包含 '\0'  */

sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement 輸出:預編譯語句控制代碼 */

constchar **pzTail/* OUT: End of parsed string 輸出:指向 sql 語句中未使用的部分*/

){

int rc;

rc = sqlite3LockAndPrepare(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,0,

ppStmt,pzTail);

assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 );

return rc;

}

1.2:sqlite3LockAndPrepare函式

該函式並沒有做太多事情主要是多了兩個引數,判斷一下db是否合法。

呼叫sqlite3Prepare函式。

staticint sqlite3LockAndPrepare(

sqlite3 *db,              /* Database handle. */

constchar *zSql,         /* UTF-8 encoded SQL statement.(UTF-8 編碼的 SQL 語句) */

intnBytes,               /* Length of zSql in bytes. (zSql 的位元組數)*/

u32prepFlags,            /* Zero or more SQLITE_PREPARE_* flags(儲存的sql文字) */

Vdbe *pOld,               /* VM being reprepared */

sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement(指向已準備好的語句的指標 ) */

constchar **pzTail/* OUT: End of parsed string(未處理的 SQL 串) */

){

int rc;

#ifdef SQLITE_ENABLE_API_ARMOR

if( ppStmt==0 ) return SQLITE_MISUSE_BKPT;

#endif

*ppStmt = 0;

if( !sqlite3SafetyCheckOk(db)||zSql==0 ){//確定 db 指標的合法性

returnSQLITE_MISUSE_BKPT;

}

sqlite3_mutex_enter(db->mutex);

sqlite3BtreeEnterAll(db);

 rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);//將 UTF-8 編碼的 SQL 語句 zSql 編譯成

if( rc==SQLITE_SCHEMA ){    // 如果遇到 SCHEMA 改變,定案,再編譯

sqlite3ResetOneSchema(db, -1);

sqlite3_finalize(*ppStmt);

rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);

}

sqlite3BtreeLeaveAll(db);

sqlite3_mutex_leave(db->mutex);

assert( rc==SQLITE_OK || *ppStmt==0 );

return rc;

}

1.3: sqlite3Prepare函式

這個函式做了以下幾個事:

1:加入解析器Parse

2:所需記憶體的初始化以及是否禁用後備記憶體

3:判斷連結的資料庫是否加鎖,如果加鎖需要解鎖

4:zSqlCopy= sqlite3DbStrNDup(db, zSql, nBytes)語句複製了zsql

5:呼叫sqlite3RunParser(&sParse,zSql, &zErrMsg);

還有的後面補上

staticint sqlite3Prepare(

sqlite3 *db,              /* Database handle. */

constchar *zSql,         /* UTF-8 encoded SQL statement. */

intnBytes,               /* Length of zSql in bytes. */

u32prepFlags,            /* Zero or more SQLITE_PREPARE_* flags */

Vdbe *pReprepare,         /* VM being reprepared */

sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement (輸出經過處理的準備語句)*/

constchar **pzTail/* OUT: End of parsed string */

){

char *zErrMsg = 0;        /* Error message */

int rc = SQLITE_OK;       /* Result code */

int i;                    /* Loop counter */

Parse sParse;             /* Parsing context(解析語境) */

memset(&sParse, 0, PARSE_HDR_SZ);

memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ);//初始化記憶體空間

sParse.pReprepare = pReprepare;

assert( ppStmt && *ppStmt==0 );

/* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */

assert( sqlite3_mutex_held(db->mutex) );

/* For a long-term use prepared statement avoid the use of

** lookaside memory.

(長期使用事先準備好的宣告避免後備儲存器的使用。)

*/

if( prepFlags & SQLITE_PREPARE_PERSISTENT ){

sParse.disableLookaside++;//禁用後備記憶體 來使需要重複使用的準備語句可以多次使用

db->lookaside.bDisable++;//同時禁止db中後備記憶體分配的引數

}

/*

**(檢查以驗證是否可以在所有資料庫模式上獲得讀鎖 .無法獲得讀鎖,表明某些其他資料庫連線持有寫鎖。

這反過來意味著另一個連線對模式做出了未提交的更改。 )

**(我們是否要繼續和準備對未提交的模式更改的宣告,如果這些模式更改隨後回滾,並在其位置進行不同的更改

然後,當準備好的語句執行時,模式cookie將無法檢測模式更改。災難會隨之發生。

*/

for(i=0; i<db->nDb; i++) {//依此遍歷連線的資料庫

Btree *pBt = db->aDb[i].pBt;

if( pBt ){

assert( sqlite3BtreeHoldsMutex(pBt) );

rc = sqlite3BtreeSchemaLocked(pBt);//判斷這個btree是否被鎖

if( rc ){ //如果是就提示該資料庫已被加鎖

constchar *zDb = db->aDb[i].zDbSName;

sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb);

testcase( db->flags & SQLITE_ReadUncommit );

goto end_prepare;

}

}

}

sqlite3VtabUnlockList(db);//然後解鎖

sParse.db = db;

if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){

char *zSqlCopy;

int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; //準備語句最大長度

testcase( nBytes==mxLen );

testcase( nBytes==mxLen+1 );

if( nBytes>mxLen ){//如果超過了這個長度就提示

sqlite3ErrorWithMsg(db, SQLITE_TOOBIG, "statement too long");

rc = sqlite3ApiExit(db, SQLITE_TOOBIG);

goto end_prepare;//結束準備階段

}

zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes);//複製zSql字串

if( zSqlCopy ){

sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg);

sParse.zTail = &zSql[sParse.zTail-zSqlCopy];

sqlite3DbFree(db, zSqlCopy);

}else{

sParse.zTail = &zSql[nBytes];

}

}else{

sqlite3RunParser(&sParse, zSql, &zErrMsg);

}

assert( 0==sParse.nQueryLoop );

if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;

if( sParse.checkSchema ){

schemaIsValid(&sParse);

}

if( db->mallocFailed ){

sParse.rc = SQLITE_NOMEM_BKPT;

}

if( pzTail ){

*pzTail = sParse.zTail;

}

rc = sParse.rc;

#ifndef SQLITE_OMIT_EXPLAIN

if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){

staticconstchar * const azColName[] = {

"addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",

"selectid", "order", "from", "detail"

};

int iFirst, mx;

if( sParse.explain==2 ){

sqlite3VdbeSetNumCols(sParse.pVdbe, 4);

iFirst = 8;

mx = 12;

}else{

sqlite3VdbeSetNumCols(sParse.pVdbe, 8);

iFirst = 0;

mx = 8;

}

for(i=iFirst; i<mx; i++){

sqlite3VdbeSetColName(sParse.pVdbe, i-iFirst, COLNAME_NAME,

azColName[i], SQLITE_STATIC);

}

}

#endif

if( db->init.busy==0 ){

sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags);

}

if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){

sqlite3VdbeFinalize(sParse.pVdbe);

assert(!(*ppStmt));

}else{

*ppStmt = (sqlite3_stmt*)sParse.pVdbe;

}

if( zErrMsg ){

sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg);

sqlite3DbFree(db, zErrMsg);

}else{

sqlite3Error(db, rc);

}

/* Delete any TriggerPrg structures allocated while parsing this statement. */

while( sParse.pTriggerPrg ){

TriggerPrg *pT = sParse.pTriggerPrg;

sParse.pTriggerPrg = pT->pNext;

sqlite3DbFree(db, pT);

}

end_prepare:

sqlite3ParserReset(&sParse);

rc = sqlite3ApiExit(db, rc);

assert( (rc&db->errMask)==rc );

return rc;

}

1.4:sqlite3RunParser函式

sqlite3RunParser位於token.c檔案中,它是進行SQL語句分析的入口,它呼叫sqlite3GetTokenSQL語句zSql進行分詞,然後呼叫sqlite3Parser進行語法分析。而sqlite3Parser在語法規則發生規約時呼叫相應的opcode生成子例程,生成opcode

在給定的SQL字串上執行解析器.解析器結構傳入。

功能:在給定的 SQL 字串上執行分析器。傳入一個 parser 結構。返回一個 SQLITE_狀態

碼。如果有錯誤發生,將錯誤資訊寫入*pzErrMsg

本函式內部是一個迴圈語句,每次迴圈處理一個詞,根據詞的型別做出不同的處理。如果是

正經的詞(不是空格什麼的),都會呼叫 sqlite3Parser()函式對其進行分析

SQLITE_PRIVATEint sqlite3RunParser(Parse *pParse, constchar *zSql, char **pzErrMsg){

int nErr = 0;                   /* Number of errors encountered(遇到的錯誤數) */

void *pEngine;                  /* The LEMON-generated LALR(1) parser(lemon演算法解析器) */

int n = 0;                      /* Length of the next token token(下一個token的長度) */

int tokenType;                  /* type of the next token (下一個token的型別)*/

int lastTokenParsed = -1;       /* type of the previous token (上一個token的型別)*/

sqlite3 *db = pParse->db;       /* The database connection */

int mxSqlLen;                   /* Max length of an SQL string(sql字串的最大長度) */

#ifdefsqlite3Parser_ENGINEALWAYSONSTACK

yyParser sEngine;    /* Space to hold the Lemon-generated Parser object(儲存lemon生成的解析器物件的空間 ) */

#endif

assert( zSql!=0 );

mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH];//     mxSqlLen 1000000000   int

if( db->nVdbeActive==0 ){//虛擬機器活動數量為0

db->u1.isInterrupted = 0;//sqlite3_interrupt不被執行(我猜是中斷執行)

}

pParse->rc = SQLITE_OK;

pParse->zTail = zSql;

assert( pzErrMsg!=0 );

/* sqlite3ParserTrace(stdout, "parser: "); */

#ifdefsqlite3Parser_ENGINEALWAYSONSTACK

pEngine = &sEngine;

 sqlite3ParserInit(pEngine);//初始化已分配的解析器(初始化解析器物件各個欄位)

#else

pEngine = sqlite3ParserAlloc(sqlite3Malloc);

if( pEngine==0 ){

sqlite3OomFault(db);

return SQLITE_NOMEM_BKPT;

}

#endif

assert( pParse->pNewTable==0 );

assert( pParse->pNewTrigger==0 );

assert( pParse->nVar==0 );

assert( pParse->pVList==0 );

while( 1 ){

if( zSql[0]!=0 ){// zSql[0]表示一個token 開頭(比如select就為s)

   n = sqlite3GetToken((u8*)zSql, &tokenType);

mxSqlLen -= n;

if( mxSqlLen<0 ){

pParse->rc = SQLITE_TOOBIG;

break;

}

}else{

if( lastTokenParsed==TK_SEMI ){

tokenType = 0;

}elseif( lastTokenParsed==0 ){

break;

}else{

tokenType = TK_SEMI;

}

zSql -= n;

}

if( tokenType>=TK_SPACE ){

assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL );

if( db->u1.isInterrupted ){

pParse->rc = SQLITE_INTERRUPT;

break;

}

if( tokenType==TK_ILLEGAL ){

sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql);

break;

}

zSql += n;

}else{

pParse->sLastToken.z = zSql;//0x0070808f "id,name from stu" const char *

pParse->sLastToken.n = n;

  sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse);

lastTokenParsed = tokenType;

zSql += n;

if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break;

}

}

assert( nErr==0 );

pParse->zTail = zSql;

#ifdef YYTRACKMAXSTACKDEPTH

sqlite3_mutex_enter(sqlite3MallocMutex());

sqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK,

sqlite3ParserStackPeak(pEngine)

);

sqlite3_mutex_leave(sqlite3MallocMutex());

#endif/* YYDEBUG */

#ifdefsqlite3Parser_ENGINEALWAYSONSTACK

sqlite3ParserFinalize(pEngine);

#else

sqlite3ParserFree(pEngine, sqlite3_free);

#endif

if( db->mallocFailed ){

pParse->rc = SQLITE_NOMEM_BKPT;

}

if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){

pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc));

}

assert( pzErrMsg!=0 );

if( pParse->zErrMsg ){

*pzErrMsg = pParse->zErrMsg;

sqlite3_log(pParse->rc, "%s", *pzErrMsg);

pParse->zErrMsg = 0;

nErr++;

}

if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){

sqlite3VdbeDelete(pParse->pVdbe);

pParse

相關推薦

mybatis原始碼解析Configuration載入

概要 上一篇,我們主要搭建了一個簡單的環境,這邊我們主要來分析下mybatis是如何來載入它的配置檔案Configuration.xml的。 分析 1 public class App { 2 public static void main(String[] args) { 3

spring原始碼解析IOC容器

  學習優秀框架的原始碼,是提升個人技術水平必不可少的一個環節。如果只是停留在知道怎麼用,但是不懂其中的來龍去脈,在技術的道路上註定走不長遠。最近,學習了一段時間的spring原始碼,現在整理出來,以便日後溫故知新。   IOC容器是spring最核心的模組之一,是整個spring體系的基石,spring其

複習SQL語句——常用單表“增刪改查”語句使用

結構化查詢語言(Structured Query Language)簡稱SQL 結構化查詢語言是高階的非過程化程式語言,允許使用者在高層資料結構上工作。它不要求使用者指定對資料的存放方法,也不需要使用者瞭解具體的資料存放方式,所以具有完全不同底層結構的不同資料庫

Android4.4.2原始碼分析WiFi模組

已經寫了幾篇關於Android原始碼的,原始碼程式碼量太大,所以如果想分析某個模組可能不知如何下手,說一下思路 1,分析原始碼英文閱讀能力要夠,想要分析某個模組一般找模組對應的英文,就是模組 2,找到之後首先檢視清單配置檔案Androidmani.fest,找到程式主介面activity 3,通過檢視配置檔

Java併發包原始碼學習AQS框架概述

AQS其實就是java.util.concurrent.locks.AbstractQueuedSynchronizer這個類。 閱讀Java的併發包原始碼你會發現這個類是整個java.util.concurrent的核心之一,也可以說是閱讀整個併發包原始碼的一個突破口。 比如讀ReentrantLock的

spark mllib原始碼分析L-BFGS

1. 使用 spark給出的example中涉及到LBFGS有兩個,分別是LBFGSExample.scala和LogisticRegressionWithLBFGSExample.scala,第一個是直接使用LBFGS直接訓練,需要指定一系列優化引數,優

sqlite3原始碼解析sql解析

一:sql準備過程在前面的分析中我們知道,sqlite3_open()為我們打開了資料庫並準備了所要的記憶體空間,鎖,vfs等。接下來就分析sql是如何被解析器一步一步解析的。上圖是準備sql語句的過程分析圖。  1.1:sqlite3_prepare_v2函式:該函數是準備

springboot原始碼解析-管中窺豹系列總體結構

# 一、簡介 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

mybatis原始碼解析Configuration載入

概述 上一篇我們講了configuation.xml中幾個標籤的解析,例如<properties>,<typeAlises>,<settings>等,今天我們來介紹剩下的兩個比較重要的標籤之一,<environments>,這個標籤主要用於我們訪問資料庫的配置

併發程式設計十二—— Java 執行緒池 實現原理與原始碼深度解析 submit方法

在上一篇《併發程式設計(十一)—— Java 執行緒池 實現原理與原始碼深度解析(一)》中提到了執行緒池ThreadPoolExecutor的原理以及它的execute方法。這篇文章是接著上一篇文章寫的,如果你沒有閱讀上一篇文章,建議你去讀讀。本文解析ThreadPoolExecutor#submit。  

SOFABoot原始碼解析啟動原理1-註解分析

一 、概述        SOFABoot是螞蟻金服開源的基於 Spring Boot 的研發框架,它在Spring Boot 的基礎上,提供了諸如 Readiness Check,類隔離,日誌空間隔離等等能力。在增強了 Spring Boot 的同時,SOFABoot 提供

C語言網路程式設計域名解析

在網路程式設計時,知道域名是不能直接訪問一個主機的,需要轉換成相應的IP地址。有時在程式中需要將一個IP地址轉換成一個域名。本節將講解C程式中的IP地址與域名的轉換問題。 提示:在TCP/IP網路中,通訊雙方的主機必須知道彼此的IP地址方可進行正常的通訊,如果給出的主機的域

SOFABoot原始碼解析啟動原理2-原始碼解析

1.  private voidprocessImports(ConfigurationClass configClass, SourceClass currentSourceClass,2.           Collection<SourceClass>importCandidates, b

Android原始碼解析Window系列第篇---Window的基本認識和Activity的Window建立過程

您可能聽說過View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道這幾個類是什麼關係,幹嘛用的。概括的來說,View是放在Window中的,Window是一個抽象

Hystrix原始碼解析--HystrixCircuitBreaker--思想層面

一、什麼是HystrixCircuitBreaker? HystrixCircuitBreaker可以防止應用程式重複的嘗試呼叫容易失敗的依賴服務。HystrixCircuitBreaker的目的和Retry模式的目的是不同的。Retry模式令應用程式不斷的去重試呼叫依賴服

spring原始碼解析IOC容器------載入和註冊

  上一篇跟蹤了IOC容器對配置檔案的定位,現在我們繼續跟蹤程式碼,看看IOC容器是怎麼載入和註冊配置檔案中的資訊的。開始之前,首先我們先來了解一下IOC容器所使用的資料結構-------BeanDefinition,它是一個上層介面,有很多實現類,分別對應不同的資料載體。我們平時開發的時候,也會定義很多po

spring原始碼解析IOC容器——依賴注入

  上一篇主要是跟蹤了IOC容器對bean標籤進行解析之後存入Map中的過程,這些bean只是以BeanDefinition為載體單純的儲存起來了,並沒有轉換成一個個的物件,今天繼續進行跟蹤,看一看IOC容器是怎樣例項化物件的。   我們都使用過以下程式碼: 1 FileSystemXmlApplicati

Spring原始碼解析基礎應用

方法注入 在spring容器中,大部分bean的作用域(scope)是單例(singleton)的,少部分bean的作用域是原型(prototype),如果一個bean的作用域是原型,我們A bean的作用域是原型,B bean中以@Autowired的方式注入A,那麼B在A中依舊是單例。我們可以讓B類實現A

Spring原始碼解析基礎應用

組合Java配置 在XML中,我們可以使用<import/>標籤,在一個XML檔案中引入另一個XML檔案,在Java類中,我們同樣可以在一個配置類中用@Import引入另一個配置類,被引入的配置類中的@Bean也會載入到spring容器。程式碼如下: @Configuration public

T-SQL基礎簡單查詢

名詞解釋 SQL: Structured Query Language,結構化查詢語言,是一種在關係型資料庫中用於管理資料的標準語言。SQL是一種宣告式程式語言,即只需表明需要什麼而無需關注實現細節(C#中的LINQ也是如此)。 SQL方言:在SQL基礎上延伸的其它語言,如SQL Server中