FMDatabaseQueue.m 6.3 KB

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