SQLite3 源码分析-sqlite_schema(sqlite_master) 的创建
前记
在 sqlite 初始化的过程中,会首先创建 sqlite_schema(sqlite_master) 这个表,然后执行 select * from main.sqlite_master 语句,将数据库中的表,索引,视图,触发器等信息加载到 sqlite->aDb->pSchema 中。
整体结构图
flowchart LR
A(["sqlite init"]) --> B(["创建 sqlite_master"]) --> C(["读取 sqlite_master"]) --> D(["加载数据到内存"])
代码详解
根据 SQLite3 的数据格式 我们可以看到,在数据库文件中的第一个页面存在一个名字叫做 sqlite_schema 的表,这个表中会记录 sqlite 数据库中所有的表,索引,视图,以及触发器。这个表会在我们第一次对数据库进行操作的时候进行创建。这个表就相当于数据库中的一个元信息表,我们对数据库的任何 DDL(Data Definition Language,数据定义语言) 操作之前,都需要检查一下,这个数据库中是不是已经存在了相同名字的表。具体的函数就是在 sqlite3ReadSchema
sqlite3ReadSchema
1
2
3
4
5
int sqlite3ReadSchema(Parse *pParse){
sqlite3 *db = pParse->db;
if( !db->init.busy ){
rc = sqlite3Init(db, &pParse->zErrMsg);
}
在这个函数中我们可以看到如果 db.init.busy 来判断数据库当前是否在初始化中,如果没有在初始化,那么我们就进行初始化。
sqlite3Init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int sqlite3Init(sqlite3 *db, char **pzErrMsg){
int i, rc;
/* Do the main schema first */
if( !DbHasProperty(db, 0, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, 0, pzErrMsg, 0);
if( rc ) return rc;
}
for(i=db->nDb-1; i>0; i--){
assert( i==1 || sqlite3BtreeHoldsMutex(db->aDb[i].pBt) );
if( !DbHasProperty(db, i, DB_SchemaLoaded) ){
rc = sqlite3InitOne(db, i, pzErrMsg, 0);
if( rc ) return rc;
}
}
}
在这个函数中,我们看到主要分为两个部分,一个是检查当前数据库是否已经将数据库中的表,索引等信息读取到内存中的哈希表,这个通过 DB_SchemaLoaded 来判断。如果不是最新的,我们就通过 sqlite3InitOne 来读取。在第二个 for 循环中功能也是一样的,用来读取数据库中的元数据。
sqlite 中有两个数据库,一个是 main 数据库,用于储存长期数据,一个是 temp 数据库,用于存储一些临时的对象。
sqlite3InitOne
现在我们来看一下 sqlite3InitOne 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){
int rc;
int i;
Db *pDb;
char const *azArg[6];
int meta[5];
InitData initData;
const char *zSchemaTabName;
int openedTransaction = 0;
int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed);
db->init.busy = 1;
azArg[0] = "table";
azArg[1] = zSchemaTabName = SCHEMA_TABLE(iDb);
azArg[2] = azArg[1];
azArg[3] = "1";
azArg[4] = "CREATE TABLE x(type text,name text,tbl_name text,"
"rootpage int,sql text)";
azArg[5] = 0;
initData.db = db;
initData.iDb = iDb;
initData.rc = SQLITE_OK;
initData.pzErrMsg = pzErrMsg;
initData.mInitFlags = mFlags;
initData.nInitRow = 0;
initData.mxPage = 0;
sqlite3InitCallback(&initData, 5, (char **)azArg, 0);
/* Create a cursor to hold the database open
*/
pDb = &db->aDb[iDb];
if( pDb->pBt==0 ){
assert( iDb==1 );
DbSetProperty(db, 1, DB_SchemaLoaded);
rc = SQLITE_OK;
goto error_out;
}
sqlite3BtreeEnter(pDb->pBt);
if( sqlite3BtreeTxnState(pDb->pBt)==SQLITE_TXN_NONE ){
rc = sqlite3BtreeBeginTrans(pDb->pBt, 0, 0);
if( rc!=SQLITE_OK ){
sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc));
goto initone_error_out;
}
openedTransaction = 1;
}
for(i=0; i<ArraySize(meta); i++){
sqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]);
}
if( (db->flags & SQLITE_ResetDatabase)!=0 ){
memset(meta, 0, sizeof(meta));
}
pDb->pSchema->schema_cookie = meta[BTREE_SCHEMA_VERSION-1];
pDb->pSchema->enc = ENC(db);
pDb->pSchema->file_format = (u8)meta[BTREE_FILE_FORMAT-1];
if( pDb->pSchema->file_format==0 ){
pDb->pSchema->file_format = 1;
}
if( pDb->pSchema->file_format>SQLITE_MAX_FILE_FORMAT ){
sqlite3SetString(pzErrMsg, db, "unsupported file format");
rc = SQLITE_ERROR;
goto initone_error_out;
}
if( iDb==0 && meta[BTREE_FILE_FORMAT-1]>=4 ){
db->flags &= ~(u64)SQLITE_LegacyFileFmt;
}
assert( db->init.busy );
initData.mxPage = sqlite3BtreeLastPage(pDb->pBt);
{
char *zSql;
zSql = sqlite3MPrintf(db,
"SELECT*FROM\"%w\".%s ORDER BY rowid",
db->aDb[iDb].zDbSName, zSchemaTabName);
rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
}
db->init.busy = 0;
return rc;
}
在这个函数中,我们需要重点看一下:
sqliteInitCallback这个函数- 调用 sqlite_exec 这个函数,这个会指定 sqliteInitCallback 作为回调函数
sqliteInitCallback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){
InitData *pData = (InitData*)pInit;
sqlite3 *db = pData->db;
int iDb = pData->iDb;
if( argv[3]==0 ){
corruptSchema(pData, argv, 0);
}else if( argv[4]
&& 'c'==sqlite3UpperToLower[(unsigned char)argv[4][0]]
&& 'r'==sqlite3UpperToLower[(unsigned char)argv[4][1]] ){
int rc;
u8 saved_iDb = db->init.iDb;
sqlite3_stmt *pStmt;
db->init.iDb = iDb;
pStmt = 0;
TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0);
rc = db->errCode;
assert( (rc&0xFF)==(rcp&0xFF) );
db->init.iDb = saved_iDb;
/* assert( saved_iDb==0 || (db->mDbFlags & DBFLAG_Vacuum)!=0 ); */
db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */
sqlite3_finalize(pStmt);
}
return 0;
}
在这个 argv[4] 中是这个 sql 语句,这个就是我们刚刚创建那个 CREATE TABLE x(type text, name text,tbl_name text), 这里就进入到我们判断第一个字符是不是 ‘c’ ,第二个字符是不是 ‘r’ ,如果是的话,说明就是一个创建表的语句(v这里为什么不是创建索引或者其他之类的呢,因为这是在初始化的过程,记录元信息表才是最重要的)。接下来我们就会进入到 sqlite3Prepare
sqlite3Prepare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
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 */
){
int rc = SQLITE_OK; /* Result code */
int i; /* Loop counter */
Parse sParse; /* Parsing context */
if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){
...
}else{
sqlite3RunParser(&sParse, zSql);
}
assert( 0==sParse.nQueryLoop );
if( pzTail ){
*pzTail = sParse.zTail;
}
return rc;
}
这里我们调用 sqlite3RunParser 函数进行 sql 解析,等解析完成后,我们就会生成调用”编译器” 来生成字节码,这一部分我们后续再说。进入到这个函数以后,就会调用由 parse.y 这个文件生成的语法生成器来解析 sql 语法。
这里我们引入部分 parse.y 的文件内容来进行说明:
create_table_args ::= LP columnlist conslist_opt(X) RP(E) table_option_set(F). {
sqlite3EndTable(pParse,&X,&E,F,0);
}
这里我们可以看到创建一个表的解析过程为左括号(LP), 数据列列表(columnlist),限制选项(conslist_opt),右括号(RP), 建表的一些属性(table_option_set) ,最后由 sqlite3EndTable 这个函数来创建一个表。接下来我们来看一下这个函数
sqlite3EndTable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void sqlite3EndTable(
Parse *pParse, /* Parse context */
Token *pCons, /* The ',' token after the last column defn. */
Token *pEnd, /* The ')' before options in the CREATE TABLE */
u32 tabOpts, /* Extra table options. Usually 0. */
Select *pSelect /* Select from a "CREATE ... AS SELECT" */
){
Table *p; /* The new table */
sqlite3 *db = pParse->db; /* The database connection */
int iDb; /* Database in which the table lives */
Index *pIdx; /* An implied index of the table */
/* Compute the complete text of the CREATE statement */
if( pSelect ){
zStmt = createTableStmt(db, p);
}else{
Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd;
n = (int)(pEnd2->z - pParse->sNameToken.z);
if( pEnd2->z[0]!=';' ) n += pEnd2->n;
zStmt = sqlite3MPrintf(db,
"CREATE %s %.*s", zType2, n, pParse->sNameToken.z
);
}
/* Add the table to the in-memory representation of the database.
*/
if( db->init.busy ){
Table *pOld;
Schema *pSchema = p->pSchema;
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
assert( HasRowid(p) || p->iPKey<0 );
pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p);
if( pOld ){
assert( p==pOld ); /* Malloc must have failed inside HashInsert() */
sqlite3OomFault(db);
return;
}
}
}
这里我们主要看一下有一个 ` if( pSelect ) 语句,这里用来判断是否是 CREATE TABLE AS SELECT 这种形式的语句。这里我们不做说明, 然后我们进入 else 语句块,这里实际是组成了 CREATE TABLE master.sqlite_schema 的语句,然后我们就可以在下面的的if 判断中将数据表插入到内存中。sqlite3HashInsert` 这个函数用来将数据表插入到内存中。
这个函数的原型为 sqlite3HashInsert(Hash *pH, const char *pKey, void *data) ,目的就是将表,索引等插入到 Hash 表中。 修改的数据对象为 db.aDb.pSchema.tblHash.
后记
第一篇 sqlite3 的源码解析,已经写完了,但是感觉写的并不好,逻辑不够清晰,代码解释有点云里雾里,不过加油吧。💪。