FMDatabasePool.m 9.2 KB

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