FMDatabaseTests.m 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554
  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. [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"];
  21. [db beginTransaction];
  22. int i = 0;
  23. while (i++ < 20) {
  24. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  25. @"hi'", // look! I put in a ', and I'm not escaping it!
  26. [NSString stringWithFormat:@"number %d", i],
  27. [NSNumber numberWithInt:i],
  28. [NSDate date],
  29. [NSNumber numberWithFloat:2.2f]];
  30. }
  31. [db commit];
  32. // do it again, just because
  33. [db beginTransaction];
  34. i = 0;
  35. while (i++ < 20) {
  36. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  37. @"hi again'", // look! I put in a ', and I'm not escaping it!
  38. [NSString stringWithFormat:@"number %d", i],
  39. [NSNumber numberWithInt:i],
  40. [NSDate date],
  41. [NSNumber numberWithFloat:2.2f]];
  42. }
  43. [db commit];
  44. [db executeUpdate:@"create table t3 (a somevalue)"];
  45. [db beginTransaction];
  46. for (int i=0; i < 20; i++) {
  47. [db executeUpdate:@"insert into t3 (a) values (?)", [NSNumber numberWithInt:i]];
  48. }
  49. [db commit];
  50. }
  51. - (void)setUp{
  52. [super setUp];
  53. // Put setup code here. This method is called before the invocation of each test method in the class.
  54. }
  55. - (void)tearDown {
  56. // Put teardown code here. This method is called after the invocation of each test method in the class.
  57. [super tearDown];
  58. }
  59. - (void)testOpenWithVFS {
  60. // create custom vfs
  61. sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
  62. vfs.zName = "MyCustomVFS";
  63. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
  64. // use custom vfs to open a in memory database
  65. FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
  66. [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
  67. XCTAssertFalse([db hadError], @"Open with a custom VFS should have succeeded");
  68. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_unregister(&vfs));
  69. }
  70. - (void)testURLOpen {
  71. NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  72. NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  73. FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
  74. XCTAssert(db, @"Database should be returned");
  75. XCTAssertTrue([db open], @"Open should succeed");
  76. XCTAssertEqualObjects([db databaseURL], fileURL);
  77. XCTAssertTrue([db close], @"close should succeed");
  78. [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
  79. }
  80. - (void)testFailOnOpenWithUnknownVFS {
  81. FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
  82. [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"UnknownVFS"];
  83. XCTAssertTrue([db hadError], @"Should have failed");
  84. }
  85. - (void)testFailOnUnopenedDatabase {
  86. [self.db close];
  87. XCTAssertNil([self.db executeQuery:@"select * from table"], @"Shouldn't get results from an empty table");
  88. XCTAssertTrue([self.db hadError], @"Should have failed");
  89. }
  90. - (void)testFailOnBadStatement {
  91. XCTAssertFalse([self.db executeUpdate:@"blah blah blah"], @"Invalid statement should fail");
  92. XCTAssertTrue([self.db hadError], @"Should have failed");
  93. }
  94. - (void)testFailOnBadStatementWithError
  95. {
  96. NSError *error = nil;
  97. XCTAssertFalse([self.db executeUpdate:@"blah blah blah" withErrorAndBindings:&error], @"Invalid statement should fail");
  98. XCTAssertNotNil(error, @"Should have a non-nil NSError");
  99. XCTAssertEqual([error code], (NSInteger)SQLITE_ERROR, @"Error should be SQLITE_ERROR");
  100. }
  101. - (void)testPragmaJournalMode
  102. {
  103. FMResultSet *ps = [self.db executeQuery:@"pragma journal_mode=delete"];
  104. XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
  105. XCTAssertNotNil(ps, @"Result set should be non-nil");
  106. XCTAssertTrue([ps next], @"Result set should have a next result");
  107. [ps close];
  108. }
  109. - (void)testPragmaPageSize
  110. {
  111. [self.db executeUpdate:@"PRAGMA page_size=2048"];
  112. XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
  113. }
  114. - (void)testVacuum
  115. {
  116. [self.db executeUpdate:@"VACUUM"];
  117. XCTAssertFalse([self.db hadError], @"VACUUM should have succeeded");
  118. }
  119. - (void)testSelectULL
  120. {
  121. // Unsigned long long
  122. [self.db executeUpdate:@"create table ull (a integer)"];
  123. [self.db executeUpdate:@"insert into ull (a) values (?)", [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]];
  124. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  125. FMResultSet *rs = [self.db executeQuery:@"select a from ull"];
  126. while ([rs next]) {
  127. XCTAssertEqual([rs unsignedLongLongIntForColumnIndex:0], ULLONG_MAX, @"Result should be ULLONG_MAX");
  128. XCTAssertEqual([rs unsignedLongLongIntForColumn:@"a"], ULLONG_MAX, @"Result should be ULLONG_MAX");
  129. }
  130. [rs close];
  131. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  132. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  133. }
  134. - (void)testSelectByColumnName
  135. {
  136. FMResultSet *rs = [self.db executeQuery:@"select rowid,* from test where a = ?", @"hi"];
  137. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  138. while ([rs next]) {
  139. [rs intForColumn:@"c"];
  140. XCTAssertNotNil([rs stringForColumn:@"b"], @"Should have non-nil string for 'b'");
  141. XCTAssertNotNil([rs stringForColumn:@"a"], @"Should have non-nil string for 'a'");
  142. XCTAssertNotNil([rs stringForColumn:@"rowid"], @"Should have non-nil string for 'rowid'");
  143. XCTAssertNotNil([rs dateForColumn:@"d"], @"Should have non-nil date for 'd'");
  144. [rs doubleForColumn:@"d"];
  145. [rs doubleForColumn:@"e"];
  146. XCTAssertEqualObjects([rs columnNameForIndex:0], @"rowid", @"Wrong column name for result set column number");
  147. XCTAssertEqualObjects([rs columnNameForIndex:1], @"a", @"Wrong column name for result set column number");
  148. }
  149. [rs close];
  150. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  151. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  152. }
  153. - (void)testInvalidColumnNames
  154. {
  155. FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
  156. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  157. NSString *invalidColumnName = @"foobar";
  158. while ([rs next]) {
  159. XCTAssertNil(rs[invalidColumnName], @"Invalid column name should return nil");
  160. XCTAssertNil([rs stringForColumn:invalidColumnName], @"Invalid column name should return nil");
  161. XCTAssertEqual([rs UTF8StringForColumn:invalidColumnName], (const unsigned char *)0, @"Invalid column name should return nil");
  162. XCTAssertNil([rs dateForColumn:invalidColumnName], @"Invalid column name should return nil");
  163. XCTAssertNil([rs dataForColumn:invalidColumnName], @"Invalid column name should return nil");
  164. XCTAssertNil([rs dataNoCopyForColumn:invalidColumnName], @"Invalid column name should return nil");
  165. XCTAssertNil([rs objectForColumn:invalidColumnName], @"Invalid column name should return nil");
  166. }
  167. [rs close];
  168. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  169. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  170. }
  171. - (void)testInvalidColumnIndexes
  172. {
  173. FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
  174. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  175. int invalidColumnIndex = 999;
  176. while ([rs next]) {
  177. XCTAssertNil(rs[invalidColumnIndex], @"Invalid column name should return nil");
  178. XCTAssertNil([rs stringForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
  179. XCTAssertEqual([rs UTF8StringForColumnIndex:invalidColumnIndex], (const unsigned char *)0, @"Invalid column name should return nil");
  180. XCTAssertNil([rs dateForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
  181. XCTAssertNil([rs dataForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
  182. XCTAssertNil([rs dataNoCopyForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
  183. XCTAssertNil([rs objectForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
  184. }
  185. [rs close];
  186. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  187. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  188. }
  189. - (void)testBusyRetryTimeout
  190. {
  191. [self.db executeUpdate:@"create table t1 (a integer)"];
  192. [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
  193. [self.db setMaxBusyRetryTimeInterval:2];
  194. FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
  195. [newDB open];
  196. FMResultSet *rs = [newDB executeQuery:@"select rowid,* from test where a = ?", @"hi'"];
  197. [rs next]; // just grab one... which will keep the db locked
  198. XCTAssertFalse([self.db executeUpdate:@"insert into t1 values (5)"], @"Insert should fail because the db is locked by a read");
  199. XCTAssertEqual([self.db lastErrorCode], SQLITE_BUSY, @"SQLITE_BUSY should be the last error");
  200. [rs close];
  201. [newDB close];
  202. XCTAssertTrue([self.db executeUpdate:@"insert into t1 values (5)"], @"The database shouldn't be locked at this point");
  203. }
  204. - (void)testCaseSensitiveResultDictionary
  205. {
  206. // case sensitive result dictionary test
  207. [self.db executeUpdate:@"create table cs (aRowName integer, bRowName text)"];
  208. [self.db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)", [NSNumber numberWithInt:1], @"hello"];
  209. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  210. FMResultSet *rs = [self.db executeQuery:@"select * from cs"];
  211. while ([rs next]) {
  212. NSDictionary *d = [rs resultDictionary];
  213. XCTAssertNotNil([d objectForKey:@"aRowName"], @"aRowName should be non-nil");
  214. XCTAssertNil([d objectForKey:@"arowname"], @"arowname should be nil");
  215. XCTAssertNotNil([d objectForKey:@"bRowName"], @"bRowName should be non-nil");
  216. XCTAssertNil([d objectForKey:@"browname"], @"browname should be nil");
  217. }
  218. [rs close];
  219. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  220. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  221. }
  222. - (void)testBoolInsert
  223. {
  224. [self.db executeUpdate:@"create table btest (aRowName integer)"];
  225. [self.db executeUpdate:@"insert into btest (aRowName) values (?)", [NSNumber numberWithBool:YES]];
  226. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  227. FMResultSet *rs = [self.db executeQuery:@"select * from btest"];
  228. while ([rs next]) {
  229. XCTAssertTrue([rs boolForColumnIndex:0], @"first column should be true.");
  230. XCTAssertTrue([rs intForColumnIndex:0] == 1, @"first column should be equal to 1 - it was %d.", [rs intForColumnIndex:0]);
  231. }
  232. [rs close];
  233. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  234. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  235. }
  236. - (void)testNamedParametersCount
  237. {
  238. XCTAssertTrue([self.db executeUpdate:@"create table namedparamcounttest (a text, b text, c integer, d double)"]);
  239. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  240. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  241. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  242. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  243. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  244. XCTAssertTrue([self.db executeUpdate:@"insert into namedparamcounttest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  245. FMResultSet *rs = [self.db executeQuery:@"select * from namedparamcounttest"];
  246. XCTAssertNotNil(rs);
  247. [rs next];
  248. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
  249. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  250. XCTAssertEqual([rs intForColumn:@"c"], 1);
  251. XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
  252. [rs close];
  253. // note that at this point, dictionaryArgs has way more values than we need, but the query should still work since
  254. // a is in there, and that's all we need.
  255. rs = [self.db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs];
  256. XCTAssertNotNil(rs);
  257. XCTAssertTrue([rs next]);
  258. [rs close];
  259. // ***** Please note the following codes *****
  260. dictionaryArgs = [NSMutableDictionary dictionary];
  261. [dictionaryArgs setObject:@"NewText1" forKey:@"a"];
  262. [dictionaryArgs setObject:@"NewText2" forKey:@"b"];
  263. [dictionaryArgs setObject:@"OneMoreText" forKey:@"OneMore"];
  264. XCTAssertTrue([self.db executeUpdate:@"update namedparamcounttest set a = :a, b = :b where b = 'Text2'" withParameterDictionary:dictionaryArgs]);
  265. }
  266. - (void)testBlobs
  267. {
  268. [self.db executeUpdate:@"create table blobTable (a text, b blob)"];
  269. // let's read an image from safari's app bundle.
  270. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  271. if (safariCompass) {
  272. [self.db executeUpdate:@"insert into blobTable (a, b) values (?, ?)", @"safari's compass", safariCompass];
  273. FMResultSet *rs = [self.db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"];
  274. XCTAssertTrue([rs next]);
  275. NSData *readData = [rs dataForColumn:@"b"];
  276. XCTAssertEqualObjects(readData, safariCompass);
  277. // ye shall read the header for this function, or suffer the consequences.
  278. NSData *readDataNoCopy = [rs dataNoCopyForColumn:@"b"];
  279. XCTAssertEqualObjects(readDataNoCopy, safariCompass);
  280. [rs close];
  281. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  282. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  283. }
  284. }
  285. - (void)testNullValues
  286. {
  287. [self.db executeUpdate:@"create table t2 (a integer, b integer)"];
  288. BOOL result = [self.db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]];
  289. XCTAssertTrue(result, @"Failed to insert a nil value");
  290. FMResultSet *rs = [self.db executeQuery:@"select * from t2"];
  291. while ([rs next]) {
  292. XCTAssertNil([rs stringForColumnIndex:0], @"Wasn't able to retrieve a null string");
  293. XCTAssertEqualObjects([rs stringForColumnIndex:1], @"5");
  294. }
  295. [rs close];
  296. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  297. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  298. }
  299. - (void)testNestedResultSets
  300. {
  301. FMResultSet *rs = [self.db executeQuery:@"select * from t3"];
  302. while ([rs next]) {
  303. int foo = [rs intForColumnIndex:0];
  304. int newVal = foo + 100;
  305. [self.db executeUpdate:@"update t3 set a = ? where a = ?", [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]];
  306. FMResultSet *rs2 = [self.db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]];
  307. [rs2 next];
  308. XCTAssertEqual([rs2 intForColumnIndex:0], newVal);
  309. [rs2 close];
  310. }
  311. [rs close];
  312. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  313. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  314. }
  315. - (void)testNSNullInsertion
  316. {
  317. [self.db executeUpdate:@"create table nulltest (a text, b text)"];
  318. [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", [NSNull null], @"a"];
  319. [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", nil, @"b"];
  320. FMResultSet *rs = [self.db executeQuery:@"select * from nulltest"];
  321. while ([rs next]) {
  322. XCTAssertNil([rs stringForColumnIndex:0]);
  323. XCTAssertNotNil([rs stringForColumnIndex:1]);
  324. }
  325. [rs close];
  326. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  327. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  328. }
  329. - (void)testNullDates
  330. {
  331. NSDate *date = [NSDate date];
  332. [self.db executeUpdate:@"create table datetest (a double, b double, c double)"];
  333. [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date];
  334. FMResultSet *rs = [self.db executeQuery:@"select * from datetest"];
  335. XCTAssertNotNil(rs);
  336. while ([rs next]) {
  337. NSDate *b = [rs dateForColumnIndex:1];
  338. NSDate *c = [rs dateForColumnIndex:2];
  339. XCTAssertNil([rs dateForColumnIndex:0]);
  340. XCTAssertNotNil(c, @"zero date shouldn't be nil");
  341. XCTAssertEqualWithAccuracy([b timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second");
  342. XCTAssertEqualWithAccuracy([c timeIntervalSince1970], 0.0, 1.0, @"Dates should be the same to within a second");
  343. }
  344. [rs close];
  345. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  346. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  347. }
  348. - (void)testLotsOfNULLs
  349. {
  350. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  351. if (!safariCompass)
  352. return;
  353. [self.db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"];
  354. [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]];
  355. [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]];
  356. FMResultSet *rs = [self.db executeQuery:@"select * from nulltest2"];
  357. while ([rs next]) {
  358. int i = [rs intForColumnIndex:2];
  359. if (i == 12) {
  360. // it's the first row we inserted.
  361. XCTAssertFalse([rs columnIndexIsNull:0]);
  362. XCTAssertFalse([rs columnIndexIsNull:1]);
  363. XCTAssertFalse([rs columnIndexIsNull:2]);
  364. XCTAssertFalse([rs columnIndexIsNull:3]);
  365. XCTAssertFalse([rs columnIndexIsNull:4]);
  366. XCTAssertTrue( [rs columnIndexIsNull:5]);
  367. XCTAssertEqualObjects([rs dataForColumn:@"d"], safariCompass);
  368. XCTAssertNil([rs dataForColumn:@"notthere"]);
  369. XCTAssertNil([rs stringForColumnIndex:-2], @"Negative columns should return nil results");
  370. XCTAssertTrue([rs boolForColumnIndex:4]);
  371. XCTAssertTrue([rs boolForColumn:@"b"]);
  372. XCTAssertEqualWithAccuracy(4.4, [rs doubleForColumn:@"f"], 0.0000001, @"Saving a float and returning it as a double shouldn't change the result much");
  373. XCTAssertEqual([rs intForColumn:@"i"], 12);
  374. XCTAssertEqual([rs intForColumnIndex:2], 12);
  375. XCTAssertEqual([rs intForColumnIndex:12], 0, @"Non-existent columns should return zero for ints");
  376. XCTAssertEqual([rs intForColumn:@"notthere"], 0, @"Non-existent columns should return zero for ints");
  377. XCTAssertEqual([rs longForColumn:@"i"], 12l);
  378. XCTAssertEqual([rs longLongIntForColumn:@"i"], 12ll);
  379. }
  380. else {
  381. // let's test various null things.
  382. XCTAssertTrue([rs columnIndexIsNull:0]);
  383. XCTAssertTrue([rs columnIndexIsNull:1]);
  384. XCTAssertTrue([rs columnIndexIsNull:2]);
  385. XCTAssertTrue([rs columnIndexIsNull:3]);
  386. XCTAssertTrue([rs columnIndexIsNull:4]);
  387. XCTAssertTrue([rs columnIndexIsNull:5]);
  388. XCTAssertNil([rs dataForColumn:@"d"]);
  389. }
  390. }
  391. [rs close];
  392. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  393. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  394. }
  395. - (void)testUTF8Strings
  396. {
  397. [self.db executeUpdate:@"create table utest (a text)"];
  398. [self.db executeUpdate:@"insert into utest values (?)", @"/übertest"];
  399. FMResultSet *rs = [self.db executeQuery:@"select * from utest where a = ?", @"/übertest"];
  400. XCTAssertTrue([rs next]);
  401. [rs close];
  402. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  403. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  404. }
  405. - (void)testArgumentsInArray
  406. {
  407. [self.db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"];
  408. [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]];
  409. [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]];
  410. FMResultSet *rs = [self.db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]];
  411. XCTAssertTrue([rs next]);
  412. XCTAssertTrue([rs hasAnotherRow]);
  413. XCTAssertFalse([self.db hadError]);
  414. XCTAssertEqualObjects([rs stringForColumnIndex:0], @"one");
  415. XCTAssertEqual([rs intForColumnIndex:1], 2);
  416. XCTAssertTrue([rs next]);
  417. XCTAssertEqual([rs intForColumnIndex:1], 3);
  418. XCTAssertFalse([rs next]);
  419. XCTAssertFalse([rs hasAnotherRow]);
  420. }
  421. - (void)testColumnNamesContainingPeriods
  422. {
  423. XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]);
  424. [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"];
  425. FMResultSet *rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"];
  426. XCTAssertNotNil(rs);
  427. XCTAssertTrue([rs next]);
  428. XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
  429. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
  430. XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumn:@"b"], "two"), 0, @"String comparison should return zero");
  431. [rs close];
  432. // let's try these again, with the withArgumentsInArray: variation
  433. XCTAssertTrue([self.db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]);
  434. XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]);
  435. [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]];
  436. rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]];
  437. XCTAssertNotNil(rs);
  438. XCTAssertTrue([rs next]);
  439. XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
  440. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
  441. XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumn:@"b"], "two"), 0, @"String comparison should return zero");
  442. [rs close];
  443. }
  444. - (void)testFormatStringParsing
  445. {
  446. XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  447. [self.db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234ll];
  448. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42];
  449. XCTAssertNotNil(rs);
  450. XCTAssertTrue([rs next]);
  451. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"text");
  452. XCTAssertEqual([rs intForColumn:@"b"], 42);
  453. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"BLOB");
  454. XCTAssertEqualObjects([rs stringForColumn:@"d"], @"d");
  455. XCTAssertEqual([rs longLongIntForColumn:@"e"], 12345678901234ll);
  456. [rs close];
  457. }
  458. - (void)testFormatStringParsingWithSizePrefixes
  459. {
  460. XCTAssertTrue([self.db executeUpdate:@"create table t55 (a text, b int, c float)"]);
  461. short testShort = -4;
  462. float testFloat = 5.5;
  463. [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat];
  464. unsigned short testUShort = 6;
  465. [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat];
  466. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"];
  467. XCTAssertNotNil(rs);
  468. XCTAssertTrue([rs next]);
  469. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
  470. XCTAssertEqual([rs intForColumn:@"b"], -4);
  471. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
  472. XCTAssertTrue([rs next]);
  473. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
  474. XCTAssertEqual([rs intForColumn:@"b"], 6);
  475. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
  476. [rs close];
  477. }
  478. - (void)testFormatStringParsingWithNilValue
  479. {
  480. XCTAssertTrue([self.db executeUpdate:@"create table tatwhat (a text)"]);
  481. BOOL worked = [self.db executeUpdateWithFormat:@"insert into tatwhat values(%@)", nil];
  482. XCTAssertTrue(worked);
  483. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from tatwhat"];
  484. XCTAssertNotNil(rs);
  485. XCTAssertTrue([rs next]);
  486. XCTAssertTrue([rs columnIndexIsNull:0]);
  487. XCTAssertFalse([rs next]);
  488. }
  489. - (void)testUpdateWithErrorAndBindings
  490. {
  491. XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  492. NSError *err = nil;
  493. BOOL result = [self.db executeUpdate:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]];
  494. XCTAssertTrue(result);
  495. }
  496. - (void)testSelectWithEmptyArgumentsArray
  497. {
  498. FMResultSet *rs = [self.db executeQuery:@"select * from test where a=?" withArgumentsInArray:@[]];
  499. XCTAssertNil(rs);
  500. }
  501. - (void)testDatabaseAttach
  502. {
  503. NSFileManager *fileManager = [NSFileManager new];
  504. [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil];
  505. FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"];
  506. XCTAssertTrue([dbB open]);
  507. XCTAssertTrue([dbB executeUpdate:@"create table attached (a text)"]);
  508. XCTAssertTrue(([dbB executeUpdate:@"insert into attached values (?)", @"test"]));
  509. XCTAssertTrue([dbB close]);
  510. [self.db executeUpdate:@"attach database '/tmp/attachme.db' as attack"];
  511. FMResultSet *rs = [self.db executeQuery:@"select * from attack.attached"];
  512. XCTAssertNotNil(rs);
  513. XCTAssertTrue([rs next]);
  514. [rs close];
  515. }
  516. - (void)testNamedParameters
  517. {
  518. // -------------------------------------------------------------------------------
  519. // Named parameters.
  520. XCTAssertTrue([self.db executeUpdate:@"create table namedparamtest (a text, b text, c integer, d double)"]);
  521. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  522. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  523. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  524. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  525. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  526. XCTAssertTrue([self.db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  527. FMResultSet *rs = [self.db executeQuery:@"select * from namedparamtest"];
  528. XCTAssertNotNil(rs);
  529. XCTAssertTrue([rs next]);
  530. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
  531. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  532. XCTAssertEqual([rs intForColumn:@"c"], 1);
  533. XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
  534. [rs close];
  535. dictionaryArgs = [NSMutableDictionary dictionary];
  536. [dictionaryArgs setObject:@"Text2" forKey:@"blah"];
  537. rs = [self.db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs];
  538. XCTAssertNotNil(rs);
  539. XCTAssertTrue([rs next]);
  540. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  541. [rs close];
  542. }
  543. - (void)testPragmaDatabaseList
  544. {
  545. FMResultSet *rs = [self.db executeQuery:@"pragma database_list"];
  546. int counter = 0;
  547. while ([rs next]) {
  548. counter++;
  549. XCTAssertEqualObjects([rs stringForColumn:@"file"], self.databasePath);
  550. }
  551. XCTAssertEqual(counter, 1, @"Only one database should be attached");
  552. }
  553. - (void)testCachedStatementsInUse
  554. {
  555. [self.db setShouldCacheStatements:true];
  556. [self.db executeUpdate:@"CREATE TABLE testCacheStatements(key INTEGER PRIMARY KEY, value INTEGER)"];
  557. [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (1, 2)"];
  558. [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (2, 4)"];
  559. XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
  560. XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
  561. }
  562. - (void)testStatementCachingWorks
  563. {
  564. [self.db executeUpdate:@"CREATE TABLE testStatementCaching ( value INTEGER )"];
  565. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
  566. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
  567. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (2)"];
  568. [self.db setShouldCacheStatements:YES];
  569. // two iterations.
  570. // the first time through no statements will be from the cache.
  571. // the second time through all statements come from the cache.
  572. for (int i = 1; i <= 2; i++ ) {
  573. FMResultSet* rs1 = [self.db executeQuery: @"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @1]; // results in 2 rows...
  574. XCTAssertNotNil(rs1);
  575. XCTAssertTrue([rs1 next]);
  576. // confirm that we're seeing the benefits of caching.
  577. XCTAssertEqual([[rs1 statement] useCount], (long)i);
  578. FMResultSet* rs2 = [self.db executeQuery:@"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @2]; // results in 1 row
  579. XCTAssertNotNil(rs2);
  580. XCTAssertTrue([rs2 next]);
  581. XCTAssertEqual([[rs2 statement] useCount], (long)i);
  582. // 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.
  583. XCTAssertTrue([rs1 next]);
  584. [rs1 close];
  585. [rs2 close];
  586. }
  587. }
  588. /*
  589. Test the date format
  590. */
  591. - (void)testDateFormat
  592. {
  593. void (^testOneDateFormat)(FMDatabase *, NSDate *) = ^( FMDatabase *db, NSDate *testDate ){
  594. [db executeUpdate:@"DROP TABLE IF EXISTS test_format"];
  595. [db executeUpdate:@"CREATE TABLE test_format ( test TEXT )"];
  596. [db executeUpdate:@"INSERT INTO test_format(test) VALUES (?)", testDate];
  597. FMResultSet *rs = [db executeQuery:@"SELECT test FROM test_format"];
  598. XCTAssertNotNil(rs);
  599. XCTAssertTrue([rs next]);
  600. XCTAssertEqualObjects([rs dateForColumnIndex:0], testDate);
  601. [rs close];
  602. };
  603. NSDateFormatter *fmt = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  604. NSDate *testDate = [fmt dateFromString:@"2013-02-20 12:00:00"];
  605. // test timestamp dates (ensuring our change does not break those)
  606. testOneDateFormat(self.db,testDate);
  607. // now test the string-based timestamp
  608. [self.db setDateFormat:fmt];
  609. testOneDateFormat(self.db, testDate);
  610. }
  611. - (void)testColumnNameMap
  612. {
  613. XCTAssertTrue([self.db executeUpdate:@"create table colNameTest (a, b, c, d)"]);
  614. XCTAssertTrue([self.db executeUpdate:@"insert into colNameTest values (1, 2, 3, 4)"]);
  615. FMResultSet *ars = [self.db executeQuery:@"select * from colNameTest"];
  616. XCTAssertNotNil(ars);
  617. NSDictionary *d = [ars columnNameToIndexMap];
  618. XCTAssertEqual([d count], (NSUInteger)4);
  619. XCTAssertEqualObjects([d objectForKey:@"a"], @0);
  620. XCTAssertEqualObjects([d objectForKey:@"b"], @1);
  621. XCTAssertEqualObjects([d objectForKey:@"c"], @2);
  622. XCTAssertEqualObjects([d objectForKey:@"d"], @3);
  623. }
  624. - (void)testCustomStringFunction {
  625. [self createCustomFunctions];
  626. FMResultSet *ars = [self.db executeQuery:@"SELECT RemoveDiacritics(?)", @"José"];
  627. if (![ars next]) {
  628. XCTFail("Should have returned value");
  629. return;
  630. }
  631. NSString *result = [ars stringForColumnIndex:0];
  632. XCTAssertEqualObjects(result, @"Jose");
  633. }
  634. - (void)testFailCustomStringFunction {
  635. [self createCustomFunctions];
  636. FMResultSet *rs = [self.db executeQuery:@"SELECT RemoveDiacritics(?)", @(M_PI)];
  637. XCTAssert(rs, @"Prepare should have succeeded");
  638. NSError *error;
  639. BOOL success = [rs nextWithError:&error];
  640. XCTAssertFalse(success, @"'next' should have failed");
  641. XCTAssertEqualObjects(error.localizedDescription, @"Expected text");
  642. rs = [self.db executeQuery:@"SELECT RemoveDiacritics('jose','ortega')"];
  643. XCTAssertNil(rs);
  644. error = [self.db lastError];
  645. XCTAssert([error.localizedDescription containsString:@"wrong number of arguments"], @"Should get wrong number of arguments error, but got '%@'", error.localizedDescription);
  646. }
  647. - (void)testCustomDoubleFunction {
  648. [self createCustomFunctions];
  649. FMResultSet *rs = [self.db executeQuery:@"SELECT Hypotenuse(?, ?)", @(3.0), @(4.0)];
  650. if (![rs next]) {
  651. XCTFail("Should have returned value");
  652. return;
  653. }
  654. double value = [rs doubleForColumnIndex:0];
  655. XCTAssertEqual(value, 5.0);
  656. }
  657. - (void)testCustomIntFunction {
  658. [self createCustomFunctions];
  659. FMResultSet *rs = [self.db executeQuery:@"SELECT Hypotenuse(?, ?)", @(3), @(4)];
  660. if (![rs next]) {
  661. XCTFail("Should have returned value");
  662. return;
  663. }
  664. int value = [rs intForColumnIndex:0];
  665. XCTAssertEqual(value, 5);
  666. }
  667. - (void)testFailCustomNumericFunction {
  668. [self createCustomFunctions];
  669. FMResultSet *rs = [self.db executeQuery:@"SELECT Hypotenuse(?, ?)", @"foo", @"bar"];
  670. NSError *error;
  671. if ([rs nextWithError:&error]) {
  672. XCTFail("Should have failed");
  673. return;
  674. }
  675. XCTAssertEqualObjects(error.localizedDescription, @"Expected numeric");
  676. rs = [self.db executeQuery:@"SELECT Hypotenuse(?)", @(3.0)];
  677. XCTAssertNil(rs, @"Should fail for wrong number of arguments");
  678. error = [self.db lastError];
  679. XCTAssert([error.localizedDescription containsString:@"wrong number of arguments"], @"Should get wrong number of arguments error, but got '%@'", error.localizedDescription);
  680. }
  681. - (void)testCustomDataFunction {
  682. [self createCustomFunctions];
  683. NSMutableData *data = [NSMutableData data];
  684. for (NSInteger i = 0; i < 256; i++) {
  685. uint8_t byte = i;
  686. [data appendBytes:&byte length:1];
  687. }
  688. FMResultSet *rs = [self.db executeQuery:@"SELECT SetAlternatingByteToOne(?)", data];
  689. if (![rs next]) {
  690. XCTFail("Should have returned value");
  691. return;
  692. }
  693. NSData *result = [rs dataForColumnIndex:0];
  694. XCTAssert(result, @"should have result");
  695. XCTAssertEqual(result.length, (unsigned long)256);
  696. for (NSInteger i = 0; i < 256; i++) {
  697. uint8_t byte;
  698. [result getBytes:&byte range:NSMakeRange(i, 1)];
  699. if (i % 2 == 0) {
  700. XCTAssertEqual(byte, (uint8_t)1);
  701. } else {
  702. XCTAssertEqual(byte, (uint8_t)i);
  703. }
  704. }
  705. }
  706. - (void)testFailCustomDataFunction {
  707. [self createCustomFunctions];
  708. FMResultSet *rs = [self.db executeQuery:@"SELECT SetAlternatingByteToOne(?)", @"foo"];
  709. XCTAssert(rs, @"Query should succeed");
  710. NSError *error;
  711. BOOL success = [rs nextWithError:&error];
  712. XCTAssertFalse(success, @"Performing SetAlternatingByteToOne with string should fail");
  713. XCTAssertEqualObjects(error.localizedDescription, @"Expected blob");
  714. }
  715. - (void)testCustomFunctionNullValues {
  716. [self.db makeFunctionNamed:@"FunctionThatDoesntTestTypes" arguments:1 block:^(void *context, int argc, void **argv) {
  717. NSData *data = [self.db valueData:argv[0]];
  718. XCTAssertNil(data);
  719. NSString *string = [self.db valueString:argv[0]];
  720. XCTAssertNil(string);
  721. int intValue = [self.db valueInt:argv[0]];
  722. XCTAssertEqual(intValue, 0);
  723. long longValue = [self.db valueLong:argv[0]];
  724. XCTAssertEqual(longValue, 0L);
  725. double doubleValue = [self.db valueDouble:argv[0]];
  726. XCTAssertEqual(doubleValue, 0.0);
  727. [self.db resultInt:42 context:context];
  728. }];
  729. FMResultSet *rs = [self.db executeQuery:@"SELECT FunctionThatDoesntTestTypes(?)", [NSNull null]];
  730. XCTAssert(rs, @"Creating query should succeed");
  731. NSError *error = nil;
  732. if (rs) {
  733. BOOL success = [rs nextWithError:&error];
  734. XCTAssert(success, @"Performing query should succeed");
  735. }
  736. }
  737. - (void)testCustomFunctionIntResult {
  738. [self.db makeFunctionNamed:@"IntResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  739. [self.db resultInt:42 context:context];
  740. }];
  741. FMResultSet *rs = [self.db executeQuery:@"SELECT IntResultFunction()"];
  742. XCTAssert(rs, @"Creating query should succeed");
  743. BOOL success = [rs next];
  744. XCTAssert(success, @"Performing query should succeed");
  745. XCTAssertEqual([rs intForColumnIndex:0], 42);
  746. }
  747. - (void)testCustomFunctionLongResult {
  748. [self.db makeFunctionNamed:@"LongResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  749. [self.db resultLong:42 context:context];
  750. }];
  751. FMResultSet *rs = [self.db executeQuery:@"SELECT LongResultFunction()"];
  752. XCTAssert(rs, @"Creating query should succeed");
  753. BOOL success = [rs next];
  754. XCTAssert(success, @"Performing query should succeed");
  755. XCTAssertEqual([rs longForColumnIndex:0], (long)42);
  756. }
  757. - (void)testCustomFunctionDoubleResult {
  758. [self.db makeFunctionNamed:@"DoubleResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  759. [self.db resultDouble:0.1 context:context];
  760. }];
  761. FMResultSet *rs = [self.db executeQuery:@"SELECT DoubleResultFunction()"];
  762. XCTAssert(rs, @"Creating query should succeed");
  763. BOOL success = [rs next];
  764. XCTAssert(success, @"Performing query should succeed");
  765. XCTAssertEqual([rs doubleForColumnIndex:0], 0.1);
  766. }
  767. - (void)testCustomFunctionNullResult {
  768. [self.db makeFunctionNamed:@"NullResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  769. [self.db resultNullInContext:context];
  770. }];
  771. FMResultSet *rs = [self.db executeQuery:@"SELECT NullResultFunction()"];
  772. XCTAssert(rs, @"Creating query should succeed");
  773. BOOL success = [rs next];
  774. XCTAssert(success, @"Performing query should succeed");
  775. XCTAssertEqualObjects([rs objectForColumnIndex:0], [NSNull null]);
  776. }
  777. - (void)testCustomFunctionErrorResult {
  778. [self.db makeFunctionNamed:@"ErrorResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  779. [self.db resultError:@"foo" context:context];
  780. [self.db resultErrorCode:42 context:context];
  781. }];
  782. FMResultSet *rs = [self.db executeQuery:@"SELECT ErrorResultFunction()"];
  783. XCTAssert(rs, @"Creating query should succeed");
  784. NSError *error = nil;
  785. BOOL success = [rs nextWithError:&error];
  786. XCTAssertFalse(success, @"Performing query should fail.");
  787. XCTAssertEqualObjects(error.localizedDescription, @"foo");
  788. XCTAssertEqual(error.code, 42);
  789. }
  790. - (void)testCustomFunctionTooBigErrorResult {
  791. [self.db makeFunctionNamed:@"TooBigErrorResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  792. [self.db resultErrorTooBigInContext:context];
  793. }];
  794. FMResultSet *rs = [self.db executeQuery:@"SELECT TooBigErrorResultFunction()"];
  795. XCTAssert(rs, @"Creating query should succeed");
  796. NSError *error = nil;
  797. BOOL success = [rs nextWithError:&error];
  798. XCTAssertFalse(success, @"Performing query should fail.");
  799. XCTAssertEqualObjects(error.localizedDescription, @"string or blob too big");
  800. XCTAssertEqual(error.code, SQLITE_TOOBIG);
  801. }
  802. - (void)testCustomFunctionNoMemoryErrorResult {
  803. [self.db makeFunctionNamed:@"NoMemoryErrorResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
  804. [self.db resultErrorNoMemoryInContext:context];
  805. }];
  806. FMResultSet *rs = [self.db executeQuery:@"SELECT NoMemoryErrorResultFunction()"];
  807. XCTAssert(rs, @"Creating query should succeed");
  808. NSError *error = nil;
  809. BOOL success = [rs nextWithError:&error];
  810. XCTAssertFalse(success, @"Performing query should fail.");
  811. XCTAssertEqualObjects(error.localizedDescription, @"out of memory");
  812. XCTAssertEqual(error.code, SQLITE_NOMEM);
  813. }
  814. - (void)createCustomFunctions {
  815. [self.db makeFunctionNamed:@"RemoveDiacritics" arguments:1 block:^(void *context, int argc, void **argv) {
  816. SqliteValueType type = [self.db valueType:argv[0]];
  817. if (type == SqliteValueTypeNull) {
  818. [self.db resultNullInContext:context];
  819. return;
  820. }
  821. if (type != SqliteValueTypeText) {
  822. [self.db resultError:@"Expected text" context:context];
  823. return;
  824. }
  825. NSString *string = [self.db valueString:argv[0]];
  826. NSString *result = [string stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:nil];
  827. [self.db resultString:result context:context];
  828. }];
  829. [self.db makeFunctionNamed:@"Hypotenuse" arguments:2 block:^(void *context, int argc, void **argv) {
  830. SqliteValueType type1 = [self.db valueType:argv[0]];
  831. SqliteValueType type2 = [self.db valueType:argv[1]];
  832. if (type1 != SqliteValueTypeFloat && type1 != SqliteValueTypeInteger && type2 != SqliteValueTypeFloat && type2 != SqliteValueTypeInteger) {
  833. [self.db resultError:@"Expected numeric" context:context];
  834. return;
  835. }
  836. double value1 = [self.db valueDouble:argv[0]];
  837. double value2 = [self.db valueDouble:argv[1]];
  838. [self.db resultDouble:hypot(value1, value2) context:context];
  839. }];
  840. [self.db makeFunctionNamed:@"SetAlternatingByteToOne" arguments:1 block:^(void *context, int argc, void **argv) {
  841. SqliteValueType type = [self.db valueType:argv[0]];
  842. if (type != SqliteValueTypeBlob) {
  843. [self.db resultError:@"Expected blob" context:context];
  844. return;
  845. }
  846. NSMutableData *data = [[self.db valueData:argv[0]] mutableCopy];
  847. uint8_t byte = 1;
  848. for (NSUInteger i = 0; i < data.length; i += 2) {
  849. [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:&byte];
  850. }
  851. [self.db resultData:data context:context];
  852. }];
  853. }
  854. - (void)testVersionNumber {
  855. XCTAssertEqual([FMDatabase FMDBVersion], 0x0276); // this is going to break everytime we bump it.
  856. }
  857. - (void)testVersionStringAboveRequired {
  858. NSComparisonResult result = [[FMDatabase FMDBUserVersion] compare:@"1.100.42" options:NSNumericSearch];
  859. XCTAssertEqual(result, NSOrderedDescending);
  860. }
  861. - (void)testVersionStringBelowRequired {
  862. NSComparisonResult result = [[FMDatabase FMDBUserVersion] compare:@"10.0.42" options:NSNumericSearch];
  863. XCTAssertEqual(result, NSOrderedAscending);
  864. }
  865. - (void)testExecuteStatements {
  866. BOOL success;
  867. NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
  868. "create table bulktest2 (id integer primary key autoincrement, y text);"
  869. "create table bulktest3 (id integer primary key autoincrement, z text);"
  870. "insert into bulktest1 (x) values ('XXX');"
  871. "insert into bulktest2 (y) values ('YYY');"
  872. "insert into bulktest3 (z) values ('ZZZ');";
  873. success = [self.db executeStatements:sql];
  874. XCTAssertTrue(success, @"bulk create");
  875. sql = @"select count(*) as count from bulktest1;"
  876. "select count(*) as count from bulktest2;"
  877. "select count(*) as count from bulktest3;";
  878. success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
  879. NSInteger count = [dictionary[@"count"] integerValue];
  880. XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
  881. return 0;
  882. }];
  883. XCTAssertTrue(success, @"bulk select");
  884. // select blob type records
  885. [self.db executeUpdate:@"create table bulktest4 (id integer primary key autoincrement, b blob);"];
  886. NSData *blobData = [[NSData alloc] initWithContentsOfFile:@"/bin/bash"];
  887. [self.db executeUpdate:@"insert into bulktest4 (b) values (?)" values:@[blobData] error:nil];
  888. sql = @"select * from bulktest4";
  889. success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary * _Nonnull resultsDictionary) {
  890. return 0;
  891. }];
  892. XCTAssertTrue(success, @"bulk select");
  893. sql = @"drop table bulktest1;"
  894. "drop table bulktest2;"
  895. "drop table bulktest3;"
  896. "drop table bulktest4";
  897. success = [self.db executeStatements:sql];
  898. XCTAssertTrue(success, @"bulk drop");
  899. }
  900. - (void)testCharAndBoolTypes
  901. {
  902. XCTAssertTrue([self.db executeUpdate:@"create table charBoolTest (a, b, c)"]);
  903. BOOL success = [self.db executeUpdate:@"insert into charBoolTest values (?, ?, ?)", @YES, @NO, @('x')];
  904. XCTAssertTrue(success, @"Unable to insert values");
  905. FMResultSet *rs = [self.db executeQuery:@"select * from charBoolTest"];
  906. XCTAssertNotNil(rs);
  907. XCTAssertTrue([rs next], @"Did not return row");
  908. XCTAssertEqual([rs boolForColumn:@"a"], true);
  909. XCTAssertEqualObjects([rs objectForColumn:@"a"], @YES);
  910. XCTAssertEqual([rs boolForColumn:@"b"], false);
  911. XCTAssertEqualObjects([rs objectForColumn:@"b"], @NO);
  912. XCTAssertEqual([rs intForColumn:@"c"], 'x');
  913. XCTAssertEqualObjects([rs objectForColumn:@"c"], @('x'));
  914. [rs close];
  915. XCTAssertTrue([self.db executeUpdate:@"drop table charBoolTest"], @"Did not drop table");
  916. }
  917. - (void)testSqliteLibVersion
  918. {
  919. NSString *version = [FMDatabase sqliteLibVersion];
  920. XCTAssert([version compare:@"3.7" options:NSNumericSearch] == NSOrderedDescending, @"earlier than 3.7");
  921. XCTAssert([version compare:@"4.0" options:NSNumericSearch] == NSOrderedAscending, @"not earlier than 4.0");
  922. }
  923. - (void)testIsThreadSafe
  924. {
  925. BOOL isThreadSafe = [FMDatabase isSQLiteThreadSafe];
  926. XCTAssert(isThreadSafe, @"not threadsafe");
  927. }
  928. - (void)testOpenNilPath
  929. {
  930. FMDatabase *db = [[FMDatabase alloc] init];
  931. XCTAssert([db open], @"open failed");
  932. XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
  933. NSString *value = @"baz";
  934. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
  935. NSString *retrievedValue = [db stringForQuery:@"select bar from foo"];
  936. XCTAssert([value compare:retrievedValue] == NSOrderedSame, @"values didn't match");
  937. }
  938. - (void)testOpenZeroLengthPath
  939. {
  940. FMDatabase *db = [[FMDatabase alloc] initWithPath:@""];
  941. XCTAssert([db open], @"open failed");
  942. XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
  943. NSString *value = @"baz";
  944. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
  945. NSString *retrievedValue = [db stringForQuery:@"select bar from foo"];
  946. XCTAssert([value compare:retrievedValue] == NSOrderedSame, @"values didn't match");
  947. }
  948. - (void)testOpenTwice
  949. {
  950. FMDatabase *db = [[FMDatabase alloc] init];
  951. [db open];
  952. XCTAssert([db open], @"Double open failed");
  953. }
  954. - (void)testInvalid
  955. {
  956. NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
  957. NSString *path = [documentsPath stringByAppendingPathComponent:@"nonexistentfolder/test.sqlite"];
  958. FMDatabase *db = [[FMDatabase alloc] initWithPath:path];
  959. XCTAssertFalse([db open], @"open did NOT fail");
  960. }
  961. - (void)testChangingMaxBusyRetryTimeInterval
  962. {
  963. FMDatabase *db = [[FMDatabase alloc] init];
  964. XCTAssert([db open], @"open failed");
  965. NSTimeInterval originalInterval = db.maxBusyRetryTimeInterval;
  966. NSTimeInterval updatedInterval = originalInterval > 0 ? originalInterval + 1 : 1;
  967. db.maxBusyRetryTimeInterval = updatedInterval;
  968. NSTimeInterval diff = fabs(db.maxBusyRetryTimeInterval - updatedInterval);
  969. XCTAssert(diff < 1e-5, @"interval should have changed %.1f", diff);
  970. }
  971. - (void)testChangingMaxBusyRetryTimeIntervalDatabaseNotOpened
  972. {
  973. FMDatabase *db = [[FMDatabase alloc] init];
  974. // XCTAssert([db open], @"open failed"); // deliberately not opened
  975. NSTimeInterval originalInterval = db.maxBusyRetryTimeInterval;
  976. NSTimeInterval updatedInterval = originalInterval > 0 ? originalInterval + 1 : 1;
  977. db.maxBusyRetryTimeInterval = updatedInterval;
  978. XCTAssertNotEqual(originalInterval, db.maxBusyRetryTimeInterval, @"interval should not have changed");
  979. }
  980. - (void)testZeroMaxBusyRetryTimeInterval
  981. {
  982. FMDatabase *db = [[FMDatabase alloc] init];
  983. XCTAssert([db open], @"open failed");
  984. NSTimeInterval updatedInterval = 0;
  985. db.maxBusyRetryTimeInterval = updatedInterval;
  986. XCTAssertEqual(db.maxBusyRetryTimeInterval, updatedInterval, @"busy handler not disabled");
  987. }
  988. - (void)testCloseOpenResultSets
  989. {
  990. FMDatabase *db = [[FMDatabase alloc] init];
  991. XCTAssert([db open], @"open failed");
  992. XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
  993. NSString *value = @"baz";
  994. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
  995. FMResultSet *rs = [db executeQuery:@"select bar from foo"];
  996. [db closeOpenResultSets];
  997. XCTAssertFalse([rs next], @"step should have failed");
  998. }
  999. - (void)testGoodConnection
  1000. {
  1001. FMDatabase *db = [[FMDatabase alloc] init];
  1002. XCTAssert([db open], @"open failed");
  1003. XCTAssert([db goodConnection], @"no good connection");
  1004. }
  1005. - (void)testBadConnection
  1006. {
  1007. FMDatabase *db = [[FMDatabase alloc] init];
  1008. // XCTAssert([db open], @"open failed"); // deliberately did not open
  1009. XCTAssertFalse([db goodConnection], @"no good connection");
  1010. }
  1011. - (void)testLastRowId
  1012. {
  1013. FMDatabase *db = [[FMDatabase alloc] init];
  1014. XCTAssert([db open], @"open failed");
  1015. XCTAssert([db executeUpdate:@"create table foo (foo_id integer primary key autoincrement, bar text)"], @"create failed");
  1016. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"baz"]], @"insert failed");
  1017. sqlite3_int64 firstRowId = [db lastInsertRowId];
  1018. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"qux"]], @"insert failed");
  1019. sqlite3_int64 secondRowId = [db lastInsertRowId];
  1020. XCTAssertEqual(secondRowId - firstRowId, 1, @"rowid should have incremented");
  1021. }
  1022. - (void)testChanges
  1023. {
  1024. FMDatabase *db = [[FMDatabase alloc] init];
  1025. XCTAssert([db open], @"open failed");
  1026. XCTAssert([db executeUpdate:@"create table foo (foo_id integer primary key autoincrement, bar text)"], @"create failed");
  1027. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"baz"]], @"insert failed");
  1028. XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"qux"]], @"insert failed");
  1029. XCTAssert([db executeUpdate:@"update foo set bar = ?" withArgumentsInArray:@[@"xxx"]], @"insert failed");
  1030. int changes = [db changes];
  1031. XCTAssertEqual(changes, 2, @"two rows should have incremented \(%ld)", (long)changes);
  1032. }
  1033. - (void)testBind {
  1034. FMDatabase *db = [[FMDatabase alloc] init];
  1035. XCTAssert([db open], @"open failed");
  1036. XCTAssert([db executeUpdate:@"create table foo (id integer primary key autoincrement, a numeric)"], @"create failed");
  1037. NSNumber *insertedValue;
  1038. NSNumber *retrievedValue;
  1039. insertedValue = [NSNumber numberWithChar:51];
  1040. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1041. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1042. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1043. insertedValue = [NSNumber numberWithUnsignedChar:52];
  1044. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1045. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1046. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1047. insertedValue = [NSNumber numberWithShort:53];
  1048. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1049. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1050. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1051. insertedValue = [NSNumber numberWithUnsignedShort:54];
  1052. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1053. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1054. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1055. insertedValue = [NSNumber numberWithInt:54];
  1056. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1057. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1058. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1059. insertedValue = [NSNumber numberWithUnsignedInt:55];
  1060. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1061. retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1062. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1063. insertedValue = [NSNumber numberWithLong:56];
  1064. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1065. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1066. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1067. insertedValue = [NSNumber numberWithUnsignedLong:57];
  1068. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1069. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1070. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1071. insertedValue = [NSNumber numberWithLongLong:56];
  1072. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1073. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1074. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1075. insertedValue = [NSNumber numberWithUnsignedLongLong:57];
  1076. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1077. retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1078. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1079. insertedValue = [NSNumber numberWithFloat:58];
  1080. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1081. retrievedValue = @([db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1082. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1083. insertedValue = [NSNumber numberWithDouble:59];
  1084. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1085. retrievedValue = @([db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1086. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1087. insertedValue = @TRUE;
  1088. XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
  1089. retrievedValue = @([db boolForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
  1090. XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
  1091. }
  1092. - (void)testFormatStrings {
  1093. FMDatabase *db = [[FMDatabase alloc] init];
  1094. XCTAssert([db open], @"open failed");
  1095. XCTAssert([db executeUpdate:@"create table foo (id integer primary key autoincrement, a numeric)"], @"create failed");
  1096. BOOL success;
  1097. char insertedChar = 'A';
  1098. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%c)", insertedChar];
  1099. XCTAssert(success, @"insert failed");
  1100. const char *retrievedChar = [[db stringForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])] UTF8String];
  1101. XCTAssertEqual(insertedChar, retrievedChar[0], @"values don't match");
  1102. const char *insertedString = "baz";
  1103. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%s)", insertedString];
  1104. XCTAssert(success, @"insert failed");
  1105. const char *retrievedString = [[db stringForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])] UTF8String];
  1106. XCTAssert(strcmp(insertedString, retrievedString) == 0, @"values don't match");
  1107. int insertedInt = 42;
  1108. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%d)", insertedInt];
  1109. XCTAssert(success, @"insert failed");
  1110. int retrievedInt = [db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  1111. XCTAssertEqual(insertedInt, retrievedInt, @"values don't match");
  1112. char insertedUnsignedInt = 43;
  1113. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%u)", insertedUnsignedInt];
  1114. XCTAssert(success, @"insert failed");
  1115. char retrievedUnsignedInt = [db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  1116. XCTAssertEqual(insertedUnsignedInt, retrievedUnsignedInt, @"values don't match");
  1117. float insertedFloat = 44;
  1118. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%f)", insertedFloat];
  1119. XCTAssert(success, @"insert failed");
  1120. float retrievedFloat = [db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  1121. XCTAssertEqual(insertedFloat, retrievedFloat, @"values don't match");
  1122. unsigned long long insertedUnsignedLongLong = 45;
  1123. success = [db executeUpdateWithFormat:@"insert into foo (a) values (%llu)", insertedUnsignedLongLong];
  1124. XCTAssert(success, @"insert failed");
  1125. unsigned long long retrievedUnsignedLongLong = [db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
  1126. XCTAssertEqual(insertedUnsignedLongLong, retrievedUnsignedLongLong, @"values don't match");
  1127. }
  1128. - (void)testStepError {
  1129. FMDatabase *db = [[FMDatabase alloc] init];
  1130. XCTAssert([db open], @"open failed");
  1131. XCTAssert([db executeUpdate:@"create table foo (id integer primary key)"], @"create failed");
  1132. XCTAssert([db executeUpdate:@"insert into foo (id) values (?)" values:@[@1] error:nil], @"create failed");
  1133. NSError *error;
  1134. BOOL success = [db executeUpdate:@"insert into foo (id) values (?)" values:@[@1] error:&error];
  1135. XCTAssertFalse(success, @"insert of duplicate key should have failed");
  1136. XCTAssertNotNil(error, @"error object should have been generated");
  1137. XCTAssertEqual(error.code, 19, @"error code 19 should have been generated");
  1138. }
  1139. - (void)testCheckpoint {
  1140. FMDatabase *db = [[FMDatabase alloc] init];
  1141. XCTAssertTrue([db open], @"open failed");
  1142. NSError *error = nil;
  1143. int frameCount = 0;
  1144. int checkpointCount = 0;
  1145. [db checkpoint:FMDBCheckpointModeTruncate name:NULL logFrameCount:&frameCount checkpointCount:&checkpointCount error:&error];
  1146. // Verify that we're calling the checkpoint interface, which is a decent scope for this test, without going so far as to verify what checkpoint does
  1147. XCTAssertEqual(frameCount, -1, @"frameCount should be -1 (means not using WAL mode) to verify that we're using the proper checkpoint interface");
  1148. XCTAssertEqual(checkpointCount, -1, @"checkpointCount should be -1 (means not using WAL mode) to verify that we're using the proper checkpoint interface");
  1149. }
  1150. - (void)testImmediateTransaction {
  1151. FMDatabase *db = [[FMDatabase alloc] init];
  1152. XCTAssertTrue([db open], @"open failed");
  1153. [db beginImmediateTransaction];
  1154. [db beginImmediateTransaction];
  1155. // Verify that beginImmediateTransaction behaves as advertised and starts a transaction
  1156. XCTAssertEqualObjects([db lastError].localizedDescription, @"cannot start a transaction within a transaction");
  1157. }
  1158. - (void)testOpenFailure {
  1159. NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  1160. NSURL *fileURL1 = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  1161. NSURL *fileURL2 = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
  1162. NSFileManager *manager = [NSFileManager defaultManager];
  1163. // ok, first create one database
  1164. FMDatabase *db = [FMDatabase databaseWithURL:fileURL1];
  1165. BOOL success = [db open];
  1166. XCTAssert(success, @"Database not created correctly for purposes of test");
  1167. success = [db executeUpdate:@"create table if not exists foo (bar text)"];
  1168. XCTAssert(success, @"Table created correctly for purposes of test");
  1169. [db close];
  1170. // now, try to create open second database even though it doesn't exist
  1171. db = [FMDatabase databaseWithURL:fileURL2];
  1172. success = [db openWithFlags:SQLITE_OPEN_READWRITE];
  1173. XCTAssert(!success, @"Opening second database file that doesn't exist should not have succeeded");
  1174. // OK, everything so far is fine, opening a db without CREATE option above should have failed,
  1175. // but so fix the missing file issue and re-opening
  1176. success = [manager copyItemAtURL:fileURL1 toURL:fileURL2 error:nil];
  1177. XCTAssert(success, @"Copying of db should have succeeded");
  1178. // now let's try opening it again
  1179. success = [db openWithFlags:SQLITE_OPEN_READWRITE];
  1180. XCTAssert(success, @"Opening second database should now succeed");
  1181. // now let's try using it
  1182. FMResultSet *rs = [db executeQuery:@"select * from foo"];
  1183. XCTAssertNotNil(rs, @"Should successfully be able to use re-opened database");
  1184. // let's clean up
  1185. [rs close];
  1186. [db close];
  1187. [manager removeItemAtURL:fileURL1 error:nil];
  1188. [manager removeItemAtURL:fileURL2 error:nil];
  1189. }
  1190. @end