在上篇文章中介紹了文件的組成並詳細的介紹了 FMResultSet
類,本文將接着上篇的分析進行 FMDatabase
文件的解讀。php
FMDB源碼主要有一下幾個文件組成:html
FMDatabase:
表示一個單獨的SQLite DB實例,經過它能夠對數據庫進行增刪改查等操做。sql
FMResultSet:
表示經過sql在DB中查詢到的結果集,而且將查詢結果轉化成對應的值或對象,例如:int、long、bool、NSString、NSDate、NSData、char *、 id等。數據庫
FMDatabaseQueue:
用來管理數據查詢的隊列,保證大部分時間下對數據庫的操做是串行的。數組
FMDatabaseAdditions:
做爲 FMDatabase
類的拓展。新增了一些經常使用的校驗方法,例如:表是否存在、列是否存在、版本號、sql校驗等。緩存
FMDatabasePool:
用來管理數據庫查詢任務。不過在頭文件中,做者寫的很是清楚牆裂不建議使用,而是用 FMDatabaseQueue
代替。若是必定要用的話,必定要注意死鎖。bash
FMDatabase
對象並在多個線程中使用它。用 FMDatabaseQueue
代替。+ (NSString*)FMDBUserVersion;
FMDB版本+ (NSString*)sqliteLibVersion;
sqliteLib版本號- (BOOL)open
打開數據庫,並返回狀態標識- (BOOL)open { if (_db) { return YES; } int err = sqlite3_open([self sqlitePath], &_db ); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } if (_maxBusyRetryTimeInterval > 0.0) { // set the handler [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval]; } return YES; } 複製代碼
sqlite3_open
方法,傳入兩個參數,數據庫的localPath和db的內存地址,而且返回執行的狀態結果。SQLITE_OK
則繼續向下執行。maxBusyRetryTimeInterval
初始化的時候默認設置爲2。static int FMDBDatabaseBusyHandler(void *f, int count) { FMDatabase *self = (__bridge FMDatabase*)f; if (count == 0) { self->_startBusyRetryTime = [NSDate timeIntervalSinceReferenceDate]; return 1; } NSTimeInterval delta = [NSDate timeIntervalSinceReferenceDate] - (self->_startBusyRetryTime); if (delta < [self maxBusyRetryTimeInterval]) { sqlite3_sleep(50); // milliseconds return 1; } return 0; } - (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout { _maxBusyRetryTimeInterval = timeout; if (!_db) { return; } if (timeout > 0) { sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self)); } else { // turn it off otherwise sqlite3_busy_handler(_db, nil, nil); } } 複製代碼
FMDBDatabaseBusyHandler
註冊一個回調來處理SQLITE_BUSY錯誤sqlite3_busy_handler(D,X,P)
例程設置了一個回調函數X,當另外一個線程或進程將表鎖定時,只要嘗試訪問與[database connection]
D關聯的數據庫表,就能夠用參數P調用它。 sqlite3_busy_handler()
接口用於實現[sqlite3_busy_timeout()]
和[PRAGMA busy_timeout]。
busy callBack
是 NULL
,則遇到鎖后里面返回 SQLITE_BUSY
。若是 busy callBack
不是 NULL,則可使用兩個參數做爲回調。busy handler
的第一個參數是 void * 指針的副本,同時他也是 sqlite3_busy_handler()
的第三個參數。sqlite3_busy_handler
的第二個參數是須要回調的 busy handler
的次數,表明前面相同 locking event
的次數busy callback
返回0,則不會進行其餘嘗試來訪問數據庫,直接返回 SQLITE_BUSY
,若是不是0,則再次嘗試訪問數據庫並重復循環。busy handler
並不能確保有 在lock contention
的時候被調用。若是 SQLite
斷定在調用 busy handler
的時候會形成死鎖,則會直接返回 SQLITE_BUSY
,而再也不調用 busy handler
read lock
嘗試提高爲 reserved lock
,另外一個線程持有一個 reserved lock
嘗試提高爲 exclusive lock
。這個時候,第一個線程沒法進行,由於它被第二個 blocked;第二個也沒有辦法進行,由於它被第一個blocked。若是兩個線程都調用了 busy handlers
,則二者都不會成功。所以,SQLite爲第一個線程返回 SQLITE_BUSY,但願第一個線程釋放其 read lock,而且第二個線程能夠繼續。[database connection]
只能設置一個 busy handler
.設置新的 handler
的時候,須要提早清除以前的 handler
。注意:調用 [sqlite3_busy_timeout()]
或者計算 [PRAGMA busy_timeout=N]
將會改變 busy handler
從而清除以前的設置。busy callback
不該執行任何修改調用 busy handler
的數據庫鏈接操做。換句話說,busy handler
是不容許重入的。任何此類操做都會致使未定義的行爲。busy handler
不能關閉數據庫鏈接,也不能調用 [prepared statement]
方法。- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { if (![self databaseExists]) { return 0x00; } if (_isExecutingStatement) { [self warnInUse]; return 0x00; } _isExecutingStatement = YES; int rc = 0x00; sqlite3_stmt *pStmt = 0x00; FMStatement *statement = 0x00; FMResultSet *rs = 0x00; if (_traceExecution && sql) { NSLog(@"%@ executeQuery: %@", self, sql); } if (_shouldCacheStatements) { statement = [self cachedStatementForQuery:sql]; pStmt = statement ? [statement statement] : 0x00; [statement reset]; } if (!pStmt) { rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); if (SQLITE_OK != rc) { if (_logsErrors) { NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); NSLog(@"DB Query: %@", sql); NSLog(@"DB Path: %@", _databasePath); } if (_crashOnErrors) { NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); abort(); } sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } } id obj; int idx = 0; int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!) // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support if (dictionaryArgs) { for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon. NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; if (_traceExecution) { NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]); } // Get the index for the parameter name. int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > 0) { // Standard binding from here. [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // increment the binding count, so our check below works out idx++; } else { NSLog(@"Could not find index for %@", dictionaryKey); } } } else { while (idx < queryCount) { if (arrayArgs && idx < (int)[arrayArgs count]) { obj = [arrayArgs objectAtIndex:(NSUInteger)idx]; } else if (args) { obj = va_arg(args, id); } else { //We ran out of arguments break; } if (_traceExecution) { if ([obj isKindOfClass:[NSData class]]) { NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]); } else { NSLog(@"obj: %@", obj); } } idx++; [self bindObject:obj toColumn:idx inStatement:pStmt]; } } if (idx != queryCount) { NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } FMDBRetain(statement); // to balance the release below if (!statement) { statement = [[FMStatement alloc] init]; [statement setStatement:pStmt]; if (_shouldCacheStatements && sql) { [self setCachedStatement:statement forQuery:sql]; } } // the statement gets closed in rs's dealloc or [rs close]; rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self]; [rs setQuery:sql]; NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs]; [_openResultSets addObject:openResultSet]; [statement setUseCount:[statement useCount] + 1]; FMDBRelease(statement); _isExecutingStatement = NO; return rs; } 複製代碼
FMResultSet
對象,失敗的話返回 nil
;和執行更新語句同樣,有一個變量接收error對象。你能夠用 lastErrorMessage
和 lastErrorMessage
方法來肯定查詢失敗的緣由。<[FMResultSet next]>
來實現從一個記錄到另外一個記錄切換。sqlite3_bind
可選的參數值(sqlite.org/c3ref/bind_… )。能夠正確地轉義任何須要轉義序列的字符(例如引號),從而消除簡單的SQL錯誤並防止SQL注入攻擊。本地處理 nsstring
、nsnumber
、「nsnull
」、「nsdate
」和「nsdata
」對象。全部其餘對象類型將使用對象的「description
」方法解釋爲文本值。sql
參數,SELECT statement
可使用 ?來佔位。?
只能是OC對象(例如 nsstring
、nsnumber
等),而不是基本的c數據類型(例如「int
」、「char
」等)。0x00(nil)
statement
,在執行的話,提示數據庫正在使用,並返回 0x00
。isExecutingStatement
置爲 yes,開始進行下面的處理。shouldCacheStatements
字段來判斷是否是緩存傳入的 Statements
,緩存了的話,經過sql做爲key取出 statementsSets
,若是取出的對象是 prepare
的 statement
則賦值給 sqlite3_stmt
pStmt
不存在,則調用 sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
返回成功或失敗狀態,並將準備好的 statement
值放到 pStmt
中。int sqlite3_bind_parameter_count(sqlite3_stmt*)
返回 [SQL parameters]
參數的個數dictionaryArgs
遍歷裏面的參數名的值,經過該值拿到name對應的index[self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]
將傳入的參數和前面sql的 ?
值綁定idx
用來遍歷參數的個數,若是沒有傳入 dictionaryArgs
參數,傳入的而是 arrayArgs
,則經過遍歷數組的拿到對應的 obj
綁定到 idx
位置,即:將通配符?:age
按照索引 賦值爲 obj
pStmt
中參數的個數不一樣,則拋出錯誤?
綁定完的 pStmt
賦值給 FMStatement
,若是須要緩存,則將 sql
做爲 key
,FMStatement
做爲 object
對象放到緩存的字典裏面statement
賦值給 FMResultSet
執行操做。做者特地提到 在 rs的 dealloc
或者 [rs close]
會將statement close
- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
...
綁定完數據,生成最終的 pStmt
rc = sqlite3_step(pStmt);
...
}
複製代碼
sqlite3_step
拓展:
sqlite3_step()
這個過程用於執行有前面sqlite3_prepare建立的準備語句。這個語句執行到結果的第一行可用的位置。繼續前進到結果的第二行的話,只需再次調用sqlite3_step()。繼續調用sqlite3_setp()知道這個語句完成,那些不返回結果的語句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只執行一次就返回
函數的返回值基於建立sqlite3_stmt參數所使用的函數,假如是使用老版本的接口sqlite3_prepare()和sqlite3_prepare16(),返回值會是 SQLITE_BUSY, SQLITE_DONE, SQLITE_ROW, SQLITE_ERROR 或 SQLITE_MISUSE,而v2版本的接口sqlite3_prepare_v2()和sqlite3_prepare16_v2()則會同時返回這些結果碼和擴展結果碼。
對全部V3.6.23.1以及其前面的全部版本,須要在sqlite3_step()以後調用sqlite3_reset(),在後續的sqlite3_ step以前。若是調用sqlite3_reset重置準備語句失敗,將會致使sqlite3_ step返回SQLITE_MISUSE,可是在V3. 6.23.1之後,sqlite3_step()將會自動調用sqlite3_reset。
複製代碼
[cachedStmt useCount] + 1
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... { va_list args; va_start(args, format); NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]]; NSMutableArray *arguments = [NSMutableArray array]; [self extractSQL:format argumentsList:args intoString:sql arguments:arguments]; va_end(args); return [self executeQuery:sql withArgumentsInArray:arguments]; } 複製代碼
該方法其實調用的是- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments
,其中 sql
爲 SELECT * FROM t_student WHERE age > %d
,cleanedSQL
爲轉換完的值 SELECT * FROM t_student WHERE age > ?
markdown
- (BOOL)setKey:(NSString*)key; - (BOOL)setKeyWithData:(NSData *)keyData { #ifdef SQLITE_HAS_CODEC if (!keyData) { return NO; } int rc = sqlite3_key(_db, [keyData bytes], (int)[keyData length]); return (rc == SQLITE_OK); #else #pragma unused(keyData) return NO; #endif } 複製代碼
- (BOOL)rekey:(NSString*)key; - (BOOL)rekeyWithData:(NSData *)keyData { #ifdef SQLITE_HAS_CODEC if (!keyData) { return NO; } int rc = sqlite3_rekey(_db, [keyData bytes], (int)[keyData length]); if (rc != SQLITE_OK) { NSLog(@"error on rekey: %d", rc); NSLog(@"%@", [self lastErrorMessage]); } return (rc == SQLITE_OK); #else #pragma unused(keyData) return NO; #endif } 複製代碼
該類做爲 FMDatabase 的補充,添加了一些經常使用的方法app
-(BOOL)validateSQL:(NSString)sql error:(NSError*)error;
sql的有效性函數
-(BOOL)tableExists:(NSString*)tableName;
數據庫表是否存在。
-(BOOL)columnExists:(NSString)columnName inTableWithName:(NSString)tableName;
在tableName表中columnName是否存在。
-(FMResultSet*)getSchema;
數據庫的一些概要信息
1.歡迎你們對文章給出建議或意見。
2.本文凝結了做者的心血,但願你們在轉發、傳閱的時候可以保留文章的初始地址。
相關連接:
參考連接: 1.www.sqlite.org/index.html