在上篇文章中介绍了文件的组成并详细的介绍了 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