FMDatabaseQueue.m 7.4 KB

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