fmdb.m 51 KB

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