FMDatabaseQueue.m 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. /*
  16. Note: we call [self retain]; before using dispatch_sync, just incase
  17. FMDatabaseQueue is released on another thread and we're in the middle of doing
  18. something in dispatch_sync
  19. */
  20. /*
  21. * A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses.
  22. * This in turn is used for deadlock detection by seeing if inDatabase: is called on
  23. * the queue's dispatch queue, which should not happen and causes a deadlock.
  24. */
  25. static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
  26. @implementation FMDatabaseQueue
  27. @synthesize path = _path;
  28. @synthesize openFlags = _openFlags;
  29. @synthesize vfsName = _vfsName;
  30. + (instancetype)databaseQueueWithPath:(NSString*)aPath {
  31. FMDatabaseQueue *q = [[self alloc] initWithPath:aPath];
  32. FMDBAutorelease(q);
  33. return q;
  34. }
  35. + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags {
  36. FMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags];
  37. FMDBAutorelease(q);
  38. return q;
  39. }
  40. + (Class)databaseClass {
  41. return [FMDatabase class];
  42. }
  43. - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName {
  44. self = [super init];
  45. if (self != nil) {
  46. _db = [[[self class] databaseClass] databaseWithPath:aPath];
  47. FMDBRetain(_db);
  48. #if SQLITE_VERSION_NUMBER >= 3005000
  49. BOOL success = [_db openWithFlags:openFlags vfs:vfsName];
  50. #else
  51. BOOL success = [_db open];
  52. #endif
  53. if (!success) {
  54. NSLog(@"Could not create database queue for path %@", aPath);
  55. FMDBRelease(self);
  56. return 0x00;
  57. }
  58. _path = FMDBReturnRetained(aPath);
  59. _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
  60. dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
  61. _openFlags = openFlags;
  62. _vfsName = [vfsName copy];
  63. }
  64. return self;
  65. }
  66. - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags {
  67. return [self initWithPath:aPath flags:openFlags vfs:nil];
  68. }
  69. - (instancetype)initWithPath:(NSString*)aPath {
  70. // default flags for sqlite3_open
  71. return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:nil];
  72. }
  73. - (instancetype)init {
  74. return [self initWithPath:nil];
  75. }
  76. - (void)dealloc {
  77. FMDBRelease(_db);
  78. FMDBRelease(_path);
  79. if (_queue) {
  80. FMDBDispatchQueueRelease(_queue);
  81. _queue = 0x00;
  82. }
  83. #if ! __has_feature(objc_arc)
  84. [super dealloc];
  85. #endif
  86. }
  87. - (void)close {
  88. FMDBRetain(self);
  89. dispatch_sync(_queue, ^() {
  90. [self->_db close];
  91. FMDBRelease(_db);
  92. self->_db = 0x00;
  93. });
  94. FMDBRelease(self);
  95. }
  96. - (void)interrupt
  97. {
  98. [[self database] interrupt];
  99. }
  100. - (FMDatabase*)database {
  101. if (!_db) {
  102. _db = FMDBReturnRetained([[[self class] databaseClass] databaseWithPath:_path]);
  103. #if SQLITE_VERSION_NUMBER >= 3005000
  104. BOOL success = [_db openWithFlags:_openFlags vfs:_vfsName];
  105. #else
  106. BOOL success = [_db open];
  107. #endif
  108. if (!success) {
  109. NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path);
  110. FMDBRelease(_db);
  111. _db = 0x00;
  112. return 0x00;
  113. }
  114. }
  115. return _db;
  116. }
  117. - (void)inDatabase:(void (^)(FMDatabase *db))block {
  118. /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
  119. * and then check it against self to make sure we're not about to deadlock. */
  120. FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
  121. assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
  122. FMDBRetain(self);
  123. dispatch_sync(_queue, ^() {
  124. FMDatabase *db = [self database];
  125. block(db);
  126. if ([db hasOpenResultSets]) {
  127. NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
  128. #if defined(DEBUG) && DEBUG
  129. NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
  130. for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
  131. FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
  132. NSLog(@"query: '%@'", [rs query]);
  133. }
  134. #endif
  135. }
  136. });
  137. FMDBRelease(self);
  138. }
  139. - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
  140. FMDBRetain(self);
  141. dispatch_sync(_queue, ^() {
  142. BOOL shouldRollback = NO;
  143. if (useDeferred) {
  144. [[self database] beginDeferredTransaction];
  145. }
  146. else {
  147. [[self database] beginTransaction];
  148. }
  149. block([self database], &shouldRollback);
  150. if (shouldRollback) {
  151. [[self database] rollback];
  152. }
  153. else {
  154. [[self database] commit];
  155. }
  156. });
  157. FMDBRelease(self);
  158. }
  159. - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
  160. [self beginTransaction:YES withBlock:block];
  161. }
  162. - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
  163. [self beginTransaction:NO withBlock:block];
  164. }
  165. - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block {
  166. #if SQLITE_VERSION_NUMBER >= 3007000
  167. static unsigned long savePointIdx = 0;
  168. __block NSError *err = 0x00;
  169. FMDBRetain(self);
  170. dispatch_sync(_queue, ^() {
  171. NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];
  172. BOOL shouldRollback = NO;
  173. if ([[self database] startSavePointWithName:name error:&err]) {
  174. block([self database], &shouldRollback);
  175. if (shouldRollback) {
  176. // We need to rollback and release this savepoint to remove it
  177. [[self database] rollbackToSavePointWithName:name error:&err];
  178. }
  179. [[self database] releaseSavePointWithName:name error:&err];
  180. }
  181. });
  182. FMDBRelease(self);
  183. return err;
  184. #else
  185. NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil);
  186. if (self.logsErrors) NSLog(@"%@", errorMessage);
  187. return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
  188. #endif
  189. }
  190. @end