FMDatabasePool.m 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. //
  2. // FMDatabasePool.m
  3. // fmdb
  4. //
  5. // Created by August Mueller on 6/22/11.
  6. // Copyright 2011 Flying Meat Inc. All rights reserved.
  7. //
  8. #if FMDB_SQLITE_STANDALONE
  9. #import <sqlite3/sqlite3.h>
  10. #else
  11. #import <sqlite3.h>
  12. #endif
  13. #import "FMDatabasePool.h"
  14. #import "FMDatabase.h"
  15. @interface FMDatabasePool () {
  16. dispatch_queue_t _lockQueue;
  17. NSMutableArray *_databaseInPool;
  18. NSMutableArray *_databaseOutPool;
  19. }
  20. - (void)pushDatabaseBackInPool:(FMDatabase*)db;
  21. - (FMDatabase*)db;
  22. @end
  23. @implementation FMDatabasePool
  24. @synthesize path=_path;
  25. @synthesize delegate=_delegate;
  26. @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate;
  27. @synthesize openFlags=_openFlags;
  28. + (instancetype)databasePoolWithPath:(NSString *)aPath {
  29. return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
  30. }
  31. + (instancetype)databasePoolWithURL:(NSURL *)url {
  32. return FMDBReturnAutoreleased([[self alloc] initWithPath:url.path]);
  33. }
  34. + (instancetype)databasePoolWithPath:(NSString *)aPath flags:(int)openFlags {
  35. return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath flags:openFlags]);
  36. }
  37. + (instancetype)databasePoolWithURL:(NSURL *)url flags:(int)openFlags {
  38. return FMDBReturnAutoreleased([[self alloc] initWithPath:url.path flags:openFlags]);
  39. }
  40. - (instancetype)initWithURL:(NSURL *)url flags:(int)openFlags vfs:(NSString *)vfsName {
  41. return [self initWithPath:url.path flags:openFlags vfs:vfsName];
  42. }
  43. - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
  44. self = [super init];
  45. if (self != nil) {
  46. _path = [aPath copy];
  47. _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
  48. _databaseInPool = FMDBReturnRetained([NSMutableArray array]);
  49. _databaseOutPool = FMDBReturnRetained([NSMutableArray array]);
  50. _openFlags = openFlags;
  51. _vfsName = [vfsName copy];
  52. }
  53. return self;
  54. }
  55. - (instancetype)initWithPath:(NSString *)aPath flags:(int)openFlags {
  56. return [self initWithPath:aPath flags:openFlags vfs:nil];
  57. }
  58. - (instancetype)initWithURL:(NSURL *)url flags:(int)openFlags {
  59. return [self initWithPath:url.path flags:openFlags vfs:nil];
  60. }
  61. - (instancetype)initWithPath:(NSString*)aPath {
  62. // default flags for sqlite3_open
  63. return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE];
  64. }
  65. - (instancetype)initWithURL:(NSURL *)url {
  66. return [self initWithPath:url.path];
  67. }
  68. - (instancetype)init {
  69. return [self initWithPath:nil];
  70. }
  71. + (Class)databaseClass {
  72. return [FMDatabase class];
  73. }
  74. - (void)dealloc {
  75. _delegate = 0x00;
  76. FMDBRelease(_path);
  77. FMDBRelease(_databaseInPool);
  78. FMDBRelease(_databaseOutPool);
  79. FMDBRelease(_vfsName);
  80. if (_lockQueue) {
  81. FMDBDispatchQueueRelease(_lockQueue);
  82. _lockQueue = 0x00;
  83. }
  84. #if ! __has_feature(objc_arc)
  85. [super dealloc];
  86. #endif
  87. }
  88. - (void)executeLocked:(void (^)(void))aBlock {
  89. dispatch_sync(_lockQueue, aBlock);
  90. }
  91. - (void)pushDatabaseBackInPool:(FMDatabase*)db {
  92. if (!db) { // db can be null if we set an upper bound on the # of databases to create.
  93. return;
  94. }
  95. [self executeLocked:^() {
  96. if ([self->_databaseInPool containsObject:db]) {
  97. [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise];
  98. }
  99. [self->_databaseInPool addObject:db];
  100. [self->_databaseOutPool removeObject:db];
  101. }];
  102. }
  103. - (FMDatabase*)db {
  104. __block FMDatabase *db;
  105. [self executeLocked:^() {
  106. db = [self->_databaseInPool lastObject];
  107. BOOL shouldNotifyDelegate = NO;
  108. if (db) {
  109. [self->_databaseOutPool addObject:db];
  110. [self->_databaseInPool removeLastObject];
  111. }
  112. else {
  113. if (self->_maximumNumberOfDatabasesToCreate) {
  114. NSUInteger currentCount = [self->_databaseOutPool count] + [self->_databaseInPool count];
  115. if (currentCount >= self->_maximumNumberOfDatabasesToCreate) {
  116. NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount);
  117. return;
  118. }
  119. }
  120. db = [[[self class] databaseClass] databaseWithPath:self->_path];
  121. shouldNotifyDelegate = YES;
  122. }
  123. //This ensures that the db is opened before returning
  124. #if SQLITE_VERSION_NUMBER >= 3005000
  125. BOOL success = [db openWithFlags:self->_openFlags vfs:self->_vfsName];
  126. #else
  127. BOOL success = [db open];
  128. #endif
  129. if (success) {
  130. if ([self->_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![self->_delegate databasePool:self shouldAddDatabaseToPool:db]) {
  131. [db close];
  132. db = 0x00;
  133. }
  134. else {
  135. //It should not get added in the pool twice if lastObject was found
  136. if (![self->_databaseOutPool containsObject:db]) {
  137. [self->_databaseOutPool addObject:db];
  138. if (shouldNotifyDelegate && [self->_delegate respondsToSelector:@selector(databasePool:didAddDatabase:)]) {
  139. [self->_delegate databasePool:self didAddDatabase:db];
  140. }
  141. }
  142. }
  143. }
  144. else {
  145. NSLog(@"Could not open up the database at path %@", self->_path);
  146. db = 0x00;
  147. }
  148. }];
  149. return db;
  150. }
  151. - (NSUInteger)countOfCheckedInDatabases {
  152. __block NSUInteger count;
  153. [self executeLocked:^() {
  154. count = [self->_databaseInPool count];
  155. }];
  156. return count;
  157. }
  158. - (NSUInteger)countOfCheckedOutDatabases {
  159. __block NSUInteger count;
  160. [self executeLocked:^() {
  161. count = [self->_databaseOutPool count];
  162. }];
  163. return count;
  164. }
  165. - (NSUInteger)countOfOpenDatabases {
  166. __block NSUInteger count;
  167. [self executeLocked:^() {
  168. count = [self->_databaseOutPool count] + [self->_databaseInPool count];
  169. }];
  170. return count;
  171. }
  172. - (void)releaseAllDatabases {
  173. [self executeLocked:^() {
  174. [self->_databaseOutPool removeAllObjects];
  175. [self->_databaseInPool removeAllObjects];
  176. }];
  177. }
  178. - (void)inDatabase:(void (^)(FMDatabase *db))block {
  179. FMDatabase *db = [self db];
  180. block(db);
  181. [self pushDatabaseBackInPool:db];
  182. }
  183. - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
  184. BOOL shouldRollback = NO;
  185. FMDatabase *db = [self db];
  186. if (useDeferred) {
  187. [db beginDeferredTransaction];
  188. }
  189. else {
  190. [db beginTransaction];
  191. }
  192. block(db, &shouldRollback);
  193. if (shouldRollback) {
  194. [db rollback];
  195. }
  196. else {
  197. [db commit];
  198. }
  199. [self pushDatabaseBackInPool:db];
  200. }
  201. - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
  202. [self beginTransaction:YES withBlock:block];
  203. }
  204. - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
  205. [self beginTransaction:NO withBlock:block];
  206. }
  207. - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block {
  208. #if SQLITE_VERSION_NUMBER >= 3007000
  209. static unsigned long savePointIdx = 0;
  210. NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];
  211. BOOL shouldRollback = NO;
  212. FMDatabase *db = [self db];
  213. NSError *err = 0x00;
  214. if (![db startSavePointWithName:name error:&err]) {
  215. [self pushDatabaseBackInPool:db];
  216. return err;
  217. }
  218. block(db, &shouldRollback);
  219. if (shouldRollback) {
  220. // We need to rollback and release this savepoint to remove it
  221. [db rollbackToSavePointWithName:name error:&err];
  222. }
  223. [db releaseSavePointWithName:name error:&err];
  224. [self pushDatabaseBackInPool:db];
  225. return err;
  226. #else
  227. NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil);
  228. if (self.logsErrors) NSLog(@"%@", errorMessage);
  229. return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
  230. #endif
  231. }
  232. @end