New file |
| | |
| | | // |
| | | // 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 <sqlite3/sqlite3.h> |
| | | #else |
| | | #import <sqlite3.h> |
| | | #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 |