// // FMDatabaseQueue.m // fmdb // // Created by August Mueller on 6/22/11. // Copyright 2011 Flying Meat Inc. All rights reserved. // #import "AWSFMDatabaseQueue.h" #import "AWSFMDatabase.h" #import "AWSFMDatabase+Private.h" /* 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; @implementation AWSFMDatabaseQueue @synthesize path = _path; @synthesize openFlags = _openFlags; + (instancetype)databaseQueueWithPath:(NSString*)aPath { AWSFMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; AWSFMDBAutorelease(q); return q; } + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags { AWSFMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags]; AWSFMDBAutorelease(q); return q; } + (Class)databaseClass { return [AWSFMDatabase class]; } - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName { self = [super init]; if (self != nil) { _db = [[[self class] databaseClass] databaseWithPath:aPath]; AWSFMDBRetain(_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); AWSFMDBRelease(self); return 0x00; } _path = AWSFMDBReturnRetained(aPath); _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); _openFlags = openFlags; } return self; } - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { return [self initWithPath:aPath flags:openFlags vfs:nil]; } - (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 { AWSFMDBRelease(_db); AWSFMDBRelease(_path); if (_queue) { AWSFMDBDispatchQueueRelease(_queue); _queue = 0x00; } #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)close { AWSFMDBRetain(self); dispatch_sync(_queue, ^() { [self->_db close]; AWSFMDBRelease(_db); self->_db = 0x00; }); AWSFMDBRelease(self); } - (AWSFMDatabase*)database { if (!_db) { _db = AWSFMDBReturnRetained([AWSFMDatabase databaseWithPath:_path]); #if SQLITE_VERSION_NUMBER >= 3005000 BOOL success = [_db openWithFlags:_openFlags]; #else BOOL success = [_db open]; #endif if (!success) { NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); AWSFMDBRelease(_db); _db = 0x00; return 0x00; } } return _db; } - (void)inDatabase:(void (^)(AWSFMDatabase *db))block { /* 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. */ AWSFMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); AWSFMDBRetain(self); dispatch_sync(_queue, ^() { AWSFMDatabase *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 = AWSFMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]); for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { AWSFMResultSet *rs = (AWSFMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; NSLog(@"query: '%@'", [rs query]); } #endif } }); AWSFMDBRelease(self); } - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(AWSFMDatabase *db, BOOL *rollback))block { AWSFMDBRetain(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]; } }); AWSFMDBRelease(self); } - (void)inDeferredTransaction:(void (^)(AWSFMDatabase *db, BOOL *rollback))block { [self beginTransaction:YES withBlock:block]; } - (void)inTransaction:(void (^)(AWSFMDatabase *db, BOOL *rollback))block { [self beginTransaction:NO withBlock:block]; } - (NSError*)inSavePoint:(void (^)(AWSFMDatabase *db, BOOL *rollback))block { __block NSError *err = 0x00; #if SQLITE_VERSION_NUMBER >= 3007000 static unsigned long savePointIdx = 0; AWSFMDBRetain(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]; } }); AWSFMDBRelease(self); #endif return err; } @end