PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/io/fmdb.m

https://gitlab.com/base.io/io
Objective C | 1258 lines | 754 code | 438 blank | 66 comment | 140 complexity | fa403a8d338df1c5a3db679aaa29009e MD5 | raw file
  1. #import <Foundation/Foundation.h>
  2. #import "FMDatabase.h"
  3. #import "FMDatabaseAdditions.h"
  4. #import "FMDatabasePool.h"
  5. #import "FMDatabaseQueue.h"
  6. #define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) { NSLog(@"Failure on line %d", __LINE__); abort(); } }
  7. void testPool(NSString *dbPath);
  8. void FMDBReportABugFunction();
  9. int main (int argc, const char * argv[]) {
  10. @autoreleasepool {
  11. FMDBReportABugFunction();
  12. NSString *dbPath = @"/tmp/tmp.db";
  13. // delete the old db.
  14. NSFileManager *fileManager = [NSFileManager defaultManager];
  15. [fileManager removeItemAtPath:dbPath error:nil];
  16. FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
  17. NSLog(@"Is SQLite compiled with it's thread safe options turned on? %@!", [FMDatabase isSQLiteThreadSafe] ? @"Yes" : @"No");
  18. {
  19. // -------------------------------------------------------------------------------
  20. // Un-opened database check.
  21. FMDBQuickCheck([db executeQuery:@"select * from table"] == nil);
  22. NSLog(@"%d: %@", [db lastErrorCode], [db lastErrorMessage]);
  23. }
  24. if (![db open]) {
  25. NSLog(@"Could not open db.");
  26. return 0;
  27. }
  28. // kind of experimentalish.
  29. [db setShouldCacheStatements:YES];
  30. // create a bad statement, just to test the error code.
  31. [db executeUpdate:@"blah blah blah"];
  32. FMDBQuickCheck([db hadError]);
  33. if ([db hadError]) {
  34. NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
  35. }
  36. NSError *err = 0x00;
  37. FMDBQuickCheck(![db update:@"blah blah blah" withErrorAndBindings:&err]);
  38. FMDBQuickCheck(err != nil);
  39. FMDBQuickCheck([err code] == SQLITE_ERROR);
  40. NSLog(@"err: '%@'", err);
  41. // empty strings should still return a value.
  42. FMDBQuickCheck(([db boolForQuery:@"SELECT ? not null", @""]));
  43. // same with empty bits o' mutable data
  44. FMDBQuickCheck(([db boolForQuery:@"SELECT ? not null", [NSMutableData data]]));
  45. // same with empty bits o' data
  46. FMDBQuickCheck(([db boolForQuery:@"SELECT ? not null", [NSData data]]));
  47. // how do we do pragmas? Like so:
  48. FMResultSet *ps = [db executeQuery:@"PRAGMA journal_mode=delete"];
  49. FMDBQuickCheck(![db hadError]);
  50. FMDBQuickCheck(ps);
  51. FMDBQuickCheck([ps next]);
  52. [ps close];
  53. // oh, but some pragmas require updates?
  54. [db executeUpdate:@"PRAGMA page_size=2048"];
  55. FMDBQuickCheck(![db hadError]);
  56. // what about a vacuum?
  57. [db executeUpdate:@"vacuum"];
  58. FMDBQuickCheck(![db hadError]);
  59. exit(0);
  60. // but of course, I don't bother checking the error codes below.
  61. // Bad programmer, no cookie.
  62. [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"];
  63. [db beginTransaction];
  64. int i = 0;
  65. while (i++ < 20) {
  66. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  67. @"hi'", // look! I put in a ', and I'm not escaping it!
  68. [NSString stringWithFormat:@"number %d", i],
  69. [NSNumber numberWithInt:i],
  70. [NSDate date],
  71. [NSNumber numberWithFloat:2.2f]];
  72. }
  73. [db commit];
  74. // do it again, just because
  75. [db beginTransaction];
  76. i = 0;
  77. while (i++ < 20) {
  78. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  79. @"hi again'", // look! I put in a ', and I'm not escaping it!
  80. [NSString stringWithFormat:@"number %d", i],
  81. [NSNumber numberWithInt:i],
  82. [NSDate date],
  83. [NSNumber numberWithFloat:2.2f]];
  84. }
  85. [db commit];
  86. FMResultSet *rs = [db executeQuery:@"select rowid,* from test where a = ?", @"hi'"];
  87. while ([rs next]) {
  88. // just print out what we've got in a number of formats.
  89. NSLog(@"%d %@ %@ %@ %@ %f %f",
  90. [rs intForColumn:@"c"],
  91. [rs stringForColumn:@"b"],
  92. [rs stringForColumn:@"a"],
  93. [rs stringForColumn:@"rowid"],
  94. [rs dateForColumn:@"d"],
  95. [rs doubleForColumn:@"d"],
  96. [rs doubleForColumn:@"e"]);
  97. if (!([[rs columnNameForIndex:0] isEqualToString:@"rowid"] &&
  98. [[rs columnNameForIndex:1] isEqualToString:@"a"])
  99. ) {
  100. NSLog(@"WHOA THERE BUDDY, columnNameForIndex ISN'T WORKING!");
  101. return 7;
  102. }
  103. }
  104. // close the result set.
  105. // it'll also close when it's dealloc'd, but we're closing the database before
  106. // the autorelease pool closes, so sqlite will complain about it.
  107. [rs close];
  108. FMDBQuickCheck(![db hasOpenResultSets]);
  109. [db executeUpdate:@"create table ull (a integer)"];
  110. [db executeUpdate:@"insert into ull (a) values (?)" , [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]];
  111. rs = [db executeQuery:@"select a from ull"];
  112. while ([rs next]) {
  113. unsigned long long a = [rs unsignedLongLongIntForColumnIndex:0];
  114. unsigned long long b = [rs unsignedLongLongIntForColumn:@"a"];
  115. FMDBQuickCheck(a == ULLONG_MAX);
  116. FMDBQuickCheck(b == ULLONG_MAX);
  117. }
  118. // check case sensitive result dictionary.
  119. [db executeUpdate:@"create table cs (aRowName integer, bRowName text)"];
  120. FMDBQuickCheck(![db hadError]);
  121. [db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)" , [NSNumber numberWithBool:1], @"hello"];
  122. FMDBQuickCheck(![db hadError]);
  123. rs = [db executeQuery:@"select * from cs"];
  124. while ([rs next]) {
  125. NSDictionary *d = [rs resultDictionary];
  126. FMDBQuickCheck([d objectForKey:@"aRowName"]);
  127. FMDBQuickCheck(![d objectForKey:@"arowname"]);
  128. FMDBQuickCheck([d objectForKey:@"bRowName"]);
  129. FMDBQuickCheck(![d objectForKey:@"browname"]);
  130. }
  131. // check funky table names + getTableSchema
  132. [db executeUpdate:@"create table '234 fds' (foo text)"];
  133. FMDBQuickCheck(![db hadError]);
  134. rs = [db getTableSchema:@"234 fds"];
  135. FMDBQuickCheck([rs next]);
  136. [rs close];
  137. // ----------------------------------------------------------------------------------------
  138. // blob support.
  139. [db executeUpdate:@"create table blobTable (a text, b blob)"];
  140. // let's read in an image from safari's app bundle.
  141. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  142. if (safariCompass) {
  143. [db executeUpdate:@"insert into blobTable (a, b) values (?,?)", @"safari's compass", safariCompass];
  144. rs = [db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"];
  145. if ([rs next]) {
  146. safariCompass = [rs dataForColumn:@"b"];
  147. [safariCompass writeToFile:@"/tmp/compass.icns" atomically:NO];
  148. // let's look at our fancy image that we just wrote out..
  149. system("/usr/bin/open /tmp/compass.icns");
  150. // ye shall read the header for this function, or suffer the consequences.
  151. safariCompass = [rs dataNoCopyForColumn:@"b"];
  152. [safariCompass writeToFile:@"/tmp/compass_data_no_copy.icns" atomically:NO];
  153. system("/usr/bin/open /tmp/compass_data_no_copy.icns");
  154. }
  155. else {
  156. NSLog(@"Could not select image.");
  157. }
  158. [rs close];
  159. }
  160. else {
  161. NSLog(@"Can't find compass image..");
  162. }
  163. // test out the convenience methods in +Additions
  164. [db executeUpdate:@"create table t1 (a integer)"];
  165. [db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
  166. NSLog(@"Count of changes (should be 1): %d", [db changes]);
  167. FMDBQuickCheck([db changes] == 1);
  168. int ia = [db intForQuery:@"select a from t1 where a = ?", [NSNumber numberWithInt:5]];
  169. if (ia != 5) {
  170. NSLog(@"intForQuery didn't work (a != 5)");
  171. }
  172. // test the busy rety timeout schtuff.
  173. [db setBusyRetryTimeout:500];
  174. FMDatabase *newDb = [FMDatabase databaseWithPath:dbPath];
  175. [newDb open];
  176. rs = [newDb executeQuery:@"select rowid,* from test where a = ?", @"hi'"];
  177. [rs next]; // just grab one... which will keep the db locked.
  178. NSLog(@"Testing the busy timeout");
  179. BOOL success = [db executeUpdate:@"insert into t1 values (5)"];
  180. if (success) {
  181. NSLog(@"Whoa- the database didn't stay locked!");
  182. return 7;
  183. }
  184. else {
  185. NSLog(@"Hurray, our timeout worked");
  186. }
  187. [rs close];
  188. [newDb close];
  189. success = [db executeUpdate:@"insert into t1 values (5)"];
  190. if (!success) {
  191. NSLog(@"Whoa- the database shouldn't be locked!");
  192. return 8;
  193. }
  194. else {
  195. NSLog(@"Hurray, we can insert again!");
  196. }
  197. // test some nullness.
  198. [db executeUpdate:@"create table t2 (a integer, b integer)"];
  199. if (![db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]]) {
  200. NSLog(@"UH OH, can't insert a nil value for some reason...");
  201. }
  202. rs = [db executeQuery:@"select * from t2"];
  203. while ([rs next]) {
  204. NSString *aa = [rs stringForColumnIndex:0];
  205. NSString *b = [rs stringForColumnIndex:1];
  206. if (aa != nil) {
  207. NSLog(@"%s:%d", __FUNCTION__, __LINE__);
  208. NSLog(@"OH OH, PROBLEMO!");
  209. return 10;
  210. }
  211. else {
  212. NSLog(@"YAY, NULL VALUES");
  213. }
  214. if (![b isEqualToString:@"5"]) {
  215. NSLog(@"%s:%d", __FUNCTION__, __LINE__);
  216. NSLog(@"OH OH, PROBLEMO!");
  217. return 10;
  218. }
  219. }
  220. // test some inner loop funkness.
  221. [db executeUpdate:@"create table t3 (a somevalue)"];
  222. // do it again, just because
  223. [db beginTransaction];
  224. i = 0;
  225. while (i++ < 20) {
  226. [db executeUpdate:@"insert into t3 (a) values (?)" , [NSNumber numberWithInt:i]];
  227. }
  228. [db commit];
  229. rs = [db executeQuery:@"select * from t3"];
  230. while ([rs next]) {
  231. int foo = [rs intForColumnIndex:0];
  232. int newVal = foo + 100;
  233. [db executeUpdate:@"update t3 set a = ? where a = ?" , [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]];
  234. FMResultSet *rs2 = [db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]];
  235. [rs2 next];
  236. if ([rs2 intForColumnIndex:0] != newVal) {
  237. NSLog(@"Oh crap, our update didn't work out!");
  238. return 9;
  239. }
  240. [rs2 close];
  241. }
  242. // NSNull tests
  243. [db executeUpdate:@"create table nulltest (a text, b text)"];
  244. [db executeUpdate:@"insert into nulltest (a, b) values (?, ?)" , [NSNull null], @"a"];
  245. [db executeUpdate:@"insert into nulltest (a, b) values (?, ?)" , nil, @"b"];
  246. rs = [db executeQuery:@"select * from nulltest"];
  247. while ([rs next]) {
  248. NSString *a = [rs stringForColumnIndex:0];
  249. NSString *b = [rs stringForColumnIndex:1];
  250. if (!b) {
  251. NSLog(@"Oh crap, the nil / null inserts didn't work!");
  252. return 10;
  253. }
  254. if (a) {
  255. NSLog(@"Oh crap, the nil / null inserts didn't work (son of error message)!");
  256. return 11;
  257. }
  258. else {
  259. NSLog(@"HURRAH FOR NSNULL (and nil)!");
  260. }
  261. }
  262. FMDBQuickCheck([db columnExists:@"a" inTableWithName:@"nulltest"]);
  263. FMDBQuickCheck([db columnExists:@"b" inTableWithName:@"nulltest"]);
  264. FMDBQuickCheck(![db columnExists:@"c" inTableWithName:@"nulltest"]);
  265. // null dates
  266. NSDate *date = [NSDate date];
  267. [db executeUpdate:@"create table datetest (a double, b double, c double)"];
  268. [db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date];
  269. rs = [db executeQuery:@"select * from datetest"];
  270. while ([rs next]) {
  271. NSDate *a = [rs dateForColumnIndex:0];
  272. NSDate *b = [rs dateForColumnIndex:1];
  273. NSDate *c = [rs dateForColumnIndex:2];
  274. if (a) {
  275. NSLog(@"Oh crap, the null date insert didn't work!");
  276. return 12;
  277. }
  278. if (!c) {
  279. NSLog(@"Oh crap, the 0 date insert didn't work!");
  280. return 12;
  281. }
  282. NSTimeInterval dti = fabs([b timeIntervalSinceDate:date]);
  283. if (floor(dti) > 0.0) {
  284. NSLog(@"Date matches didn't really happen... time difference of %f", dti);
  285. return 13;
  286. }
  287. dti = fabs([c timeIntervalSinceDate:[NSDate dateWithTimeIntervalSince1970:0]]);
  288. if (floor(dti) > 0.0) {
  289. NSLog(@"Date matches didn't really happen... time difference of %f", dti);
  290. return 13;
  291. }
  292. }
  293. NSDate *foo = [db dateForQuery:@"select b from datetest where c = 0"];
  294. assert(foo);
  295. NSTimeInterval dti = fabs([foo timeIntervalSinceDate:date]);
  296. if (floor(dti) > 0.0) {
  297. NSLog(@"Date matches didn't really happen... time difference of %f", dti);
  298. return 14;
  299. }
  300. [db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"];
  301. [db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]];
  302. [db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]];
  303. rs = [db executeQuery:@"select * from nulltest2"];
  304. while ([rs next]) {
  305. i = [rs intForColumnIndex:2];
  306. if (i == 12) {
  307. // it's the first row we inserted.
  308. FMDBQuickCheck(![rs columnIndexIsNull:0]);
  309. FMDBQuickCheck(![rs columnIndexIsNull:1]);
  310. FMDBQuickCheck(![rs columnIndexIsNull:2]);
  311. FMDBQuickCheck(![rs columnIndexIsNull:3]);
  312. FMDBQuickCheck(![rs columnIndexIsNull:4]);
  313. FMDBQuickCheck( [rs columnIndexIsNull:5]);
  314. FMDBQuickCheck([[rs dataForColumn:@"d"] length] == [safariCompass length]);
  315. FMDBQuickCheck(![rs dataForColumn:@"notthere"]);
  316. FMDBQuickCheck(![rs stringForColumnIndex:-2]);
  317. FMDBQuickCheck([rs boolForColumnIndex:4]);
  318. FMDBQuickCheck([rs boolForColumn:@"b"]);
  319. FMDBQuickCheck(fabs(4.4 - [rs doubleForColumn:@"f"]) < 0.0000001);
  320. FMDBQuickCheck(12 == [rs intForColumn:@"i"]);
  321. FMDBQuickCheck(12 == [rs intForColumnIndex:2]);
  322. FMDBQuickCheck(0 == [rs intForColumnIndex:12]); // there is no 12
  323. FMDBQuickCheck(0 == [rs intForColumn:@"notthere"]);
  324. FMDBQuickCheck(12 == [rs longForColumn:@"i"]);
  325. FMDBQuickCheck(12 == [rs longLongIntForColumn:@"i"]);
  326. }
  327. else {
  328. // let's test various null things.
  329. FMDBQuickCheck([rs columnIndexIsNull:0]);
  330. FMDBQuickCheck([rs columnIndexIsNull:1]);
  331. FMDBQuickCheck([rs columnIndexIsNull:2]);
  332. FMDBQuickCheck([rs columnIndexIsNull:3]);
  333. FMDBQuickCheck([rs columnIndexIsNull:4]);
  334. FMDBQuickCheck([rs columnIndexIsNull:5]);
  335. FMDBQuickCheck(![rs dataForColumn:@"d"]);
  336. }
  337. }
  338. {
  339. [db executeUpdate:@"create table utest (a text)"];
  340. [db executeUpdate:@"insert into utest values (?)", @"/übertest"];
  341. rs = [db executeQuery:@"select * from utest where a = ?", @"/übertest"];
  342. FMDBQuickCheck([rs next]);
  343. [rs close];
  344. }
  345. {
  346. [db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"];
  347. [db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]];
  348. [db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]];
  349. rs = [db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]];
  350. FMDBQuickCheck([rs next]);
  351. FMDBQuickCheck([rs hasAnotherRow]);
  352. FMDBQuickCheck(![db hadError]);
  353. FMDBQuickCheck([[rs stringForColumnIndex:0] isEqualToString:@"one"]);
  354. FMDBQuickCheck([rs intForColumnIndex:1] == 2);
  355. FMDBQuickCheck([rs next]);
  356. FMDBQuickCheck([rs intForColumnIndex:1] == 3);
  357. FMDBQuickCheck(![rs next]);
  358. FMDBQuickCheck(![rs hasAnotherRow]);
  359. }
  360. {
  361. FMDBQuickCheck([db executeUpdate:@"create table t4 (a text, b text)"]);
  362. FMDBQuickCheck(([db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"]));
  363. rs = [db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"];
  364. FMDBQuickCheck((rs != nil));
  365. [rs next];
  366. FMDBQuickCheck([[rs stringForColumn:@"t4.a"] isEqualToString:@"one"]);
  367. FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"two"]);
  368. FMDBQuickCheck(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two") == 0);
  369. [rs close];
  370. // let's try these again, with the withArgumentsInArray: variation
  371. FMDBQuickCheck([db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]);
  372. FMDBQuickCheck([db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]);
  373. FMDBQuickCheck(([db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]]));
  374. rs = [db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]];
  375. FMDBQuickCheck((rs != nil));
  376. [rs next];
  377. FMDBQuickCheck([[rs stringForColumn:@"t4.a"] isEqualToString:@"one"]);
  378. FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"two"]);
  379. FMDBQuickCheck(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two") == 0);
  380. [rs close];
  381. }
  382. {
  383. FMDBQuickCheck([db tableExists:@"t4"]);
  384. FMDBQuickCheck(![db tableExists:@"thisdoesntexist"]);
  385. rs = [db getSchema];
  386. while ([rs next]) {
  387. FMDBQuickCheck([[rs stringForColumn:@"type"] isEqualToString:@"table"]);
  388. }
  389. }
  390. {
  391. FMDBQuickCheck([db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  392. FMDBQuickCheck(([db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234]));
  393. rs = [db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42];
  394. FMDBQuickCheck((rs != nil));
  395. [rs next];
  396. FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"text"]);
  397. FMDBQuickCheck(([rs intForColumn:@"b"] == 42));
  398. FMDBQuickCheck([[rs stringForColumn:@"c"] isEqualToString:@"BLOB"]);
  399. FMDBQuickCheck([[rs stringForColumn:@"d"] isEqualToString:@"d"]);
  400. FMDBQuickCheck(([rs longLongIntForColumn:@"e"] == 12345678901234));
  401. [rs close];
  402. }
  403. {
  404. FMDBQuickCheck([db executeUpdate:@"create table t55 (a text, b int, c float)"]);
  405. short testShort = -4;
  406. float testFloat = 5.5;
  407. FMDBQuickCheck(([db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat]));
  408. unsigned short testUShort = 6;
  409. FMDBQuickCheck(([db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat]));
  410. rs = [db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"];
  411. FMDBQuickCheck((rs != nil));
  412. [rs next];
  413. FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"a"]);
  414. FMDBQuickCheck(([rs intForColumn:@"b"] == -4));
  415. FMDBQuickCheck([[rs stringForColumn:@"c"] isEqualToString:@"5.5"]);
  416. [rs next];
  417. FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"a"]);
  418. FMDBQuickCheck(([rs intForColumn:@"b"] == 6));
  419. FMDBQuickCheck([[rs stringForColumn:@"c"] isEqualToString:@"5.5"]);
  420. [rs close];
  421. }
  422. {
  423. FMDBQuickCheck(([db update:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]]));
  424. }
  425. // test attach for the heck of it.
  426. {
  427. //FMDatabase *dbA = [FMDatabase databaseWithPath:dbPath];
  428. [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil];
  429. FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"];
  430. FMDBQuickCheck([dbB open]);
  431. FMDBQuickCheck([dbB executeUpdate:@"create table attached (a text)"]);
  432. FMDBQuickCheck(([dbB executeUpdate:@"insert into attached values (?)", @"test"]));
  433. FMDBQuickCheck([dbB close]);
  434. [db executeUpdate:@"attach database '/tmp/attachme.db' as attack"];
  435. rs = [db executeQuery:@"select * from attack.attached"];
  436. FMDBQuickCheck([rs next]);
  437. [rs close];
  438. }
  439. {
  440. // -------------------------------------------------------------------------------
  441. // Named parameters.
  442. FMDBQuickCheck([db executeUpdate:@"create table namedparamtest (a text, b text, c integer, d double)"]);
  443. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  444. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  445. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  446. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  447. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  448. FMDBQuickCheck([db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  449. rs = [db executeQuery:@"select * from namedparamtest"];
  450. FMDBQuickCheck((rs != nil));
  451. [rs next];
  452. FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"Text1"]);
  453. FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"Text2"]);
  454. FMDBQuickCheck([rs intForColumn:@"c"] == 1);
  455. FMDBQuickCheck([rs doubleForColumn:@"d"] == 2.0);
  456. [rs close];
  457. dictionaryArgs = [NSMutableDictionary dictionary];
  458. [dictionaryArgs setObject:@"Text2" forKey:@"blah"];
  459. rs = [db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs];
  460. FMDBQuickCheck((rs != nil));
  461. FMDBQuickCheck([rs next]);
  462. FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"Text2"]);
  463. [rs close];
  464. }
  465. // just for fun.
  466. rs = [db executeQuery:@"PRAGMA database_list"];
  467. while ([rs next]) {
  468. NSString *file = [rs stringForColumn:@"file"];
  469. NSLog(@"database_list: %@", file);
  470. }
  471. // print out some stats if we are using cached statements.
  472. if ([db shouldCacheStatements]) {
  473. NSEnumerator *e = [[db cachedStatements] objectEnumerator];;
  474. FMStatement *statement;
  475. while ((statement = [e nextObject])) {
  476. NSLog(@"%@", statement);
  477. }
  478. }
  479. [db close];
  480. testPool(dbPath);
  481. FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
  482. FMDBQuickCheck(queue);
  483. {
  484. [queue inDatabase:^(FMDatabase *adb) {
  485. [adb executeUpdate:@"create table qfoo (foo text)"];
  486. [adb executeUpdate:@"insert into qfoo values ('hi')"];
  487. [adb executeUpdate:@"insert into qfoo values ('hello')"];
  488. [adb executeUpdate:@"insert into qfoo values ('not')"];
  489. int count = 0;
  490. FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
  491. while ([rsl next]) {
  492. count++;
  493. }
  494. FMDBQuickCheck(count == 2);
  495. count = 0;
  496. rsl = [adb executeQuery:@"select * from qfoo where foo like ?", @"h%"];
  497. while ([rsl next]) {
  498. count++;
  499. }
  500. FMDBQuickCheck(count == 2);
  501. }];
  502. }
  503. {
  504. // You should see pairs of numbers show up in stdout for this stuff:
  505. size_t ops = 16;
  506. dispatch_queue_t dqueue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH);
  507. dispatch_apply(ops, dqueue, ^(size_t nby) {
  508. // just mix things up a bit for demonstration purposes.
  509. if (nby % 2 == 1) {
  510. [NSThread sleepForTimeInterval:.1];
  511. [queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  512. NSLog(@"Starting query %ld", nby);
  513. FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
  514. while ([rsl next]) {
  515. ;// whatever.
  516. }
  517. NSLog(@"Ending query %ld", nby);
  518. }];
  519. }
  520. if (nby % 3 == 1) {
  521. [NSThread sleepForTimeInterval:.1];
  522. }
  523. [queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  524. NSLog(@"Starting update %ld", nby);
  525. [adb executeUpdate:@"insert into qfoo values ('1')"];
  526. [adb executeUpdate:@"insert into qfoo values ('2')"];
  527. [adb executeUpdate:@"insert into qfoo values ('3')"];
  528. NSLog(@"Ending update %ld", nby);
  529. }];
  530. });
  531. [queue close];
  532. [queue inDatabase:^(FMDatabase *adb) {
  533. FMDBQuickCheck([adb executeUpdate:@"insert into qfoo values ('1')"]);
  534. }];
  535. }
  536. {
  537. [queue inDatabase:^(FMDatabase *adb) {
  538. [adb executeUpdate:@"create table transtest (a integer)"];
  539. FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (1)"]);
  540. FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (2)"]);
  541. int rowCount = 0;
  542. FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
  543. while ([ars next]) {
  544. rowCount++;
  545. }
  546. FMDBQuickCheck(rowCount == 2);
  547. }];
  548. [queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  549. FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (3)"]);
  550. if (YES) {
  551. // uh oh!, something went wrong (not really, this is just a test
  552. *rollback = YES;
  553. return;
  554. }
  555. FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (4)"]);
  556. }];
  557. [queue inDatabase:^(FMDatabase *adb) {
  558. int rowCount = 0;
  559. FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
  560. while ([ars next]) {
  561. rowCount++;
  562. }
  563. FMDBQuickCheck(![adb hasOpenResultSets]);
  564. NSLog(@"after rollback, rowCount is %d (should be 2)", rowCount);
  565. FMDBQuickCheck(rowCount == 2);
  566. }];
  567. }
  568. // hey, let's make a custom function!
  569. [queue inDatabase:^(FMDatabase *adb) {
  570. [adb executeUpdate:@"create table ftest (foo text)"];
  571. [adb executeUpdate:@"insert into ftest values ('hello')"];
  572. [adb executeUpdate:@"insert into ftest values ('hi')"];
  573. [adb executeUpdate:@"insert into ftest values ('not h!')"];
  574. [adb executeUpdate:@"insert into ftest values ('definitely not h!')"];
  575. [adb makeFunctionNamed:@"StringStartsWithH" maximumArguments:1 withBlock:^(sqlite3_context *context, int aargc, sqlite3_value **aargv) {
  576. if (sqlite3_value_type(aargv[0]) == SQLITE_TEXT) {
  577. @autoreleasepool {
  578. const char *c = (const char *)sqlite3_value_text(aargv[0]);
  579. NSString *s = [NSString stringWithUTF8String:c];
  580. sqlite3_result_int(context, [s hasPrefix:@"h"]);
  581. }
  582. }
  583. else {
  584. NSLog(@"Unknown formart for StringStartsWithH (%d) %s:%d", sqlite3_value_type(aargv[0]), __FUNCTION__, __LINE__);
  585. sqlite3_result_null(context);
  586. }
  587. }];
  588. int rowCount = 0;
  589. FMResultSet *ars = [adb executeQuery:@"select * from ftest where StringStartsWithH(foo)"];
  590. while ([ars next]) {
  591. rowCount++;
  592. NSLog(@"Does %@ start with 'h'?", [rs stringForColumnIndex:0]);
  593. }
  594. FMDBQuickCheck(rowCount == 2);
  595. }];
  596. NSLog(@"That was version %@ of sqlite", [FMDatabase sqliteLibVersion]);
  597. }// this is the end of our @autorelease pool.
  598. return 0;
  599. }
  600. /*
  601. Test the various FMDatabasePool things.
  602. */
  603. void testPool(NSString *dbPath) {
  604. FMDatabasePool *dbPool = [FMDatabasePool databasePoolWithPath:dbPath];
  605. FMDBQuickCheck([dbPool countOfOpenDatabases] == 0);
  606. __block FMDatabase *db1;
  607. [dbPool inDatabase:^(FMDatabase *db) {
  608. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  609. FMDBQuickCheck([db tableExists:@"t4"]);
  610. db1 = db;
  611. }];
  612. [dbPool inDatabase:^(FMDatabase *db) {
  613. FMDBQuickCheck(db1 == db);
  614. [dbPool inDatabase:^(FMDatabase *db2) {
  615. FMDBQuickCheck(db2 != db);
  616. }];
  617. }];
  618. FMDBQuickCheck([dbPool countOfOpenDatabases] == 2);
  619. [dbPool inDatabase:^(FMDatabase *db) {
  620. [db executeUpdate:@"create table easy (a text)"];
  621. [db executeUpdate:@"create table easy2 (a text)"];
  622. }];
  623. FMDBQuickCheck([dbPool countOfOpenDatabases] == 2);
  624. [dbPool releaseAllDatabases];
  625. FMDBQuickCheck([dbPool countOfOpenDatabases] == 0);
  626. [dbPool inDatabase:^(FMDatabase *aDb) {
  627. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0);
  628. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1);
  629. FMDBQuickCheck([aDb tableExists:@"t4"]);
  630. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0);
  631. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1);
  632. FMDBQuickCheck(([aDb executeUpdate:@"insert into easy (a) values (?)", @"hi"]));
  633. // just for fun.
  634. FMResultSet *rs2 = [aDb executeQuery:@"select * from easy"];
  635. FMDBQuickCheck([rs2 next]);
  636. while ([rs2 next]) { ; } // whatevers.
  637. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  638. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0);
  639. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1);
  640. }];
  641. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  642. {
  643. [dbPool inDatabase:^(FMDatabase *db) {
  644. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1]];
  645. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:2]];
  646. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]];
  647. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0);
  648. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1);
  649. }];
  650. }
  651. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  652. [dbPool setMaximumNumberOfDatabasesToCreate:2];
  653. [dbPool inDatabase:^(FMDatabase *db) {
  654. [dbPool inDatabase:^(FMDatabase *db2) {
  655. [dbPool inDatabase:^(FMDatabase *db3) {
  656. FMDBQuickCheck([dbPool countOfOpenDatabases] == 2);
  657. FMDBQuickCheck(!db3);
  658. }];
  659. }];
  660. }];
  661. [dbPool setMaximumNumberOfDatabasesToCreate:0];
  662. [dbPool releaseAllDatabases];
  663. FMDBQuickCheck([dbPool countOfOpenDatabases] == 0);
  664. [dbPool inDatabase:^(FMDatabase *db) {
  665. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]];
  666. }];
  667. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  668. [dbPool inTransaction:^(FMDatabase *adb, BOOL *rollback) {
  669. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]];
  670. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]];
  671. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]];
  672. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  673. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0);
  674. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1);
  675. }];
  676. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  677. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 1);
  678. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 0);
  679. [dbPool inDatabase:^(FMDatabase *db) {
  680. FMResultSet *rs2 = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1001]];
  681. FMDBQuickCheck([rs2 next]);
  682. FMDBQuickCheck(![rs2 next]);
  683. }];
  684. [dbPool inDeferredTransaction:^(FMDatabase *adb, BOOL *rollback) {
  685. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1004]];
  686. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1005]];
  687. *rollback = YES;
  688. }];
  689. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1);
  690. FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 1);
  691. FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 0);
  692. NSError *err = [dbPool inSavePoint:^(FMDatabase *db, BOOL *rollback) {
  693. [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1006]];
  694. }];
  695. FMDBQuickCheck(!err);
  696. {
  697. err = [dbPool inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
  698. FMDBQuickCheck(![adb hadError]);
  699. [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1009]];
  700. [adb inSavePoint:^(BOOL *arollback) {
  701. FMDBQuickCheck(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1010]]));
  702. *arollback = YES;
  703. }];
  704. }];
  705. FMDBQuickCheck(!err);
  706. [dbPool inDatabase:^(FMDatabase *db) {
  707. FMResultSet *rs2 = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1009]];
  708. FMDBQuickCheck([rs2 next]);
  709. FMDBQuickCheck(![rs2 next]); // close it out.
  710. rs2 = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1010]];
  711. FMDBQuickCheck(![rs2 next]);
  712. }];
  713. }
  714. {
  715. [dbPool inDatabase:^(FMDatabase *db) {
  716. [db executeUpdate:@"create table likefoo (foo text)"];
  717. [db executeUpdate:@"insert into likefoo values ('hi')"];
  718. [db executeUpdate:@"insert into likefoo values ('hello')"];
  719. [db executeUpdate:@"insert into likefoo values ('not')"];
  720. int count = 0;
  721. FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
  722. while ([rsl next]) {
  723. count++;
  724. }
  725. FMDBQuickCheck(count == 2);
  726. count = 0;
  727. rsl = [db executeQuery:@"select * from likefoo where foo like ?", @"h%"];
  728. while ([rsl next]) {
  729. count++;
  730. }
  731. FMDBQuickCheck(count == 2);
  732. }];
  733. }
  734. {
  735. size_t ops = 128;
  736. dispatch_queue_t dqueue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH);
  737. dispatch_apply(ops, dqueue, ^(size_t nby) {
  738. // just mix things up a bit for demonstration purposes.
  739. if (nby % 2 == 1) {
  740. [NSThread sleepForTimeInterval:.1];
  741. }
  742. [dbPool inDatabase:^(FMDatabase *db) {
  743. NSLog(@"Starting query %ld", nby);
  744. FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
  745. while ([rsl next]) {
  746. if (nby % 3 == 1) {
  747. [NSThread sleepForTimeInterval:.05];
  748. }
  749. }
  750. NSLog(@"Ending query %ld", nby);
  751. }];
  752. });
  753. NSLog(@"Number of open databases after crazy gcd stuff: %ld", [dbPool countOfOpenDatabases]);
  754. }
  755. // if you want to see a deadlock, just uncomment this line and run:
  756. //#define ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD 1
  757. #ifdef ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD
  758. {
  759. int ops = 16;
  760. dispatch_queue_t dqueue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH);
  761. dispatch_apply(ops, dqueue, ^(size_t nby) {
  762. // just mix things up a bit for demonstration purposes.
  763. if (nby % 2 == 1) {
  764. [NSThread sleepForTimeInterval:.1];
  765. [dbPool inTransaction:^(FMDatabase *db, BOOL *rollback) {
  766. NSLog(@"Starting query %ld", nby);
  767. FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
  768. while ([rsl next]) {
  769. ;// whatever.
  770. }
  771. NSLog(@"Ending query %ld", nby);
  772. }];
  773. }
  774. if (nby % 3 == 1) {
  775. [NSThread sleepForTimeInterval:.1];
  776. }
  777. [dbPool inTransaction:^(FMDatabase *db, BOOL *rollback) {
  778. NSLog(@"Starting update %ld", nby);
  779. [db executeUpdate:@"insert into likefoo values ('1')"];
  780. [db executeUpdate:@"insert into likefoo values ('2')"];
  781. [db executeUpdate:@"insert into likefoo values ('3')"];
  782. NSLog(@"Ending update %ld", nby);
  783. }];
  784. });
  785. [dbPool releaseAllDatabases];
  786. [dbPool inDatabase:^(FMDatabase *db) {
  787. FMDBQuickCheck([db executeUpdate:@"insert into likefoo values ('1')"]);
  788. }];
  789. }
  790. #endif
  791. }
  792. /*
  793. What is this function for? Think of it as a template which a developer can use
  794. to report bugs.
  795. If you have a bug, make it reproduce in this function and then let the
  796. developer(s) know either via the github bug reporter or the mailing list.
  797. */
  798. void FMDBReportABugFunction() {
  799. NSString *dbPath = @"/tmp/bugreportsample.db";
  800. // delete the old db if it exists
  801. NSFileManager *fileManager = [NSFileManager defaultManager];
  802. [fileManager removeItemAtPath:dbPath error:nil];
  803. FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
  804. [queue inDatabase:^(FMDatabase *db) {
  805. /*
  806. Change the contents of this block to suit your needs.
  807. */
  808. BOOL worked = [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"];
  809. FMDBQuickCheck(worked);
  810. worked = [db executeUpdate:@"insert into test values ('a', 'b', 1, 2.2, 2.3)"];
  811. FMDBQuickCheck(worked);
  812. FMResultSet *rs = [db executeQuery:@"select * from test"];
  813. FMDBQuickCheck([rs next]);
  814. [rs close];
  815. }];
  816. [queue close];
  817. // uncomment the following line if you don't want to run through all the other tests.
  818. //exit(0);
  819. }