// // FMDatabaseQueue.m // fmdb // // Created by August Mueller on 6/22/11. // Copyright 2011 Flying Meat Inc. All rights reserved. // #import "FMDatabaseQueue.h" #import "FMDatabase.h" #if FMDB_SQLITE_STANDALONE #import #else #import #endif /* Note: we call [self retain]; before using dispatch_sync, just incase FMDatabaseQueue is released on another thread and we're in the middle of doing something in dispatch_sync */ /* * A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses. * This in turn is used for deadlock detection by seeing if inDatabase: is called on * the queue's dispatch queue, which should not happen and causes a deadlock. */ static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey; @interface FMDatabaseQueue () { dispatch_queue_t _queue; FMDatabase *_db; } @end @implementation FMDatabaseQueue + (instancetype)databaseQueueWithPath:(NSString *)aPath { FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; FMDBAutorelease(q); return q; } + (instancetype)databaseQueueWithURL:(NSURL *)url { return [self databaseQueueWithPath:url.path]; } + (instancetype)databaseQueueWithPath:(NSString *)aPath flags:(int)openFlags { FMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags]; FMDBAutorelease(q); return q; } + (instancetype)databaseQueueWithURL:(NSURL *)url flags:(int)openFlags { return [self databaseQueueWithPath:url.path flags:openFlags]; } + (Class)databaseClass { return [FMDatabase class]; } - (instancetype)initWithURL:(NSURL *)url flags:(int)openFlags vfs:(NSString *)vfsName { return [self initWithPath:url.path flags:openFlags vfs:vfsName]; } - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName { self = [super init]; if (self != nil) { _db = [[[self class] databaseClass] databaseWithPath:aPath]; FMDBRetain(_db); #if SQLITE_VERSION_NUMBER >= 3005000 BOOL success = [_db openWithFlags:openFlags vfs:vfsName]; #else BOOL success = [_db open]; #endif if (!success) { NSLog(@"Could not create database queue for path %@", aPath); FMDBRelease(self); return 0x00; } _path = FMDBReturnRetained(aPath); _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); _openFlags = openFlags; _vfsName = [vfsName copy]; } return self; } - (instancetype)initWithPath:(NSString *)aPath flags:(int)openFlags { return [self initWithPath:aPath flags:openFlags vfs:nil]; } - (instancetype)initWithURL:(NSURL *)url flags:(int)openFlags { return [self initWithPath:url.path flags:openFlags vfs:nil]; } - (instancetype)initWithURL:(NSURL *)url { return [self initWithPath:url.path]; } - (instancetype)initWithPath:(NSString *)aPath { // default flags for sqlite3_open return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:nil]; } - (instancetype)init { return [self initWithPath:nil]; } - (void)dealloc { FMDBRelease(_db); FMDBRelease(_path); FMDBRelease(_vfsName); if (_queue) { FMDBDispatchQueueRelease(_queue); _queue = 0x00; } #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)close { FMDBRetain(self); dispatch_sync(_queue, ^() { [self->_db close]; FMDBRelease(_db); self->_db = 0x00; }); FMDBRelease(self); } - (void)interrupt { [[self database] interrupt]; } - (FMDatabase*)database { if (!_db) { _db = FMDBReturnRetained([[[self class] databaseClass] databaseWithPath:_path]); #if SQLITE_VERSION_NUMBER >= 3005000 BOOL success = [_db openWithFlags:_openFlags vfs:_vfsName]; #else BOOL success = [_db open]; #endif if (!success) { NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); FMDBRelease(_db); _db = 0x00; return 0x00; } } return _db; } - (void)inDatabase:(void (^)(FMDatabase *db))block { #ifndef NDEBUG /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue * and then check it against self to make sure we're not about to deadlock. */ FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); #endif FMDBRetain(self); dispatch_sync(_queue, ^() { FMDatabase *db = [self database]; block(db); if ([db hasOpenResultSets]) { NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); #if defined(DEBUG) && DEBUG NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]); for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; NSLog(@"query: '%@'", [rs query]); } #endif } }); FMDBRelease(self); } - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { FMDBRetain(self); dispatch_sync(_queue, ^() { BOOL shouldRollback = NO; if (useDeferred) { [[self database] beginDeferredTransaction]; } else { [[self database] beginTransaction]; } block([self database], &shouldRollback); if (shouldRollback) { [[self database] rollback]; } else { [[self database] commit]; } }); FMDBRelease(self); } - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { [self beginTransaction:YES withBlock:block]; } - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { [self beginTransaction:NO withBlock:block]; } - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { #if SQLITE_VERSION_NUMBER >= 3007000 static unsigned long savePointIdx = 0; __block NSError *err = 0x00; FMDBRetain(self); dispatch_sync(_queue, ^() { NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; BOOL shouldRollback = NO; if ([[self database] startSavePointWithName:name error:&err]) { block([self database], &shouldRollback); if (shouldRollback) { // We need to rollback and release this savepoint to remove it [[self database] rollbackToSavePointWithName:name error:&err]; } [[self database] releaseSavePointWithName:name error:&err]; } }); FMDBRelease(self); return err; #else NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil); if (self.logsErrors) NSLog(@"%@", errorMessage); return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; #endif } @end