fmdb.m 39 KB

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