main.m 51 KB

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