FMDatabaseTests.m 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183
  1. //
  2. // Tests.m
  3. // Tests
  4. //
  5. // Created by Graham Dennis on 24/11/2013.
  6. //
  7. //
  8. #import "FMDBTempDBTests.h"
  9. #import "FMDatabase.h"
  10. #import "FMDatabaseAdditions.h"
  11. #if FMDB_SQLITE_STANDALONE
  12. #import <sqlite3/sqlite3.h>
  13. #else
  14. #import <sqlite3.h>
  15. #endif
  16. @interface FMDatabaseTests : FMDBTempDBTests
  17. @end
  18. @implementation FMDatabaseTests
  19. + (void)populateDatabase:(FMDatabase *)db
  20. {
  21. [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"];
  22. [db beginTransaction];
  23. int i = 0;
  24. while (i++ < 20) {
  25. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  26. @"hi'", // look! I put in a ', and I'm not escaping it!
  27. [NSString stringWithFormat:@"number %d", i],
  28. [NSNumber numberWithInt:i],
  29. [NSDate date],
  30. [NSNumber numberWithFloat:2.2f]];
  31. }
  32. [db commit];
  33. // do it again, just because
  34. [db beginTransaction];
  35. i = 0;
  36. while (i++ < 20) {
  37. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  38. @"hi again'", // look! I put in a ', and I'm not escaping it!
  39. [NSString stringWithFormat:@"number %d", i],
  40. [NSNumber numberWithInt:i],
  41. [NSDate date],
  42. [NSNumber numberWithFloat:2.2f]];
  43. }
  44. [db commit];
  45. [db executeUpdate:@"create table t3 (a somevalue)"];
  46. [db beginTransaction];
  47. for (int i=0; i < 20; i++) {
  48. [db executeUpdate:@"insert into t3 (a) values (?)", [NSNumber numberWithInt:i]];
  49. }
  50. [db commit];
  51. }
  52. - (void)setUp
  53. {
  54. [super setUp];
  55. // Put setup code here. This method is called before the invocation of each test method in the class.
  56. }
  57. - (void)tearDown
  58. {
  59. // Put teardown code here. This method is called after the invocation of each test method in the class.
  60. [super tearDown];
  61. }
  62. - (void)testOpenWithVFS
  63. {
  64. // create custom vfs
  65. sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
  66. vfs.zName = "MyCustomVFS";
  67. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
  68. // use custom vfs to open a in memory database
  69. FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
  70. [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
  71. XCTAssertFalse([db hadError], @"Open with a custom VFS should have succeeded");
  72. }
  73. - (void)testFailOnOpenWithUnknownVFS
  74. {
  75. FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
  76. [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"UnknownVFS"];
  77. XCTAssertTrue([db hadError], @"Should have failed");
  78. }
  79. - (void)testFailOnUnopenedDatabase
  80. {
  81. [self.db close];
  82. XCTAssertNil([self.db executeQuery:@"select * from table"], @"Shouldn't get results from an empty table");
  83. XCTAssertTrue([self.db hadError], @"Should have failed");
  84. }
  85. - (void)testFailOnBadStatement
  86. {
  87. XCTAssertFalse([self.db executeUpdate:@"blah blah blah"], @"Invalid statement should fail");
  88. XCTAssertTrue([self.db hadError], @"Should have failed");
  89. }
  90. - (void)testFailOnBadStatementWithError
  91. {
  92. NSError *error = nil;
  93. XCTAssertFalse([self.db executeUpdate:@"blah blah blah" withErrorAndBindings:&error], @"Invalid statement should fail");
  94. XCTAssertNotNil(error, @"Should have a non-nil NSError");
  95. XCTAssertEqual([error code], (NSInteger)SQLITE_ERROR, @"Error should be SQLITE_ERROR");
  96. }
  97. - (void)testPragmaJournalMode
  98. {
  99. FMResultSet *ps = [self.db executeQuery:@"pragma journal_mode=delete"];
  100. XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
  101. XCTAssertNotNil(ps, @"Result set should be non-nil");
  102. XCTAssertTrue([ps next], @"Result set should have a next result");
  103. [ps close];
  104. }
  105. - (void)testPragmaPageSize
  106. {
  107. [self.db executeUpdate:@"PRAGMA page_size=2048"];
  108. XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
  109. }
  110. - (void)testVacuum
  111. {
  112. [self.db executeUpdate:@"VACUUM"];
  113. XCTAssertFalse([self.db hadError], @"VACUUM should have succeeded");
  114. }
  115. - (void)testSelectULL
  116. {
  117. // Unsigned long long
  118. [self.db executeUpdate:@"create table ull (a integer)"];
  119. [self.db executeUpdate:@"insert into ull (a) values (?)", [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]];
  120. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  121. FMResultSet *rs = [self.db executeQuery:@"select a from ull"];
  122. while ([rs next]) {
  123. XCTAssertEqual([rs unsignedLongLongIntForColumnIndex:0], ULLONG_MAX, @"Result should be ULLONG_MAX");
  124. XCTAssertEqual([rs unsignedLongLongIntForColumn:@"a"], ULLONG_MAX, @"Result should be ULLONG_MAX");
  125. }
  126. [rs close];
  127. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  128. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  129. }
  130. - (void)testSelectByColumnName
  131. {
  132. FMResultSet *rs = [self.db executeQuery:@"select rowid,* from test where a = ?", @"hi"];
  133. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  134. while ([rs next]) {
  135. [rs intForColumn:@"c"];
  136. XCTAssertNotNil([rs stringForColumn:@"b"], @"Should have non-nil string for 'b'");
  137. XCTAssertNotNil([rs stringForColumn:@"a"], @"Should have non-nil string for 'a'");
  138. XCTAssertNotNil([rs stringForColumn:@"rowid"], @"Should have non-nil string for 'rowid'");
  139. XCTAssertNotNil([rs dateForColumn:@"d"], @"Should have non-nil date for 'd'");
  140. [rs doubleForColumn:@"d"];
  141. [rs doubleForColumn:@"e"];
  142. XCTAssertEqualObjects([rs columnNameForIndex:0], @"rowid", @"Wrong column name for result set column number");
  143. XCTAssertEqualObjects([rs columnNameForIndex:1], @"a", @"Wrong column name for result set column number");
  144. }
  145. [rs close];
  146. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  147. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  148. }
  149. - (void)testSelectWithIndexedAndKeyedSubscript
  150. {
  151. FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
  152. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  153. while ([rs next]) {
  154. XCTAssertEqualObjects(rs[0], rs[@"rowid"], @"Column zero should be equal to 'rowid'");
  155. XCTAssertEqualObjects(rs[1], rs[@"a"], @"Column 1 should be equal to 'a'");
  156. XCTAssertEqualObjects(rs[2], rs[@"b"], @"Column 2 should be equal to 'b'");
  157. XCTAssertEqualObjects(rs[3], rs[@"c"], @"Column 3 should be equal to 'c'");
  158. }
  159. [rs close];
  160. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  161. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  162. }
  163. - (void)testBusyRetryTimeout
  164. {
  165. [self.db executeUpdate:@"create table t1 (a integer)"];
  166. [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
  167. [self.db setMaxBusyRetryTimeInterval:2];
  168. FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
  169. [newDB open];
  170. FMResultSet *rs = [newDB executeQuery:@"select rowid,* from test where a = ?", @"hi'"];
  171. [rs next]; // just grab one... which will keep the db locked
  172. XCTAssertFalse([self.db executeUpdate:@"insert into t1 values (5)"], @"Insert should fail because the db is locked by a read");
  173. XCTAssertEqual([self.db lastErrorCode], SQLITE_BUSY, @"SQLITE_BUSY should be the last error");
  174. [rs close];
  175. [newDB close];
  176. XCTAssertTrue([self.db executeUpdate:@"insert into t1 values (5)"], @"The database shouldn't be locked at this point");
  177. }
  178. - (void)testCaseSensitiveResultDictionary
  179. {
  180. // case sensitive result dictionary test
  181. [self.db executeUpdate:@"create table cs (aRowName integer, bRowName text)"];
  182. [self.db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)", [NSNumber numberWithBool:1], @"hello"];
  183. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  184. FMResultSet *rs = [self.db executeQuery:@"select * from cs"];
  185. while ([rs next]) {
  186. NSDictionary *d = [rs resultDictionary];
  187. XCTAssertNotNil([d objectForKey:@"aRowName"], @"aRowName should be non-nil");
  188. XCTAssertNil([d objectForKey:@"arowname"], @"arowname should be nil");
  189. XCTAssertNotNil([d objectForKey:@"bRowName"], @"bRowName should be non-nil");
  190. XCTAssertNil([d objectForKey:@"browname"], @"browname should be nil");
  191. }
  192. [rs close];
  193. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  194. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  195. }
  196. - (void)testBoolInsert
  197. {
  198. [self.db executeUpdate:@"create table btest (aRowName integer)"];
  199. [self.db executeUpdate:@"insert into btest (aRowName) values (?)", [NSNumber numberWithBool:12]];
  200. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  201. FMResultSet *rs = [self.db executeQuery:@"select * from btest"];
  202. while ([rs next]) {
  203. XCTAssertTrue([rs boolForColumnIndex:0], @"first column should be true.");
  204. XCTAssertTrue([rs intForColumnIndex:0] == 1, @"first column should be equal to 1 - it was %d.", [rs intForColumnIndex:0]);
  205. }
  206. [rs close];
  207. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  208. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  209. }
  210. - (void)testNamedParametersCount
  211. {
  212. XCTAssertTrue([self.db executeUpdate:@"create table namedparamcounttest (a text, b text, c integer, d double)"]);
  213. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  214. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  215. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  216. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  217. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  218. XCTAssertTrue([self.db executeUpdate:@"insert into namedparamcounttest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  219. FMResultSet *rs = [self.db executeQuery:@"select * from namedparamcounttest"];
  220. XCTAssertNotNil(rs);
  221. [rs next];
  222. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
  223. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  224. XCTAssertEqual([rs intForColumn:@"c"], 1);
  225. XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
  226. [rs close];
  227. // note that at this point, dictionaryArgs has way more values than we need, but the query should still work since
  228. // a is in there, and that's all we need.
  229. rs = [self.db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs];
  230. XCTAssertNotNil(rs);
  231. XCTAssertTrue([rs next]);
  232. [rs close];
  233. // ***** Please note the following codes *****
  234. dictionaryArgs = [NSMutableDictionary dictionary];
  235. [dictionaryArgs setObject:@"NewText1" forKey:@"a"];
  236. [dictionaryArgs setObject:@"NewText2" forKey:@"b"];
  237. [dictionaryArgs setObject:@"OneMoreText" forKey:@"OneMore"];
  238. XCTAssertTrue([self.db executeUpdate:@"update namedparamcounttest set a = :a, b = :b where b = 'Text2'" withParameterDictionary:dictionaryArgs]);
  239. }
  240. - (void)testBlobs
  241. {
  242. [self.db executeUpdate:@"create table blobTable (a text, b blob)"];
  243. // let's read an image from safari's app bundle.
  244. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  245. if (safariCompass) {
  246. [self.db executeUpdate:@"insert into blobTable (a, b) values (?, ?)", @"safari's compass", safariCompass];
  247. FMResultSet *rs = [self.db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"];
  248. XCTAssertTrue([rs next]);
  249. NSData *readData = [rs dataForColumn:@"b"];
  250. XCTAssertEqualObjects(readData, safariCompass);
  251. // ye shall read the header for this function, or suffer the consequences.
  252. NSData *readDataNoCopy = [rs dataNoCopyForColumn:@"b"];
  253. XCTAssertEqualObjects(readDataNoCopy, safariCompass);
  254. [rs close];
  255. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  256. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  257. }
  258. }
  259. - (void)testNullValues
  260. {
  261. [self.db executeUpdate:@"create table t2 (a integer, b integer)"];
  262. BOOL result = [self.db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]];
  263. XCTAssertTrue(result, @"Failed to insert a nil value");
  264. FMResultSet *rs = [self.db executeQuery:@"select * from t2"];
  265. while ([rs next]) {
  266. XCTAssertNil([rs stringForColumnIndex:0], @"Wasn't able to retrieve a null string");
  267. XCTAssertEqualObjects([rs stringForColumnIndex:1], @"5");
  268. }
  269. [rs close];
  270. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  271. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  272. }
  273. - (void)testNestedResultSets
  274. {
  275. FMResultSet *rs = [self.db executeQuery:@"select * from t3"];
  276. while ([rs next]) {
  277. int foo = [rs intForColumnIndex:0];
  278. int newVal = foo + 100;
  279. [self.db executeUpdate:@"update t3 set a = ? where a = ?", [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]];
  280. FMResultSet *rs2 = [self.db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]];
  281. [rs2 next];
  282. XCTAssertEqual([rs2 intForColumnIndex:0], newVal);
  283. [rs2 close];
  284. }
  285. [rs close];
  286. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  287. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  288. }
  289. - (void)testNSNullInsertion
  290. {
  291. [self.db executeUpdate:@"create table nulltest (a text, b text)"];
  292. [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", [NSNull null], @"a"];
  293. [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", nil, @"b"];
  294. FMResultSet *rs = [self.db executeQuery:@"select * from nulltest"];
  295. while ([rs next]) {
  296. XCTAssertNil([rs stringForColumnIndex:0]);
  297. XCTAssertNotNil([rs stringForColumnIndex:1]);
  298. }
  299. [rs close];
  300. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  301. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  302. }
  303. - (void)testNullDates
  304. {
  305. NSDate *date = [NSDate date];
  306. [self.db executeUpdate:@"create table datetest (a double, b double, c double)"];
  307. [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date];
  308. FMResultSet *rs = [self.db executeQuery:@"select * from datetest"];
  309. XCTAssertNotNil(rs);
  310. while ([rs next]) {
  311. NSDate *b = [rs dateForColumnIndex:1];
  312. NSDate *c = [rs dateForColumnIndex:2];
  313. XCTAssertNil([rs dateForColumnIndex:0]);
  314. XCTAssertNotNil(c, @"zero date shouldn't be nil");
  315. XCTAssertEqualWithAccuracy([b timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second");
  316. XCTAssertEqualWithAccuracy([c timeIntervalSince1970], 0.0, 1.0, @"Dates should be the same to within a second");
  317. }
  318. [rs close];
  319. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  320. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  321. }
  322. - (void)testLotsOfNULLs
  323. {
  324. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  325. if (!safariCompass)
  326. return;
  327. [self.db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"];
  328. [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]];
  329. [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]];
  330. FMResultSet *rs = [self.db executeQuery:@"select * from nulltest2"];
  331. while ([rs next]) {
  332. int i = [rs intForColumnIndex:2];
  333. if (i == 12) {
  334. // it's the first row we inserted.
  335. XCTAssertFalse([rs columnIndexIsNull:0]);
  336. XCTAssertFalse([rs columnIndexIsNull:1]);
  337. XCTAssertFalse([rs columnIndexIsNull:2]);
  338. XCTAssertFalse([rs columnIndexIsNull:3]);
  339. XCTAssertFalse([rs columnIndexIsNull:4]);
  340. XCTAssertTrue( [rs columnIndexIsNull:5]);
  341. XCTAssertEqualObjects([rs dataForColumn:@"d"], safariCompass);
  342. XCTAssertNil([rs dataForColumn:@"notthere"]);
  343. XCTAssertNil([rs stringForColumnIndex:-2], @"Negative columns should return nil results");
  344. XCTAssertTrue([rs boolForColumnIndex:4]);
  345. XCTAssertTrue([rs boolForColumn:@"b"]);
  346. XCTAssertEqualWithAccuracy(4.4, [rs doubleForColumn:@"f"], 0.0000001, @"Saving a float and returning it as a double shouldn't change the result much");
  347. XCTAssertEqual([rs intForColumn:@"i"], 12);
  348. XCTAssertEqual([rs intForColumnIndex:2], 12);
  349. XCTAssertEqual([rs intForColumnIndex:12], 0, @"Non-existent columns should return zero for ints");
  350. XCTAssertEqual([rs intForColumn:@"notthere"], 0, @"Non-existent columns should return zero for ints");
  351. XCTAssertEqual([rs longForColumn:@"i"], 12l);
  352. XCTAssertEqual([rs longLongIntForColumn:@"i"], 12ll);
  353. }
  354. else {
  355. // let's test various null things.
  356. XCTAssertTrue([rs columnIndexIsNull:0]);
  357. XCTAssertTrue([rs columnIndexIsNull:1]);
  358. XCTAssertTrue([rs columnIndexIsNull:2]);
  359. XCTAssertTrue([rs columnIndexIsNull:3]);
  360. XCTAssertTrue([rs columnIndexIsNull:4]);
  361. XCTAssertTrue([rs columnIndexIsNull:5]);
  362. XCTAssertNil([rs dataForColumn:@"d"]);
  363. }
  364. }
  365. [rs close];
  366. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  367. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  368. }
  369. - (void)testUTF8Strings
  370. {
  371. [self.db executeUpdate:@"create table utest (a text)"];
  372. [self.db executeUpdate:@"insert into utest values (?)", @"/übertest"];
  373. FMResultSet *rs = [self.db executeQuery:@"select * from utest where a = ?", @"/übertest"];
  374. XCTAssertTrue([rs next]);
  375. [rs close];
  376. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  377. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  378. }
  379. - (void)testArgumentsInArray
  380. {
  381. [self.db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"];
  382. [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]];
  383. [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]];
  384. FMResultSet *rs = [self.db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]];
  385. XCTAssertTrue([rs next]);
  386. XCTAssertTrue([rs hasAnotherRow]);
  387. XCTAssertFalse([self.db hadError]);
  388. XCTAssertEqualObjects([rs stringForColumnIndex:0], @"one");
  389. XCTAssertEqual([rs intForColumnIndex:1], 2);
  390. XCTAssertTrue([rs next]);
  391. XCTAssertEqual([rs intForColumnIndex:1], 3);
  392. XCTAssertFalse([rs next]);
  393. XCTAssertFalse([rs hasAnotherRow]);
  394. }
  395. - (void)testColumnNamesContainingPeriods
  396. {
  397. XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]);
  398. [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"];
  399. FMResultSet *rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"];
  400. XCTAssertNotNil(rs);
  401. XCTAssertTrue([rs next]);
  402. XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
  403. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
  404. XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two"), 0, @"String comparison should return zero");
  405. [rs close];
  406. // let's try these again, with the withArgumentsInArray: variation
  407. XCTAssertTrue([self.db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]);
  408. XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]);
  409. [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]];
  410. rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]];
  411. XCTAssertNotNil(rs);
  412. XCTAssertTrue([rs next]);
  413. XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
  414. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
  415. XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two"), 0, @"String comparison should return zero");
  416. [rs close];
  417. }
  418. - (void)testFormatStringParsing
  419. {
  420. XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  421. [self.db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234ll];
  422. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42];
  423. XCTAssertNotNil(rs);
  424. XCTAssertTrue([rs next]);
  425. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"text");
  426. XCTAssertEqual([rs intForColumn:@"b"], 42);
  427. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"BLOB");
  428. XCTAssertEqualObjects([rs stringForColumn:@"d"], @"d");
  429. XCTAssertEqual([rs longLongIntForColumn:@"e"], 12345678901234ll);
  430. [rs close];
  431. }
  432. - (void)testFormatStringParsingWithSizePrefixes
  433. {
  434. XCTAssertTrue([self.db executeUpdate:@"create table t55 (a text, b int, c float)"]);
  435. short testShort = -4;
  436. float testFloat = 5.5;
  437. [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat];
  438. unsigned short testUShort = 6;
  439. [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat];
  440. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"];
  441. XCTAssertNotNil(rs);
  442. XCTAssertTrue([rs next]);
  443. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
  444. XCTAssertEqual([rs intForColumn:@"b"], -4);
  445. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
  446. XCTAssertTrue([rs next]);
  447. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
  448. XCTAssertEqual([rs intForColumn:@"b"], 6);
  449. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
  450. [rs close];
  451. }
  452. - (void)testFormatStringParsingWithNilValue
  453. {
  454. XCTAssertTrue([self.db executeUpdate:@"create table tatwhat (a text)"]);
  455. BOOL worked = [self.db executeUpdateWithFormat:@"insert into tatwhat values(%@)", nil];
  456. XCTAssertTrue(worked);
  457. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from tatwhat"];
  458. XCTAssertNotNil(rs);
  459. XCTAssertTrue([rs next]);
  460. XCTAssertTrue([rs columnIndexIsNull:0]);
  461. XCTAssertFalse([rs next]);
  462. }
  463. - (void)testUpdateWithErrorAndBindings
  464. {
  465. XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  466. NSError *err = nil;
  467. BOOL result = [self.db executeUpdate:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]];
  468. XCTAssertTrue(result);
  469. }
  470. - (void)testSelectWithEmptyArgumentsArray
  471. {
  472. FMResultSet *rs = [self.db executeQuery:@"select * from test where a=?" withArgumentsInArray:@[]];
  473. XCTAssertNil(rs);
  474. }
  475. - (void)testDatabaseAttach
  476. {
  477. NSFileManager *fileManager = [NSFileManager new];
  478. [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil];
  479. FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"];
  480. XCTAssertTrue([dbB open]);
  481. XCTAssertTrue([dbB executeUpdate:@"create table attached (a text)"]);
  482. XCTAssertTrue(([dbB executeUpdate:@"insert into attached values (?)", @"test"]));
  483. XCTAssertTrue([dbB close]);
  484. [self.db executeUpdate:@"attach database '/tmp/attachme.db' as attack"];
  485. FMResultSet *rs = [self.db executeQuery:@"select * from attack.attached"];
  486. XCTAssertNotNil(rs);
  487. XCTAssertTrue([rs next]);
  488. [rs close];
  489. }
  490. - (void)testNamedParameters
  491. {
  492. // -------------------------------------------------------------------------------
  493. // Named parameters.
  494. XCTAssertTrue([self.db executeUpdate:@"create table namedparamtest (a text, b text, c integer, d double)"]);
  495. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  496. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  497. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  498. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  499. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  500. XCTAssertTrue([self.db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  501. FMResultSet *rs = [self.db executeQuery:@"select * from namedparamtest"];
  502. XCTAssertNotNil(rs);
  503. XCTAssertTrue([rs next]);
  504. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
  505. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  506. XCTAssertEqual([rs intForColumn:@"c"], 1);
  507. XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
  508. [rs close];
  509. dictionaryArgs = [NSMutableDictionary dictionary];
  510. [dictionaryArgs setObject:@"Text2" forKey:@"blah"];
  511. rs = [self.db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs];
  512. XCTAssertNotNil(rs);
  513. XCTAssertTrue([rs next]);
  514. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  515. [rs close];
  516. }
  517. - (void)testPragmaDatabaseList
  518. {
  519. FMResultSet *rs = [self.db executeQuery:@"pragma database_list"];
  520. int counter = 0;
  521. while ([rs next]) {
  522. counter++;
  523. XCTAssertEqualObjects([rs stringForColumn:@"file"], self.databasePath);
  524. }
  525. XCTAssertEqual(counter, 1, @"Only one database should be attached");
  526. }
  527. - (void)testCachedStatementsInUse
  528. {
  529. [self.db setShouldCacheStatements:true];
  530. [self.db executeUpdate:@"CREATE TABLE testCacheStatements(key INTEGER PRIMARY KEY, value INTEGER)"];
  531. [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (1, 2)"];
  532. [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (2, 4)"];
  533. XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
  534. XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
  535. }
  536. - (void)testStatementCachingWorks
  537. {
  538. [self.db executeUpdate:@"CREATE TABLE testStatementCaching ( value INTEGER )"];
  539. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
  540. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
  541. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (2)"];
  542. [self.db setShouldCacheStatements:YES];
  543. // two iterations.
  544. // the first time through no statements will be from the cache.
  545. // the second time through all statements come from the cache.
  546. for (int i = 1; i <= 2; i++ ) {
  547. FMResultSet* rs1 = [self.db executeQuery: @"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @1]; // results in 2 rows...
  548. XCTAssertNotNil(rs1);
  549. XCTAssertTrue([rs1 next]);
  550. // confirm that we're seeing the benefits of caching.
  551. XCTAssertEqual([[rs1 statement] useCount], (long)i);
  552. FMResultSet* rs2 = [self.db executeQuery:@"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @2]; // results in 1 row
  553. XCTAssertNotNil(rs2);
  554. XCTAssertTrue([rs2 next]);
  555. XCTAssertEqual([[rs2 statement] useCount], (long)i);
  556. // 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.
  557. XCTAssertTrue([rs1 next]);
  558. [rs1 close];
  559. [rs2 close];
  560. }
  561. }
  562. /*
  563. Test the date format
  564. */
  565. - (void)testDateFormat
  566. {
  567. void (^testOneDateFormat)(FMDatabase *, NSDate *) = ^( FMDatabase *db, NSDate *testDate ){
  568. [db executeUpdate:@"DROP TABLE IF EXISTS test_format"];
  569. [db executeUpdate:@"CREATE TABLE test_format ( test TEXT )"];
  570. [db executeUpdate:@"INSERT INTO test_format(test) VALUES (?)", testDate];
  571. FMResultSet *rs = [db executeQuery:@"SELECT test FROM test_format"];
  572. XCTAssertNotNil(rs);
  573. XCTAssertTrue([rs next]);
  574. XCTAssertEqualObjects([rs dateForColumnIndex:0], testDate);
  575. [rs close];
  576. };
  577. NSDateFormatter *fmt = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  578. NSDate *testDate = [fmt dateFromString:@"2013-02-20 12:00:00"];
  579. // test timestamp dates (ensuring our change does not break those)
  580. testOneDateFormat(self.db,testDate);
  581. // now test the string-based timestamp
  582. [self.db setDateFormat:fmt];
  583. testOneDateFormat(self.db, testDate);
  584. }
  585. - (void)testColumnNameMap
  586. {
  587. XCTAssertTrue([self.db executeUpdate:@"create table colNameTest (a, b, c, d)"]);
  588. XCTAssertTrue([self.db executeUpdate:@"insert into colNameTest values (1, 2, 3, 4)"]);
  589. FMResultSet *ars = [self.db executeQuery:@"select * from colNameTest"];
  590. XCTAssertNotNil(ars);
  591. NSDictionary *d = [ars columnNameToIndexMap];
  592. XCTAssertEqual([d count], (NSUInteger)4);
  593. XCTAssertEqualObjects([d objectForKey:@"a"], @0);
  594. XCTAssertEqualObjects([d objectForKey:@"b"], @1);
  595. XCTAssertEqualObjects([d objectForKey:@"c"], @2);
  596. XCTAssertEqualObjects([d objectForKey:@"d"], @3);
  597. }
  598. - (void)testCustomFunction
  599. {
  600. [self.db executeUpdate:@"create table ftest (foo text)"];
  601. [self.db executeUpdate:@"insert into ftest values ('hello')"];
  602. [self.db executeUpdate:@"insert into ftest values ('hi')"];
  603. [self.db executeUpdate:@"insert into ftest values ('not h!')"];
  604. [self.db executeUpdate:@"insert into ftest values ('definitely not h!')"];
  605. [self.db makeFunctionNamed:@"StringStartsWithH" maximumArguments:1 withBlock:^(void *context, int aargc, void **aargv) {
  606. if (sqlite3_value_type(aargv[0]) == SQLITE_TEXT) {
  607. @autoreleasepool {
  608. const char *c = (const char *)sqlite3_value_text(aargv[0]);
  609. NSString *s = [NSString stringWithUTF8String:c];
  610. sqlite3_result_int(context, [s hasPrefix:@"h"]);
  611. }
  612. }
  613. else {
  614. XCTFail(@"Unknown format for StringStartsWithH (%d)", sqlite3_value_type(aargv[0]));
  615. sqlite3_result_null(context);
  616. }
  617. }];
  618. int rowCount = 0;
  619. FMResultSet *ars = [self.db executeQuery:@"select * from ftest where StringStartsWithH(foo)"];
  620. while ([ars next]) {
  621. rowCount++;
  622. }
  623. XCTAssertEqual(rowCount, 2);
  624. }
  625. - (void)testVersionNumber {
  626. XCTAssertTrue([FMDatabase FMDBVersion] == 0x0262); // this is going to break everytime we bump it.
  627. }
  628. - (void)testExecuteStatements
  629. {
  630. BOOL success;
  631. NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
  632. "create table bulktest2 (id integer primary key autoincrement, y text);"
  633. "create table bulktest3 (id integer primary key autoincrement, z text);"
  634. "insert into bulktest1 (x) values ('XXX');"
  635. "insert into bulktest2 (y) values ('YYY');"
  636. "insert into bulktest3 (z) values ('ZZZ');";
  637. success = [self.db executeStatements:sql];
  638. XCTAssertTrue(success, @"bulk create");
  639. sql = @"select count(*) as count from bulktest1;"
  640. "select count(*) as count from bulktest2;"
  641. "select count(*) as count from bulktest3;";
  642. success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
  643. NSInteger count = [dictionary[@"count"] integerValue];
  644. XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
  645. return 0;
  646. }];
  647. XCTAssertTrue(success, @"bulk select");
  648. sql = @"drop table bulktest1;"
  649. "drop table bulktest2;"
  650. "drop table bulktest3;";
  651. success = [self.db executeStatements:sql];
  652. XCTAssertTrue(success, @"bulk drop");
  653. }
  654. - (void)testCharAndBoolTypes
  655. {
  656. XCTAssertTrue([self.db executeUpdate:@"create table charBoolTest (a, b, c)"]);
  657. BOOL success = [self.db executeUpdate:@"insert into charBoolTest values (?, ?, ?)", @YES, @NO, @('x')];
  658. XCTAssertTrue(success, @"Unable to insert values");
  659. FMResultSet *rs = [self.db executeQuery:@"select * from charBoolTest"];
  660. XCTAssertNotNil(rs);
  661. XCTAssertTrue([rs next], @"Did not return row");
  662. XCTAssertEqual([rs boolForColumn:@"a"], true);
  663. XCTAssertEqualObjects([rs objectForColumnName:@"a"], @YES);
  664. XCTAssertEqual([rs boolForColumn:@"b"], false);
  665. XCTAssertEqualObjects([rs objectForColumnName:@"b"], @NO);
  666. XCTAssertEqual([rs intForColumn:@"c"], 'x');
  667. XCTAssertEqualObjects([rs objectForColumnName:@"c"], @('x'));
  668. [rs close];
  669. XCTAssertTrue([self.db executeUpdate:@"drop table charBoolTest"], @"Did not drop table");
  670. }
  671. - (void)testSqliteLibVersion
  672. {
  673. NSString *version = [FMDatabase sqliteLibVersion];
  674. XCTAssert([version compare:@"3.7" options:NSNumericSearch] == NSOrderedDescending, @"earlier than 3.7");
  675. XCTAssert([version compare:@"4.0" options:NSNumericSearch] == NSOrderedAscending, @"not earlier than 4.0");
  676. }
  677. - (void)testIsThreadSafe
  678. {
  679. BOOL isThreadSafe = [FMDatabase isSQLiteThreadSafe];
  680. XCTAssert(isThreadSafe, @"not threadsafe");
  681. }
  682. - (void)testOpenNilPath
  683. {
  684. FMDatabase *db = [[FMDatabase alloc] init];
  685. XCTAssert([db open], @"open failed");
  686. XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
  687. NSString *value = @"baz";
  688. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
  689. NSString *retrievedValue = [db stringForQuery:@"select bar from foo"];
  690. XCTAssert([value compare:retrievedValue] == NSOrderedSame, @"values didn't match");
  691. }
  692. - (void)testOpenZeroLengthPath
  693. {
  694. FMDatabase *db = [[FMDatabase alloc] initWithPath:@""];
  695. XCTAssert([db open], @"open failed");
  696. XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
  697. NSString *value = @"baz";
  698. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
  699. NSString *retrievedValue = [db stringForQuery:@"select bar from foo"];
  700. XCTAssert([value compare:retrievedValue] == NSOrderedSame, @"values didn't match");
  701. }
  702. - (void)testOpenTwice
  703. {
  704. FMDatabase *db = [[FMDatabase alloc] init];
  705. [db open];
  706. XCTAssert([db open], @"Double open failed");
  707. }
  708. - (void)testInvalid
  709. {
  710. NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
  711. NSString *path = [documentsPath stringByAppendingPathComponent:@"nonexistentfolder/test.sqlite"];
  712. FMDatabase *db = [[FMDatabase alloc] initWithPath:path];
  713. XCTAssertFalse([db open], @"open did NOT fail");
  714. }
  715. - (void)testChangingMaxBusyRetryTimeInterval
  716. {
  717. FMDatabase *db = [[FMDatabase alloc] init];
  718. XCTAssert([db open], @"open failed");
  719. NSTimeInterval originalInterval = db.maxBusyRetryTimeInterval;
  720. NSTimeInterval updatedInterval = originalInterval > 0 ? originalInterval + 1 : 1;
  721. db.maxBusyRetryTimeInterval = updatedInterval;
  722. NSTimeInterval diff = fabs(db.maxBusyRetryTimeInterval - updatedInterval);
  723. XCTAssert(diff < 1e-5, @"interval should have changed %.1f", diff);
  724. }
  725. - (void)testChangingMaxBusyRetryTimeIntervalDatabaseNotOpened
  726. {
  727. FMDatabase *db = [[FMDatabase alloc] init];
  728. // XCTAssert([db open], @"open failed"); // deliberately not opened
  729. NSTimeInterval originalInterval = db.maxBusyRetryTimeInterval;
  730. NSTimeInterval updatedInterval = originalInterval > 0 ? originalInterval + 1 : 1;
  731. db.maxBusyRetryTimeInterval = updatedInterval;
  732. XCTAssertNotEqual(originalInterval, db.maxBusyRetryTimeInterval, @"interval should not have changed");
  733. }
  734. - (void)testZeroMaxBusyRetryTimeInterval
  735. {
  736. FMDatabase *db = [[FMDatabase alloc] init];
  737. XCTAssert([db open], @"open failed");
  738. NSTimeInterval updatedInterval = 0;
  739. db.maxBusyRetryTimeInterval = updatedInterval;
  740. XCTAssertEqual(db.maxBusyRetryTimeInterval, updatedInterval, @"busy handler not disabled");
  741. }
  742. - (void)testCloseOpenResultSets
  743. {
  744. FMDatabase *db = [[FMDatabase alloc] init];
  745. XCTAssert([db open], @"open failed");
  746. XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
  747. NSString *value = @"baz";
  748. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
  749. FMResultSet *rs = [db executeQuery:@"select bar from foo"];
  750. [db closeOpenResultSets];
  751. XCTAssertFalse([rs next], @"step should have failed");
  752. }
  753. - (void)testGoodConnection
  754. {
  755. FMDatabase *db = [[FMDatabase alloc] init];
  756. XCTAssert([db open], @"open failed");
  757. XCTAssert([db goodConnection], @"no good connection");
  758. }
  759. - (void)testBadConnection
  760. {
  761. FMDatabase *db = [[FMDatabase alloc] init];
  762. // XCTAssert([db open], @"open failed"); // deliberately did not open
  763. XCTAssertFalse([db goodConnection], @"no good connection");
  764. }
  765. - (void)testLastRowId
  766. {
  767. FMDatabase *db = [[FMDatabase alloc] init];
  768. XCTAssert([db open], @"open failed");
  769. XCTAssert([db executeUpdate:@"create table foo (foo_id integer primary key autoincrement, bar text)"], @"create failed");
  770. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"baz"]], @"insert failed");
  771. sqlite3_int64 firstRowId = [db lastInsertRowId];
  772. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"qux"]], @"insert failed");
  773. sqlite3_int64 secondRowId = [db lastInsertRowId];
  774. XCTAssertEqual(secondRowId - firstRowId, 1, @"rowid should have incremented");
  775. }
  776. - (void)testChanges
  777. {
  778. FMDatabase *db = [[FMDatabase alloc] init];
  779. XCTAssert([db open], @"open failed");
  780. XCTAssert([db executeUpdate:@"create table foo (foo_id integer primary key autoincrement, bar text)"], @"create failed");
  781. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"baz"]], @"insert failed");
  782. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"qux"]], @"insert failed");
  783. XCTAssert([db executeUpdate:@"update foo set bar = ?" withArgumentsInArray:@[@"xxx"]], @"insert failed");
  784. int changes = [db changes];
  785. XCTAssertEqual(changes, 2, @"two rows should have incremented \(%ld)", (long)changes);
  786. }
  787. - (void)testBind {
  788. FMDatabase *db = [[FMDatabase alloc] init];
  789. XCTAssert([db open], @"open failed");
  790. XCTAssert([db executeUpdate:@"create table foo (id integer primary key autoincrement, a numeric)"], @"create failed");
  791. NSNumber *insertedValue;
  792. NSNumber *retrievedValue;
  793. insertedValue = [NSNumber numberWithChar:51];
  794. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  795. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  796. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  797. insertedValue = [NSNumber numberWithUnsignedChar:52];
  798. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  799. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  800. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  801. insertedValue = [NSNumber numberWithShort:53];
  802. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  803. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  804. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  805. insertedValue = [NSNumber numberWithUnsignedShort:54];
  806. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  807. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  808. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  809. insertedValue = [NSNumber numberWithInt:54];
  810. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  811. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  812. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  813. insertedValue = [NSNumber numberWithUnsignedInt:55];
  814. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  815. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  816. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  817. insertedValue = [NSNumber numberWithLong:56];
  818. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  819. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  820. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  821. insertedValue = [NSNumber numberWithUnsignedLong:57];
  822. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  823. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  824. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  825. insertedValue = [NSNumber numberWithLongLong:56];
  826. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  827. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  828. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  829. insertedValue = [NSNumber numberWithUnsignedLongLong:57];
  830. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  831. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  832. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  833. insertedValue = [NSNumber numberWithFloat:58];
  834. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  835. retrievedValue = @([db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  836. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  837. insertedValue = [NSNumber numberWithDouble:59];
  838. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  839. retrievedValue = @([db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  840. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  841. insertedValue = @TRUE;
  842. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  843. retrievedValue = @([db boolForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  844. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  845. }
  846. - (void)testFormatStrings {
  847. FMDatabase *db = [[FMDatabase alloc] init];
  848. XCTAssert([db open], @"open failed");
  849. XCTAssert([db executeUpdate:@"create table foo (id integer primary key autoincrement, a numeric)"], @"create failed");
  850. BOOL success;
  851. char insertedChar = 'A';
  852. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%c)", insertedChar];
  853. XCTAssert(success, @"insert failed");
  854. const char *retrievedChar = [[db stringForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])] UTF8String];
  855. XCTAssertEqual(insertedChar, retrievedChar[0], @"values don't match");
  856. const char *insertedString = "baz";
  857. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%s)", insertedString];
  858. XCTAssert(success, @"insert failed");
  859. const char *retrievedString = [[db stringForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])] UTF8String];
  860. XCTAssert(strcmp(insertedString, retrievedString) == 0, @"values don't match");
  861. int insertedInt = 42;
  862. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%d)", insertedInt];
  863. XCTAssert(success, @"insert failed");
  864. int retrievedInt = [db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  865. XCTAssertEqual(insertedInt, retrievedInt, @"values don't match");
  866. char insertedUnsignedInt = 43;
  867. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%u)", insertedUnsignedInt];
  868. XCTAssert(success, @"insert failed");
  869. char retrievedUnsignedInt = [db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  870. XCTAssertEqual(insertedUnsignedInt, retrievedUnsignedInt, @"values don't match");
  871. float insertedFloat = 44;
  872. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%f)", insertedFloat];
  873. XCTAssert(success, @"insert failed");
  874. float retrievedFloat = [db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  875. XCTAssertEqual(insertedFloat, retrievedFloat, @"values don't match");
  876. unsigned long long insertedUnsignedLongLong = 45;
  877. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%llu)", insertedUnsignedLongLong];
  878. XCTAssert(success, @"insert failed");
  879. unsigned long long retrievedUnsignedLongLong = [db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  880. XCTAssertEqual(insertedUnsignedLongLong, retrievedUnsignedLongLong, @"values don't match");
  881. }
  882. - (void)testStepError {
  883. FMDatabase *db = [[FMDatabase alloc] init];
  884. XCTAssert([db open], @"open failed");
  885. XCTAssert([db executeUpdate:@"create table foo (id integer primary key)"], @"create failed");
  886. XCTAssert([db executeUpdate:@"insert into foo (id) values (?)" values:@[@1] error:nil], @"create failed");
  887. NSError *error;
  888. BOOL success = [db executeUpdate:@"insert into foo (id) values (?)" values:@[@1] error:&error];
  889. XCTAssertFalse(success, @"insert of duplicate key should have failed");
  890. XCTAssertNotNil(error, @"error object should have been generated");
  891. XCTAssertEqual(error.code, 19, @"error code 19 should have been generated");
  892. }
  893. @end