New file |
| | |
| | | // |
| | | // FMDatabasePool.m |
| | | // fmdb |
| | | // |
| | | // Created by August Mueller on 6/22/11. |
| | | // Copyright 2011 Flying Meat Inc. All rights reserved. |
| | | // |
| | | |
| | | #if FMDB_SQLITE_STANDALONE |
| | | #import <sqlite3/sqlite3.h> |
| | | #else |
| | | #import <sqlite3.h> |
| | | #endif |
| | | |
| | | #import "FMDatabasePool.h" |
| | | #import "FMDatabase.h" |
| | | |
| | | @interface FMDatabasePool () { |
| | | dispatch_queue_t _lockQueue; |
| | | |
| | | NSMutableArray *_databaseInPool; |
| | | NSMutableArray *_databaseOutPool; |
| | | } |
| | | |
| | | - (void)pushDatabaseBackInPool:(FMDatabase*)db; |
| | | - (FMDatabase*)db; |
| | | |
| | | @end |
| | | |
| | | |
| | | @implementation FMDatabasePool |
| | | @synthesize path=_path; |
| | | @synthesize delegate=_delegate; |
| | | @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate; |
| | | @synthesize openFlags=_openFlags; |
| | | |
| | | |
| | | + (instancetype)databasePoolWithPath:(NSString *)aPath { |
| | | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); |
| | | } |
| | | |
| | | + (instancetype)databasePoolWithURL:(NSURL *)url { |
| | | return FMDBReturnAutoreleased([[self alloc] initWithPath:url.path]); |
| | | } |
| | | |
| | | + (instancetype)databasePoolWithPath:(NSString *)aPath flags:(int)openFlags { |
| | | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath flags:openFlags]); |
| | | } |
| | | |
| | | + (instancetype)databasePoolWithURL:(NSURL *)url flags:(int)openFlags { |
| | | return FMDBReturnAutoreleased([[self alloc] initWithPath:url.path flags:openFlags]); |
| | | } |
| | | |
| | | - (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) { |
| | | _path = [aPath copy]; |
| | | _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); |
| | | _databaseInPool = FMDBReturnRetained([NSMutableArray array]); |
| | | _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); |
| | | _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)initWithPath:(NSString*)aPath { |
| | | // default flags for sqlite3_open |
| | | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE]; |
| | | } |
| | | |
| | | - (instancetype)initWithURL:(NSURL *)url { |
| | | return [self initWithPath:url.path]; |
| | | } |
| | | |
| | | - (instancetype)init { |
| | | return [self initWithPath:nil]; |
| | | } |
| | | |
| | | + (Class)databaseClass { |
| | | return [FMDatabase class]; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | |
| | | _delegate = 0x00; |
| | | FMDBRelease(_path); |
| | | FMDBRelease(_databaseInPool); |
| | | FMDBRelease(_databaseOutPool); |
| | | FMDBRelease(_vfsName); |
| | | |
| | | if (_lockQueue) { |
| | | FMDBDispatchQueueRelease(_lockQueue); |
| | | _lockQueue = 0x00; |
| | | } |
| | | #if ! __has_feature(objc_arc) |
| | | [super dealloc]; |
| | | #endif |
| | | } |
| | | |
| | | |
| | | - (void)executeLocked:(void (^)(void))aBlock { |
| | | dispatch_sync(_lockQueue, aBlock); |
| | | } |
| | | |
| | | - (void)pushDatabaseBackInPool:(FMDatabase*)db { |
| | | |
| | | if (!db) { // db can be null if we set an upper bound on the # of databases to create. |
| | | return; |
| | | } |
| | | |
| | | [self executeLocked:^() { |
| | | |
| | | if ([self->_databaseInPool containsObject:db]) { |
| | | [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; |
| | | } |
| | | |
| | | [self->_databaseInPool addObject:db]; |
| | | [self->_databaseOutPool removeObject:db]; |
| | | |
| | | }]; |
| | | } |
| | | |
| | | - (FMDatabase*)db { |
| | | |
| | | __block FMDatabase *db; |
| | | |
| | | |
| | | [self executeLocked:^() { |
| | | db = [self->_databaseInPool lastObject]; |
| | | |
| | | BOOL shouldNotifyDelegate = NO; |
| | | |
| | | if (db) { |
| | | [self->_databaseOutPool addObject:db]; |
| | | [self->_databaseInPool removeLastObject]; |
| | | } |
| | | else { |
| | | |
| | | if (self->_maximumNumberOfDatabasesToCreate) { |
| | | NSUInteger currentCount = [self->_databaseOutPool count] + [self->_databaseInPool count]; |
| | | |
| | | if (currentCount >= self->_maximumNumberOfDatabasesToCreate) { |
| | | NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | db = [[[self class] databaseClass] databaseWithPath:self->_path]; |
| | | shouldNotifyDelegate = YES; |
| | | } |
| | | |
| | | //This ensures that the db is opened before returning |
| | | #if SQLITE_VERSION_NUMBER >= 3005000 |
| | | BOOL success = [db openWithFlags:self->_openFlags vfs:self->_vfsName]; |
| | | #else |
| | | BOOL success = [db open]; |
| | | #endif |
| | | if (success) { |
| | | if ([self->_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![self->_delegate databasePool:self shouldAddDatabaseToPool:db]) { |
| | | [db close]; |
| | | db = 0x00; |
| | | } |
| | | else { |
| | | //It should not get added in the pool twice if lastObject was found |
| | | if (![self->_databaseOutPool containsObject:db]) { |
| | | [self->_databaseOutPool addObject:db]; |
| | | |
| | | if (shouldNotifyDelegate && [self->_delegate respondsToSelector:@selector(databasePool:didAddDatabase:)]) { |
| | | [self->_delegate databasePool:self didAddDatabase:db]; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | else { |
| | | NSLog(@"Could not open up the database at path %@", self->_path); |
| | | db = 0x00; |
| | | } |
| | | }]; |
| | | |
| | | return db; |
| | | } |
| | | |
| | | - (NSUInteger)countOfCheckedInDatabases { |
| | | |
| | | __block NSUInteger count; |
| | | |
| | | [self executeLocked:^() { |
| | | count = [self->_databaseInPool count]; |
| | | }]; |
| | | |
| | | return count; |
| | | } |
| | | |
| | | - (NSUInteger)countOfCheckedOutDatabases { |
| | | |
| | | __block NSUInteger count; |
| | | |
| | | [self executeLocked:^() { |
| | | count = [self->_databaseOutPool count]; |
| | | }]; |
| | | |
| | | return count; |
| | | } |
| | | |
| | | - (NSUInteger)countOfOpenDatabases { |
| | | __block NSUInteger count; |
| | | |
| | | [self executeLocked:^() { |
| | | count = [self->_databaseOutPool count] + [self->_databaseInPool count]; |
| | | }]; |
| | | |
| | | return count; |
| | | } |
| | | |
| | | - (void)releaseAllDatabases { |
| | | [self executeLocked:^() { |
| | | [self->_databaseOutPool removeAllObjects]; |
| | | [self->_databaseInPool removeAllObjects]; |
| | | }]; |
| | | } |
| | | |
| | | - (void)inDatabase:(void (^)(FMDatabase *db))block { |
| | | |
| | | FMDatabase *db = [self db]; |
| | | |
| | | block(db); |
| | | |
| | | [self pushDatabaseBackInPool:db]; |
| | | } |
| | | |
| | | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { |
| | | |
| | | BOOL shouldRollback = NO; |
| | | |
| | | FMDatabase *db = [self db]; |
| | | |
| | | if (useDeferred) { |
| | | [db beginDeferredTransaction]; |
| | | } |
| | | else { |
| | | [db beginTransaction]; |
| | | } |
| | | |
| | | |
| | | block(db, &shouldRollback); |
| | | |
| | | if (shouldRollback) { |
| | | [db rollback]; |
| | | } |
| | | else { |
| | | [db commit]; |
| | | } |
| | | |
| | | [self pushDatabaseBackInPool:db]; |
| | | } |
| | | |
| | | - (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; |
| | | |
| | | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; |
| | | |
| | | BOOL shouldRollback = NO; |
| | | |
| | | FMDatabase *db = [self db]; |
| | | |
| | | NSError *err = 0x00; |
| | | |
| | | if (![db startSavePointWithName:name error:&err]) { |
| | | [self pushDatabaseBackInPool:db]; |
| | | return err; |
| | | } |
| | | |
| | | block(db, &shouldRollback); |
| | | |
| | | if (shouldRollback) { |
| | | // We need to rollback and release this savepoint to remove it |
| | | [db rollbackToSavePointWithName:name error:&err]; |
| | | } |
| | | [db releaseSavePointWithName:name error:&err]; |
| | | |
| | | [self pushDatabaseBackInPool:db]; |
| | | |
| | | 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 |