FMDatabasePoolTests.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. //
  2. // FMDatabasePoolTests.m
  3. // fmdb
  4. //
  5. // Created by Graham Dennis on 24/11/2013.
  6. //
  7. //
  8. #import <XCTest/XCTest.h>
  9. #if FMDB_SQLITE_STANDALONE
  10. #import <sqlite3/sqlite3.h>
  11. #else
  12. #import <sqlite3.h>
  13. #endif
  14. @interface FMDatabasePoolTests : FMDBTempDBTests
  15. @property FMDatabasePool *pool;
  16. @end
  17. @implementation FMDatabasePoolTests
  18. + (void)populateDatabase:(FMDatabase *)db
  19. {
  20. [db executeUpdate:@"create table easy (a text)"];
  21. [db executeUpdate:@"create table easy2 (a text)"];
  22. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]];
  23. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]];
  24. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]];
  25. [db executeUpdate:@"create table likefoo (foo text)"];
  26. [db executeUpdate:@"insert into likefoo values ('hi')"];
  27. [db executeUpdate:@"insert into likefoo values ('hello')"];
  28. [db executeUpdate:@"insert into likefoo values ('not')"];
  29. }
  30. - (void)setUp {
  31. [super setUp];
  32. // Put setup code here. This method is called before the invocation of each test method in the class.
  33. [self setPool:[FMDatabasePool databasePoolWithPath:self.databasePath]];
  34. [[self pool] setDelegate:self];
  35. }
  36. - (void)tearDown {
  37. // Put teardown code here. This method is called after the invocation of each test method in the class.
  38. [super tearDown];
  39. }
  40. - (void)testURLOpenNoURL {
  41. FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:nil];
  42. XCTAssert(pool, @"Database pool should be returned");
  43. pool = nil;
  44. }
  45. - (void)testURLOpen {
  46. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  47. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  48. FMDatabasePool *pool = [FMDatabasePool databasePoolWithURL:fileURL];
  49. XCTAssert(pool, @"Database pool should be returned");
  50. pool = nil;
  51. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  52. }
  53. - (void)testURLOpenInit {
  54. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  55. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  56. FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:fileURL];
  57. XCTAssert(pool, @"Database pool should be returned");
  58. pool = nil;
  59. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  60. }
  61. - (void)testURLOpenWithOptions {
  62. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  63. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  64. FMDatabasePool *pool = [FMDatabasePool databasePoolWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
  65. [pool inDatabase:^(FMDatabase * _Nonnull db) {
  66. XCTAssertNil(db, @"The database should not have been created");
  67. }];
  68. }
  69. - (void)testURLOpenInitWithOptions {
  70. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  71. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  72. FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
  73. [pool inDatabase:^(FMDatabase * _Nonnull db) {
  74. XCTAssertNil(db, @"The database should not have been created");
  75. }];
  76. pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE];
  77. [pool inDatabase:^(FMDatabase * _Nonnull db) {
  78. XCTAssert(db, @"The database should have been created");
  79. BOOL success = [db executeUpdate:@"CREATE TABLE foo (bar INT)"];
  80. XCTAssert(success, @"Create failed");
  81. success = [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", @42];
  82. XCTAssert(success, @"Insert failed");
  83. }];
  84. pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READONLY];
  85. [pool inDatabase:^(FMDatabase * _Nonnull db) {
  86. XCTAssert(db, @"Now database pool should open have been created");
  87. BOOL success = [db executeUpdate:@"CREATE TABLE baz (qux INT)"];
  88. XCTAssertFalse(success, @"But updates should fail on read only database");
  89. }];
  90. pool = nil;
  91. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  92. }
  93. - (void)testURLOpenWithOptionsVfs {
  94. sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
  95. vfs.zName = "MyCustomVFS";
  96. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
  97. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  98. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  99. FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
  100. XCTAssert(pool, @"Database pool should not have been created");
  101. pool = nil;
  102. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_unregister(&vfs));
  103. }
  104. - (void)testPoolIsInitiallyEmpty {
  105. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"Pool should be empty on creation");
  106. }
  107. - (void)testDatabaseCreation {
  108. __block FMDatabase *db1;
  109. [self.pool inDatabase:^(FMDatabase *db) {
  110. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1, @"Should only have one database at this point");
  111. db1 = db;
  112. }];
  113. [self.pool inDatabase:^(FMDatabase *db) {
  114. XCTAssertEqualObjects(db, db1, @"We should get the same database back because there was no need to create a new one");
  115. [self.pool inDatabase:^(FMDatabase *db2) {
  116. XCTAssertNotEqualObjects(db2, db, @"We should get a different database because the first was in use.");
  117. }];
  118. }];
  119. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2);
  120. [self.pool releaseAllDatabases];
  121. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"We should be back to zero databases again");
  122. }
  123. - (void)testCheckedInCheckoutOutCount
  124. {
  125. [self.pool inDatabase:^(FMDatabase *aDb) {
  126. XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0);
  127. XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1);
  128. XCTAssertTrue(([aDb executeUpdate:@"insert into easy (a) values (?)", @"hi"]));
  129. // just for fun.
  130. FMResultSet *rs = [aDb executeQuery:@"select * from easy"];
  131. XCTAssertNotNil(rs);
  132. XCTAssertTrue([rs next]);
  133. while ([rs next]) { ; } // whatevers.
  134. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
  135. XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0);
  136. XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1);
  137. }];
  138. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
  139. }
  140. - (void)testMaximumDatabaseLimit
  141. {
  142. [self.pool setMaximumNumberOfDatabasesToCreate:2];
  143. [self.pool inDatabase:^(FMDatabase *db) {
  144. [self.pool inDatabase:^(FMDatabase *db2) {
  145. [self.pool inDatabase:^(FMDatabase *db3) {
  146. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2);
  147. XCTAssertNil(db3, @"The third database must be nil because we have a maximum of 2 databases in the pool");
  148. }];
  149. }];
  150. }];
  151. }
  152. - (void)testTransaction
  153. {
  154. [self.pool inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  155. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]];
  156. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]];
  157. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]];
  158. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
  159. XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0);
  160. XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1);
  161. }];
  162. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
  163. XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)1);
  164. XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)0);
  165. }
  166. - (void)testSelect
  167. {
  168. [self.pool inDatabase:^(FMDatabase *db) {
  169. FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1001]];
  170. XCTAssertNotNil(rs);
  171. XCTAssertTrue ([rs next]);
  172. XCTAssertFalse([rs next]);
  173. }];
  174. }
  175. - (void)testTransactionRollback
  176. {
  177. [self.pool inDeferredTransaction:^(FMDatabase *adb, BOOL *rollback) {
  178. XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1004]]));
  179. XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1005]]));
  180. XCTAssertTrue([[adb executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should be in database");
  181. *rollback = YES;
  182. }];
  183. [self.pool inDatabase:^(FMDatabase *db) {
  184. XCTAssertFalse([[db executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should not be in database");
  185. }];
  186. XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
  187. XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)1);
  188. XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)0);
  189. }
  190. - (void)testSavepoint
  191. {
  192. NSError *err = [self.pool inSavePoint:^(FMDatabase *db, BOOL *rollback) {
  193. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1006]];
  194. }];
  195. XCTAssertNil(err);
  196. }
  197. - (void)testNestedSavepointRollback
  198. {
  199. NSError *err = [self.pool inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
  200. XCTAssertFalse([adb hadError]);
  201. XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1009]]));
  202. [adb inSavePoint:^(BOOL *arollback) {
  203. XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1010]]));
  204. *arollback = YES;
  205. }];
  206. }];
  207. XCTAssertNil(err);
  208. [self.pool inDatabase:^(FMDatabase *db) {
  209. FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1009]];
  210. XCTAssertTrue ([rs next]);
  211. XCTAssertFalse([rs next]); // close it out.
  212. rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1010]];
  213. XCTAssertFalse([rs next]);
  214. }];
  215. }
  216. - (void)testLikeStringQuery
  217. {
  218. [self.pool inDatabase:^(FMDatabase *db) {
  219. int count = 0;
  220. FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
  221. while ([rsl next]) {
  222. count++;
  223. }
  224. XCTAssertEqual(count, 2);
  225. count = 0;
  226. rsl = [db executeQuery:@"select * from likefoo where foo like ?", @"h%"];
  227. while ([rsl next]) {
  228. count++;
  229. }
  230. XCTAssertEqual(count, 2);
  231. }];
  232. }
  233. - (void)testStressTest
  234. {
  235. size_t ops = 128;
  236. dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  237. dispatch_apply(ops, dqueue, ^(size_t nby) {
  238. // just mix things up a bit for demonstration purposes.
  239. if (nby % 2 == 1) {
  240. [NSThread sleepForTimeInterval:.001];
  241. }
  242. [self.pool inDatabase:^(FMDatabase *db) {
  243. FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
  244. XCTAssertNotNil(rsl);
  245. int i = 0;
  246. while ([rsl next]) {
  247. i++;
  248. if (nby % 3 == 1) {
  249. [NSThread sleepForTimeInterval:.0005];
  250. }
  251. }
  252. XCTAssertEqual(i, 2);
  253. }];
  254. });
  255. XCTAssert([self.pool countOfOpenDatabases] < 64, @"There should be significantly less than 64 databases after that stress test");
  256. }
  257. - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database {
  258. [database setMaxBusyRetryTimeInterval:10];
  259. // [database setCrashOnErrors:YES];
  260. return YES;
  261. }
  262. - (void)testReadWriteStressTest
  263. {
  264. int ops = 16;
  265. dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  266. dispatch_apply(ops, dqueue, ^(size_t nby) {
  267. // just mix things up a bit for demonstration purposes.
  268. if (nby % 2 == 1) {
  269. [NSThread sleepForTimeInterval:.01];
  270. [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) {
  271. FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
  272. XCTAssertNotNil(rsl);
  273. while ([rsl next]) {
  274. ;// whatever.
  275. }
  276. }];
  277. }
  278. if (nby % 3 == 1) {
  279. [NSThread sleepForTimeInterval:.01];
  280. }
  281. [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) {
  282. XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]);
  283. XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('2')"]);
  284. XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('3')"]);
  285. }];
  286. });
  287. [self.pool releaseAllDatabases];
  288. [self.pool inDatabase:^(FMDatabase *db) {
  289. XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]);
  290. }];
  291. }
  292. @end