FMDatabaseQueue.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. //
  2. // FMDatabaseQueue.m
  3. // fmdb
  4. //
  5. // Created by August Mueller on 6/22/11.
  6. // Copyright 2011 Flying Meat Inc. All rights reserved.
  7. //
  8. #import "FMDatabaseQueue.h"
  9. #import "FMDatabase.h"
  10. #if FMDB_SQLITE_STANDALONE
  11. #import <sqlite3/sqlite3.h>
  12. #else
  13. #import <sqlite3.h>
  14. #endif
  15. typedef NS_ENUM(NSInteger, FMDBTransaction) {
  16. FMDBTransactionExclusive,
  17. FMDBTransactionDeferred,
  18. FMDBTransactionImmediate,
  19. };
  20. /*
  21. Note: we call [self retain]; before using dispatch_sync, just incase
  22. FMDatabaseQueue is released on another thread and we're in the middle of doing
  23. something in dispatch_sync
  24. */
  25. /*
  26. * A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses.
  27. * This in turn is used for deadlock detection by seeing if inDatabase: is called on
  28. * the queue's dispatch queue, which should not happen and causes a deadlock.
  29. */
  30. static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
  31. @interface FMDatabaseQueue () {
  32. dispatch_queue_t _queue;
  33. FMDatabase *_db;
  34. }
  35. @end
  36. @implementation FMDatabaseQueue
  37. + (instancetype)databaseQueueWithPath:(NSString *)aPath {
  38. FMDatabaseQueue *q = [[self alloc] initWithPath:aPath];
  39. FMDBAutorelease(q);
  40. return q;
  41. }
  42. + (instancetype)databaseQueueWithURL:(NSURL *)url {
  43. return [self databaseQueueWithPath:url.path];
  44. }
  45. + (instancetype)databaseQueueWithPath:(NSString *)aPath flags:(int)openFlags {
  46. FMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags];
  47. FMDBAutorelease(q);
  48. return q;
  49. }
  50. + (instancetype)databaseQueueWithURL:(NSURL *)url flags:(int)openFlags {
  51. return [self databaseQueueWithPath:url.path flags:openFlags];
  52. }
  53. + (Class)databaseClass {
  54. return [FMDatabase class];
  55. }
  56. - (instancetype)initWithURL:(NSURL *)url flags:(int)openFlags vfs:(NSString *)vfsName {
  57. return [self initWithPath:url.path flags:openFlags vfs:vfsName];
  58. }
  59. - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
  60. self = [super init];
  61. if (self != nil) {
  62. _db = [[[self class] databaseClass] databaseWithPath:aPath];
  63. FMDBRetain(_db);
  64. #if SQLITE_VERSION_NUMBER >= 3005000
  65. BOOL success = [_db openWithFlags:openFlags vfs:vfsName];
  66. #else
  67. BOOL success = [_db open];
  68. #endif
  69. if (!success) {
  70. NSLog(@"Could not create database queue for path %@", aPath);
  71. FMDBRelease(self);
  72. return 0x00;
  73. }
  74. _path = FMDBReturnRetained(aPath);
  75. _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
  76. dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
  77. _openFlags = openFlags;
  78. _vfsName = [vfsName copy];
  79. }
  80. return self;
  81. }
  82. - (instancetype)initWithPath:(NSString *)aPath flags:(int)openFlags {
  83. return [self initWithPath:aPath flags:openFlags vfs:nil];
  84. }
  85. - (instancetype)initWithURL:(NSURL *)url flags:(int)openFlags {
  86. return [self initWithPath:url.path flags:openFlags vfs:nil];
  87. }
  88. - (instancetype)initWithURL:(NSURL *)url {
  89. return [self initWithPath:url.path];
  90. }
  91. - (instancetype)initWithPath:(NSString *)aPath {
  92. // default flags for sqlite3_open
  93. return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:nil];
  94. }
  95. - (instancetype)init {
  96. return [self initWithPath:nil];
  97. }
  98. - (void)dealloc {
  99. FMDBRelease(_db);
  100. FMDBRelease(_path);
  101. FMDBRelease(_vfsName);
  102. if (_queue) {
  103. FMDBDispatchQueueRelease(_queue);
  104. _queue = 0x00;
  105. }
  106. #if ! __has_feature(objc_arc)
  107. [super dealloc];
  108. #endif
  109. }
  110. - (void)close {
  111. FMDBRetain(self);
  112. dispatch_sync(_queue, ^() {
  113. [self->_db close];
  114. FMDBRelease(_db);
  115. self->_db = 0x00;
  116. });
  117. FMDBRelease(self);
  118. }
  119. - (void)interrupt {
  120. [[self database] interrupt];
  121. }
  122. - (FMDatabase*)database {
  123. if (![_db isOpen]) {
  124. if (!_db) {
  125. _db = FMDBReturnRetained([[[self class] databaseClass] databaseWithPath:_path]);
  126. }
  127. #if SQLITE_VERSION_NUMBER >= 3005000
  128. BOOL success = [_db openWithFlags:_openFlags vfs:_vfsName];
  129. #else
  130. BOOL success = [_db open];
  131. #endif
  132. if (!success) {
  133. NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path);
  134. FMDBRelease(_db);
  135. _db = 0x00;
  136. return 0x00;
  137. }
  138. }
  139. return _db;
  140. }
  141. - (void)inDatabase:(__attribute__((noescape)) void (^)(FMDatabase *db))block {
  142. #ifndef NDEBUG
  143. /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
  144. * and then check it against self to make sure we're not about to deadlock. */
  145. FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
  146. assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
  147. #endif
  148. FMDBRetain(self);
  149. dispatch_sync(_queue, ^() {
  150. FMDatabase *db = [self database];
  151. block(db);
  152. if ([db hasOpenResultSets]) {
  153. NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
  154. #if defined(DEBUG) && DEBUG
  155. NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
  156. for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
  157. FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
  158. NSLog(@"query: '%@'", [rs query]);
  159. }
  160. #endif
  161. }
  162. });
  163. FMDBRelease(self);
  164. }
  165. - (void)beginTransaction:(FMDBTransaction)transaction withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
  166. FMDBRetain(self);
  167. dispatch_sync(_queue, ^() {
  168. BOOL shouldRollback = NO;
  169. switch (transaction) {
  170. case FMDBTransactionExclusive:
  171. [[self database] beginTransaction];
  172. break;
  173. case FMDBTransactionDeferred:
  174. [[self database] beginDeferredTransaction];
  175. break;
  176. case FMDBTransactionImmediate:
  177. [[self database] beginImmediateTransaction];
  178. break;
  179. }
  180. block([self database], &shouldRollback);
  181. if (shouldRollback) {
  182. [[self database] rollback];
  183. }
  184. else {
  185. [[self database] commit];
  186. }
  187. });
  188. FMDBRelease(self);
  189. }
  190. - (void)inTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block {
  191. [self beginTransaction:FMDBTransactionExclusive withBlock:block];
  192. }
  193. - (void)inDeferredTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block {
  194. [self beginTransaction:FMDBTransactionDeferred withBlock:block];
  195. }
  196. - (void)inExclusiveTransaction:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block {
  197. [self beginTransaction:FMDBTransactionExclusive withBlock:block];
  198. }
  199. - (void)inImmediateTransaction:(__attribute__((noescape)) void (^)(FMDatabase * _Nonnull, BOOL * _Nonnull))block {
  200. [self beginTransaction:FMDBTransactionImmediate withBlock:block];
  201. }
  202. - (NSError*)inSavePoint:(__attribute__((noescape)) void (^)(FMDatabase *db, BOOL *rollback))block {
  203. #if SQLITE_VERSION_NUMBER >= 3007000
  204. static unsigned long savePointIdx = 0;
  205. __block NSError *err = 0x00;
  206. FMDBRetain(self);
  207. dispatch_sync(_queue, ^() {
  208. NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];
  209. BOOL shouldRollback = NO;
  210. if ([[self database] startSavePointWithName:name error:&err]) {
  211. block([self database], &shouldRollback);
  212. if (shouldRollback) {
  213. // We need to rollback and release this savepoint to remove it
  214. [[self database] rollbackToSavePointWithName:name error:&err];
  215. }
  216. [[self database] releaseSavePointWithName:name error:&err];
  217. }
  218. });
  219. FMDBRelease(self);
  220. return err;
  221. #else
  222. NSString *errorMessage = NSLocalizedStringFromTable(@"Save point functions require SQLite 3.7", @"FMDB", nil);
  223. if (_db.logsErrors) NSLog(@"%@", errorMessage);
  224. return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
  225. #endif
  226. }
  227. - (BOOL)checkpoint:(FMDBCheckpointMode)mode error:(NSError * __autoreleasing *)error
  228. {
  229. return [self checkpoint:mode name:nil logFrameCount:NULL checkpointCount:NULL error:error];
  230. }
  231. - (BOOL)checkpoint:(FMDBCheckpointMode)mode name:(NSString *)name error:(NSError * __autoreleasing *)error
  232. {
  233. return [self checkpoint:mode name:name logFrameCount:NULL checkpointCount:NULL error:error];
  234. }
  235. - (BOOL)checkpoint:(FMDBCheckpointMode)mode name:(NSString *)name logFrameCount:(int * _Nullable)logFrameCount checkpointCount:(int * _Nullable)checkpointCount error:(NSError * __autoreleasing _Nullable * _Nullable)error
  236. {
  237. __block BOOL result;
  238. __block NSError *blockError;
  239. FMDBRetain(self);
  240. dispatch_sync(_queue, ^() {
  241. result = [self.database checkpoint:mode name:name logFrameCount:NULL checkpointCount:NULL error:&blockError];
  242. });
  243. FMDBRelease(self);
  244. if (error) {
  245. *error = blockError;
  246. }
  247. return result;
  248. }
  249. @end