fmdb.m 40 KB

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