FMDatabaseQueueTests.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. //
  2. // FMDatabaseQueueTests.m
  3. // fmdb
  4. //
  5. // Created by Graham Dennis on 24/11/2013.
  6. //
  7. //
  8. #import <XCTest/XCTest.h>
  9. #import "FMDatabaseQueue.h"
  10. #if FMDB_SQLITE_STANDALONE
  11. #import <sqlite3/sqlite3.h>
  12. #else
  13. #import <sqlite3.h>
  14. #endif
  15. @interface FMDatabaseQueueTests : FMDBTempDBTests
  16. @property FMDatabaseQueue *queue;
  17. @end
  18. @implementation FMDatabaseQueueTests
  19. + (void)populateDatabase:(FMDatabase *)db
  20. {
  21. [db executeUpdate:@"create table easy (a text)"];
  22. [db executeUpdate:@"create table qfoo (foo text)"];
  23. [db executeUpdate:@"insert into qfoo values ('hi')"];
  24. [db executeUpdate:@"insert into qfoo values ('hello')"];
  25. [db executeUpdate:@"insert into qfoo values ('not')"];
  26. }
  27. - (void)setUp
  28. {
  29. [super setUp];
  30. // Put setup code here. This method is called before the invocation of each test method in the class.
  31. self.queue = [FMDatabaseQueue databaseQueueWithPath:self.databasePath];
  32. }
  33. - (void)tearDown
  34. {
  35. // Put teardown code here. This method is called after the invocation of each test method in the class.
  36. [super tearDown];
  37. }
  38. - (void)testURLOpenNoPath {
  39. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] init];
  40. XCTAssert(queue, @"Database queue should be returned");
  41. queue = nil;
  42. }
  43. - (void)testURLOpenNoURL {
  44. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:nil];
  45. XCTAssert(queue, @"Database queue should be returned");
  46. queue = nil;
  47. }
  48. - (void)testInvalidURL {
  49. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  50. NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  51. NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
  52. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
  53. XCTAssertNil(queue, @"Database queue should not be returned for invalid path");
  54. queue = nil;
  55. }
  56. - (void)testInvalidPath {
  57. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  58. NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  59. NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
  60. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithPath:fileURL.path];
  61. XCTAssertNil(queue, @"Database queue should not be returned for invalid path");
  62. queue = nil;
  63. }
  64. - (void)testReopenFailure {
  65. NSFileManager *manager = [NSFileManager defaultManager];
  66. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  67. NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  68. BOOL success = [manager createDirectoryAtURL:folderURL withIntermediateDirectories:true attributes:nil error:nil];
  69. NSAssert(success, @"Unable to create folder");
  70. NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
  71. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
  72. XCTAssert(queue, @"Database queue was unable to be created");
  73. [queue close];
  74. success = [manager removeItemAtURL:fileURL error:nil];
  75. XCTAssert(success, @"Unable to remove database");
  76. success = [manager removeItemAtURL:folderURL error:nil];
  77. XCTAssert(success, @"Unable to remove folder");
  78. [queue inDatabase:^(FMDatabase *db) {
  79. XCTAssertNil(db, @"Should be `nil` or never have reached here because database couldn't be reopened");
  80. }];
  81. queue = nil;
  82. }
  83. - (void)testURLOpen {
  84. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  85. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  86. FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithURL:fileURL];
  87. XCTAssert(queue, @"Database queue should be returned");
  88. queue = nil;
  89. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  90. }
  91. - (void)testURLOpenInit {
  92. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  93. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  94. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
  95. XCTAssert(queue, @"Database queue should be returned");
  96. queue = nil;
  97. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  98. }
  99. - (void)testURLOpenWithOptions {
  100. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  101. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  102. FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
  103. XCTAssertNil(queue, @"Database queue should not have been created");
  104. }
  105. - (void)testURLOpenInitWithOptions {
  106. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  107. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  108. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
  109. XCTAssertNil(queue, @"Database queue should not have been created");
  110. queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE];
  111. XCTAssert(queue, @"Database queue should have been created");
  112. [queue inDatabase:^(FMDatabase * _Nonnull db) {
  113. BOOL success = [db executeUpdate:@"CREATE TABLE foo (bar INT)"];
  114. XCTAssert(success, @"Create failed");
  115. success = [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", @42];
  116. XCTAssert(success, @"Insert failed");
  117. }];
  118. queue = nil;
  119. queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READONLY];
  120. XCTAssert(queue, @"Now database queue should open have been created");
  121. [queue inDatabase:^(FMDatabase * _Nonnull db) {
  122. BOOL success = [db executeUpdate:@"CREATE TABLE baz (qux INT)"];
  123. XCTAssertFalse(success, @"But updates should fail on read only database");
  124. }];
  125. queue = nil;
  126. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  127. }
  128. - (void)testURLOpenWithOptionsVfs {
  129. sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
  130. vfs.zName = "MyCustomVFS";
  131. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
  132. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  133. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  134. FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
  135. XCTAssert(queue, @"Database queue should not have been created");
  136. queue = nil;
  137. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_unregister(&vfs));
  138. }
  139. - (void)testQueueSelect
  140. {
  141. [self.queue inDatabase:^(FMDatabase *adb) {
  142. int count = 0;
  143. FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
  144. while ([rsl next]) {
  145. count++;
  146. }
  147. XCTAssertEqual(count, 2);
  148. count = 0;
  149. rsl = [adb executeQuery:@"select * from qfoo where foo like ?", @"h%"];
  150. while ([rsl next]) {
  151. count++;
  152. }
  153. XCTAssertEqual(count, 2);
  154. }];
  155. }
  156. - (void)testReadOnlyQueue
  157. {
  158. FMDatabaseQueue *queue2 = [FMDatabaseQueue databaseQueueWithPath:self.databasePath flags:SQLITE_OPEN_READONLY];
  159. XCTAssertNotNil(queue2);
  160. {
  161. [queue2 inDatabase:^(FMDatabase *db2) {
  162. FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"];
  163. XCTAssertNotNil(rs1);
  164. [rs1 close];
  165. XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database");
  166. }];
  167. [queue2 close];
  168. // Check that when we re-open the database, it's still read-only
  169. [queue2 inDatabase:^(FMDatabase *db2) {
  170. FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"];
  171. XCTAssertNotNil(rs1);
  172. [rs1 close];
  173. XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database");
  174. }];
  175. }
  176. }
  177. - (void)testStressTest
  178. {
  179. size_t ops = 16;
  180. dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  181. dispatch_apply(ops, dqueue, ^(size_t nby) {
  182. // just mix things up a bit for demonstration purposes.
  183. if (nby % 2 == 1) {
  184. [NSThread sleepForTimeInterval:.01];
  185. [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  186. FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
  187. while ([rsl next]) {
  188. ;// whatever.
  189. }
  190. }];
  191. }
  192. if (nby % 3 == 1) {
  193. [NSThread sleepForTimeInterval:.01];
  194. }
  195. [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  196. XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]);
  197. XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('2')"]);
  198. XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('3')"]);
  199. }];
  200. });
  201. [self.queue close];
  202. [self.queue inDatabase:^(FMDatabase *adb) {
  203. XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]);
  204. }];
  205. }
  206. - (void)testTransaction
  207. {
  208. [self.queue inDatabase:^(FMDatabase *adb) {
  209. [adb executeUpdate:@"create table transtest (a integer)"];
  210. XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]);
  211. XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]);
  212. int rowCount = 0;
  213. FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
  214. while ([ars next]) {
  215. rowCount++;
  216. }
  217. XCTAssertEqual(rowCount, 2);
  218. }];
  219. [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  220. XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]);
  221. if (YES) {
  222. // uh oh!, something went wrong (not really, this is just a test
  223. *rollback = YES;
  224. return;
  225. }
  226. XCTFail(@"This shouldn't be reached");
  227. }];
  228. [self.queue inDatabase:^(FMDatabase *adb) {
  229. int rowCount = 0;
  230. FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
  231. while ([ars next]) {
  232. rowCount++;
  233. }
  234. XCTAssertFalse([adb hasOpenResultSets]);
  235. XCTAssertEqual(rowCount, 2);
  236. }];
  237. }
  238. - (void)testSavePoint
  239. {
  240. [self.queue inDatabase:^(FMDatabase *adb) {
  241. [adb executeUpdate:@"create table transtest (a integer)"];
  242. XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]);
  243. XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]);
  244. int rowCount = 0;
  245. FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
  246. while ([ars next]) {
  247. rowCount++;
  248. }
  249. XCTAssertEqual(rowCount, 2);
  250. }];
  251. [self.queue inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
  252. XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]);
  253. if (YES) {
  254. // uh oh!, something went wrong (not really, this is just a test
  255. *rollback = YES;
  256. return;
  257. }
  258. XCTFail(@"This shouldn't be reached");
  259. }];
  260. [self.queue inDatabase:^(FMDatabase *adb) {
  261. int rowCount = 0;
  262. FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
  263. while ([ars next]) {
  264. rowCount++;
  265. }
  266. XCTAssertFalse([adb hasOpenResultSets]);
  267. XCTAssertEqual(rowCount, 2);
  268. }];
  269. }
  270. - (void)testClose
  271. {
  272. [self.queue inDatabase:^(FMDatabase *adb) {
  273. XCTAssertTrue([adb executeUpdate:@"CREATE TABLE close_test (a INTEGER)"]);
  274. XCTAssertTrue([adb executeUpdate:@"INSERT INTO close_test VALUES (1)"]);
  275. [adb close];
  276. }];
  277. [self.queue inDatabase:^(FMDatabase *adb) {
  278. FMResultSet *ars = [adb executeQuery:@"select * from close_test"];
  279. XCTAssertNotNil(ars);
  280. }];
  281. }
  282. @end