在上篇文章中介绍了文件的组成并详细的介绍了 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 > ?
app
- (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 的补充,添加了一些经常使用的方法函数
-(BOOL)validateSQL:(NSString)sql error:(NSError*)error;
sql的有效性post
-(BOOL)tableExists:(NSString*)tableName;
数据库表是否存在。
-(BOOL)columnExists:(NSString)columnName inTableWithName:(NSString)tableName;
在tableName表中columnName是否存在。
-(FMResultSet*)getSchema;
数据库的一些概要信息
1.欢迎你们对文章给出建议或意见。
2.本文凝结了做者的心血,但愿你们在转发、传阅的时候可以保留文章的初始地址。
相关连接:
参考连接: 1.www.sqlite.org/index.html