FMDatabaseTests.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  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. @interface FMDatabaseTests : FMDBTempDBTests
  12. @end
  13. @implementation FMDatabaseTests
  14. + (void)populateDatabase:(FMDatabase *)db
  15. {
  16. [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"];
  17. [db beginTransaction];
  18. int i = 0;
  19. while (i++ < 20) {
  20. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  21. @"hi'", // look! I put in a ', and I'm not escaping it!
  22. [NSString stringWithFormat:@"number %d", i],
  23. [NSNumber numberWithInt:i],
  24. [NSDate date],
  25. [NSNumber numberWithFloat:2.2f]];
  26. }
  27. [db commit];
  28. // do it again, just because
  29. [db beginTransaction];
  30. i = 0;
  31. while (i++ < 20) {
  32. [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
  33. @"hi again'", // look! I put in a ', and I'm not escaping it!
  34. [NSString stringWithFormat:@"number %d", i],
  35. [NSNumber numberWithInt:i],
  36. [NSDate date],
  37. [NSNumber numberWithFloat:2.2f]];
  38. }
  39. [db commit];
  40. [db executeUpdate:@"create table t3 (a somevalue)"];
  41. [db beginTransaction];
  42. for (int i=0; i < 20; i++) {
  43. [db executeUpdate:@"insert into t3 (a) values (?)", [NSNumber numberWithInt:i]];
  44. }
  45. [db commit];
  46. }
  47. - (void)setUp
  48. {
  49. [super setUp];
  50. // Put setup code here. This method is called before the invocation of each test method in the class.
  51. }
  52. - (void)tearDown
  53. {
  54. // Put teardown code here. This method is called after the invocation of each test method in the class.
  55. [super tearDown];
  56. }
  57. - (void)testOpenWithVFS
  58. {
  59. // create custom vfs
  60. sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
  61. vfs.zName = "MyCustomVFS";
  62. XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
  63. // use custom vfs to open a in memory database
  64. FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
  65. [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
  66. XCTAssertFalse([db hadError], @"Open with a custom VFS should have succeeded");
  67. }
  68. - (void)testFailOnOpenWithUnknownVFS
  69. {
  70. FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
  71. [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"UnknownVFS"];
  72. XCTAssertTrue([db hadError], @"Should have failed");
  73. }
  74. - (void)testFailOnUnopenedDatabase
  75. {
  76. [self.db close];
  77. XCTAssertNil([self.db executeQuery:@"select * from table"], @"Shouldn't get results from an empty table");
  78. XCTAssertTrue([self.db hadError], @"Should have failed");
  79. }
  80. - (void)testFailOnBadStatement
  81. {
  82. XCTAssertFalse([self.db executeUpdate:@"blah blah blah"], @"Invalid statement should fail");
  83. XCTAssertTrue([self.db hadError], @"Should have failed");
  84. }
  85. - (void)testFailOnBadStatementWithError
  86. {
  87. NSError *error = nil;
  88. XCTAssertFalse([self.db executeUpdate:@"blah blah blah" withErrorAndBindings:&error], @"Invalid statement should fail");
  89. XCTAssertNotNil(error, @"Should have a non-nil NSError");
  90. XCTAssertEqual([error code], (NSInteger)SQLITE_ERROR, @"Error should be SQLITE_ERROR");
  91. }
  92. - (void)testPragmaJournalMode
  93. {
  94. FMResultSet *ps = [self.db executeQuery:@"pragma journal_mode=delete"];
  95. XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
  96. XCTAssertNotNil(ps, @"Result set should be non-nil");
  97. XCTAssertTrue([ps next], @"Result set should have a next result");
  98. [ps close];
  99. }
  100. - (void)testPragmaPageSize
  101. {
  102. [self.db executeUpdate:@"PRAGMA page_size=2048"];
  103. XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
  104. }
  105. - (void)testVacuum
  106. {
  107. [self.db executeUpdate:@"VACUUM"];
  108. XCTAssertFalse([self.db hadError], @"VACUUM should have succeeded");
  109. }
  110. - (void)testSelectULL
  111. {
  112. // Unsigned long long
  113. [self.db executeUpdate:@"create table ull (a integer)"];
  114. [self.db executeUpdate:@"insert into ull (a) values (?)", [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]];
  115. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  116. FMResultSet *rs = [self.db executeQuery:@"select a from ull"];
  117. while ([rs next]) {
  118. XCTAssertEqual([rs unsignedLongLongIntForColumnIndex:0], ULLONG_MAX, @"Result should be ULLONG_MAX");
  119. XCTAssertEqual([rs unsignedLongLongIntForColumn:@"a"], ULLONG_MAX, @"Result should be ULLONG_MAX");
  120. }
  121. [rs close];
  122. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  123. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  124. }
  125. - (void)testSelectByColumnName
  126. {
  127. FMResultSet *rs = [self.db executeQuery:@"select rowid,* from test where a = ?", @"hi"];
  128. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  129. while ([rs next]) {
  130. [rs intForColumn:@"c"];
  131. XCTAssertNotNil([rs stringForColumn:@"b"], @"Should have non-nil string for 'b'");
  132. XCTAssertNotNil([rs stringForColumn:@"a"], @"Should have non-nil string for 'a'");
  133. XCTAssertNotNil([rs stringForColumn:@"rowid"], @"Should have non-nil string for 'rowid'");
  134. XCTAssertNotNil([rs dateForColumn:@"d"], @"Should have non-nil date for 'd'");
  135. [rs doubleForColumn:@"d"];
  136. [rs doubleForColumn:@"e"];
  137. XCTAssertEqualObjects([rs columnNameForIndex:0], @"rowid", @"Wrong column name for result set column number");
  138. XCTAssertEqualObjects([rs columnNameForIndex:1], @"a", @"Wrong column name for result set column number");
  139. }
  140. [rs close];
  141. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  142. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  143. }
  144. - (void)testSelectWithIndexedAndKeyedSubscript
  145. {
  146. FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
  147. XCTAssertNotNil(rs, @"Should have a non-nil result set");
  148. while ([rs next]) {
  149. XCTAssertEqualObjects(rs[0], rs[@"rowid"], @"Column zero should be equal to 'rowid'");
  150. XCTAssertEqualObjects(rs[1], rs[@"a"], @"Column 1 should be equal to 'a'");
  151. XCTAssertEqualObjects(rs[2], rs[@"b"], @"Column 2 should be equal to 'b'");
  152. XCTAssertEqualObjects(rs[3], rs[@"c"], @"Column 3 should be equal to 'c'");
  153. }
  154. [rs close];
  155. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  156. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  157. }
  158. - (void)testBusyRetryTimeout
  159. {
  160. [self.db executeUpdate:@"create table t1 (a integer)"];
  161. [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
  162. [self.db setMaxBusyRetryTimeInterval:2];
  163. FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
  164. [newDB open];
  165. FMResultSet *rs = [newDB executeQuery:@"select rowid,* from test where a = ?", @"hi'"];
  166. [rs next]; // just grab one... which will keep the db locked
  167. XCTAssertFalse([self.db executeUpdate:@"insert into t1 values (5)"], @"Insert should fail because the db is locked by a read");
  168. XCTAssertEqual([self.db lastErrorCode], SQLITE_BUSY, @"SQLITE_BUSY should be the last error");
  169. [rs close];
  170. [newDB close];
  171. XCTAssertTrue([self.db executeUpdate:@"insert into t1 values (5)"], @"The database shouldn't be locked at this point");
  172. }
  173. - (void)testCaseSensitiveResultDictionary
  174. {
  175. // case sensitive result dictionary test
  176. [self.db executeUpdate:@"create table cs (aRowName integer, bRowName text)"];
  177. [self.db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)", [NSNumber numberWithBool:1], @"hello"];
  178. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  179. FMResultSet *rs = [self.db executeQuery:@"select * from cs"];
  180. while ([rs next]) {
  181. NSDictionary *d = [rs resultDictionary];
  182. XCTAssertNotNil([d objectForKey:@"aRowName"], @"aRowName should be non-nil");
  183. XCTAssertNil([d objectForKey:@"arowname"], @"arowname should be nil");
  184. XCTAssertNotNil([d objectForKey:@"bRowName"], @"bRowName should be non-nil");
  185. XCTAssertNil([d objectForKey:@"browname"], @"browname should be nil");
  186. }
  187. [rs close];
  188. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  189. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  190. }
  191. - (void)testBoolInsert
  192. {
  193. [self.db executeUpdate:@"create table btest (aRowName integer)"];
  194. [self.db executeUpdate:@"insert into btest (aRowName) values (?)", [NSNumber numberWithBool:12]];
  195. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  196. FMResultSet *rs = [self.db executeQuery:@"select * from btest"];
  197. while ([rs next]) {
  198. XCTAssertTrue([rs boolForColumnIndex:0], @"first column should be true.");
  199. XCTAssertTrue([rs intForColumnIndex:0] == 1, @"first column should be equal to 1 - it was %d.", [rs intForColumnIndex:0]);
  200. }
  201. [rs close];
  202. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  203. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  204. }
  205. - (void)testNamedParametersCount
  206. {
  207. XCTAssertTrue([self.db executeUpdate:@"create table namedparamcounttest (a text, b text, c integer, d double)"]);
  208. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  209. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  210. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  211. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  212. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  213. XCTAssertTrue([self.db executeUpdate:@"insert into namedparamcounttest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  214. FMResultSet *rs = [self.db executeQuery:@"select * from namedparamcounttest"];
  215. XCTAssertNotNil(rs);
  216. [rs next];
  217. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
  218. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  219. XCTAssertEqual([rs intForColumn:@"c"], 1);
  220. XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
  221. [rs close];
  222. // note that at this point, dictionaryArgs has way more values than we need, but the query should still work since
  223. // a is in there, and that's all we need.
  224. rs = [self.db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs];
  225. XCTAssertNotNil(rs);
  226. XCTAssertTrue([rs next]);
  227. [rs close];
  228. // ***** Please note the following codes *****
  229. dictionaryArgs = [NSMutableDictionary dictionary];
  230. [dictionaryArgs setObject:@"NewText1" forKey:@"a"];
  231. [dictionaryArgs setObject:@"NewText2" forKey:@"b"];
  232. [dictionaryArgs setObject:@"OneMoreText" forKey:@"OneMore"];
  233. XCTAssertTrue([self.db executeUpdate:@"update namedparamcounttest set a = :a, b = :b where b = 'Text2'" withParameterDictionary:dictionaryArgs]);
  234. }
  235. - (void)testBlobs
  236. {
  237. [self.db executeUpdate:@"create table blobTable (a text, b blob)"];
  238. // let's read an image from safari's app bundle.
  239. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  240. if (safariCompass) {
  241. [self.db executeUpdate:@"insert into blobTable (a, b) values (?, ?)", @"safari's compass", safariCompass];
  242. FMResultSet *rs = [self.db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"];
  243. XCTAssertTrue([rs next]);
  244. NSData *readData = [rs dataForColumn:@"b"];
  245. XCTAssertEqualObjects(readData, safariCompass);
  246. // ye shall read the header for this function, or suffer the consequences.
  247. NSData *readDataNoCopy = [rs dataNoCopyForColumn:@"b"];
  248. XCTAssertEqualObjects(readDataNoCopy, safariCompass);
  249. [rs close];
  250. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  251. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  252. }
  253. }
  254. - (void)testNullValues
  255. {
  256. [self.db executeUpdate:@"create table t2 (a integer, b integer)"];
  257. BOOL result = [self.db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]];
  258. XCTAssertTrue(result, @"Failed to insert a nil value");
  259. FMResultSet *rs = [self.db executeQuery:@"select * from t2"];
  260. while ([rs next]) {
  261. XCTAssertNil([rs stringForColumnIndex:0], @"Wasn't able to retrieve a null string");
  262. XCTAssertEqualObjects([rs stringForColumnIndex:1], @"5");
  263. }
  264. [rs close];
  265. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  266. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  267. }
  268. - (void)testNestedResultSets
  269. {
  270. FMResultSet *rs = [self.db executeQuery:@"select * from t3"];
  271. while ([rs next]) {
  272. int foo = [rs intForColumnIndex:0];
  273. int newVal = foo + 100;
  274. [self.db executeUpdate:@"update t3 set a = ? where a = ?", [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]];
  275. FMResultSet *rs2 = [self.db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]];
  276. [rs2 next];
  277. XCTAssertEqual([rs2 intForColumnIndex:0], newVal);
  278. [rs2 close];
  279. }
  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. - (void)testNSNullInsertion
  285. {
  286. [self.db executeUpdate:@"create table nulltest (a text, b text)"];
  287. [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", [NSNull null], @"a"];
  288. [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", nil, @"b"];
  289. FMResultSet *rs = [self.db executeQuery:@"select * from nulltest"];
  290. while ([rs next]) {
  291. XCTAssertNil([rs stringForColumnIndex:0]);
  292. XCTAssertNotNil([rs stringForColumnIndex:1]);
  293. }
  294. [rs close];
  295. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  296. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  297. }
  298. - (void)testNullDates
  299. {
  300. NSDate *date = [NSDate date];
  301. [self.db executeUpdate:@"create table datetest (a double, b double, c double)"];
  302. [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date];
  303. FMResultSet *rs = [self.db executeQuery:@"select * from datetest"];
  304. XCTAssertNotNil(rs);
  305. while ([rs next]) {
  306. NSDate *b = [rs dateForColumnIndex:1];
  307. NSDate *c = [rs dateForColumnIndex:2];
  308. XCTAssertNil([rs dateForColumnIndex:0]);
  309. XCTAssertNotNil(c, @"zero date shouldn't be nil");
  310. XCTAssertEqualWithAccuracy([b timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second");
  311. XCTAssertEqualWithAccuracy([c timeIntervalSince1970], 0.0, 1.0, @"Dates should be the same to within a second");
  312. }
  313. [rs close];
  314. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  315. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  316. }
  317. - (void)testLotsOfNULLs
  318. {
  319. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
  320. if (!safariCompass)
  321. return;
  322. [self.db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"];
  323. [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]];
  324. [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]];
  325. FMResultSet *rs = [self.db executeQuery:@"select * from nulltest2"];
  326. while ([rs next]) {
  327. int i = [rs intForColumnIndex:2];
  328. if (i == 12) {
  329. // it's the first row we inserted.
  330. XCTAssertFalse([rs columnIndexIsNull:0]);
  331. XCTAssertFalse([rs columnIndexIsNull:1]);
  332. XCTAssertFalse([rs columnIndexIsNull:2]);
  333. XCTAssertFalse([rs columnIndexIsNull:3]);
  334. XCTAssertFalse([rs columnIndexIsNull:4]);
  335. XCTAssertTrue( [rs columnIndexIsNull:5]);
  336. XCTAssertEqualObjects([rs dataForColumn:@"d"], safariCompass);
  337. XCTAssertNil([rs dataForColumn:@"notthere"]);
  338. XCTAssertNil([rs stringForColumnIndex:-2], @"Negative columns should return nil results");
  339. XCTAssertTrue([rs boolForColumnIndex:4]);
  340. XCTAssertTrue([rs boolForColumn:@"b"]);
  341. XCTAssertEqualWithAccuracy(4.4, [rs doubleForColumn:@"f"], 0.0000001, @"Saving a float and returning it as a double shouldn't change the result much");
  342. XCTAssertEqual([rs intForColumn:@"i"], 12);
  343. XCTAssertEqual([rs intForColumnIndex:2], 12);
  344. XCTAssertEqual([rs intForColumnIndex:12], 0, @"Non-existent columns should return zero for ints");
  345. XCTAssertEqual([rs intForColumn:@"notthere"], 0, @"Non-existent columns should return zero for ints");
  346. XCTAssertEqual([rs longForColumn:@"i"], 12l);
  347. XCTAssertEqual([rs longLongIntForColumn:@"i"], 12ll);
  348. }
  349. else {
  350. // let's test various null things.
  351. XCTAssertTrue([rs columnIndexIsNull:0]);
  352. XCTAssertTrue([rs columnIndexIsNull:1]);
  353. XCTAssertTrue([rs columnIndexIsNull:2]);
  354. XCTAssertTrue([rs columnIndexIsNull:3]);
  355. XCTAssertTrue([rs columnIndexIsNull:4]);
  356. XCTAssertTrue([rs columnIndexIsNull:5]);
  357. XCTAssertNil([rs dataForColumn:@"d"]);
  358. }
  359. }
  360. [rs close];
  361. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  362. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  363. }
  364. - (void)testUTF8Strings
  365. {
  366. [self.db executeUpdate:@"create table utest (a text)"];
  367. [self.db executeUpdate:@"insert into utest values (?)", @"/übertest"];
  368. FMResultSet *rs = [self.db executeQuery:@"select * from utest where a = ?", @"/übertest"];
  369. XCTAssertTrue([rs next]);
  370. [rs close];
  371. XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
  372. XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
  373. }
  374. - (void)testArgumentsInArray
  375. {
  376. [self.db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"];
  377. [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]];
  378. [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]];
  379. FMResultSet *rs = [self.db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]];
  380. XCTAssertTrue([rs next]);
  381. XCTAssertTrue([rs hasAnotherRow]);
  382. XCTAssertFalse([self.db hadError]);
  383. XCTAssertEqualObjects([rs stringForColumnIndex:0], @"one");
  384. XCTAssertEqual([rs intForColumnIndex:1], 2);
  385. XCTAssertTrue([rs next]);
  386. XCTAssertEqual([rs intForColumnIndex:1], 3);
  387. XCTAssertFalse([rs next]);
  388. XCTAssertFalse([rs hasAnotherRow]);
  389. }
  390. - (void)testColumnNamesContainingPeriods
  391. {
  392. XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]);
  393. [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"];
  394. FMResultSet *rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"];
  395. XCTAssertNotNil(rs);
  396. XCTAssertTrue([rs next]);
  397. XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
  398. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
  399. XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two"), 0, @"String comparison should return zero");
  400. [rs close];
  401. // let's try these again, with the withArgumentsInArray: variation
  402. XCTAssertTrue([self.db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]);
  403. XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]);
  404. [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]];
  405. rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]];
  406. XCTAssertNotNil(rs);
  407. XCTAssertTrue([rs next]);
  408. XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
  409. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
  410. XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two"), 0, @"String comparison should return zero");
  411. [rs close];
  412. }
  413. - (void)testFormatStringParsing
  414. {
  415. XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  416. [self.db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234ll];
  417. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42];
  418. XCTAssertNotNil(rs);
  419. XCTAssertTrue([rs next]);
  420. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"text");
  421. XCTAssertEqual([rs intForColumn:@"b"], 42);
  422. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"BLOB");
  423. XCTAssertEqualObjects([rs stringForColumn:@"d"], @"d");
  424. XCTAssertEqual([rs longLongIntForColumn:@"e"], 12345678901234ll);
  425. [rs close];
  426. }
  427. - (void)testFormatStringParsingWithSizePrefixes
  428. {
  429. XCTAssertTrue([self.db executeUpdate:@"create table t55 (a text, b int, c float)"]);
  430. short testShort = -4;
  431. float testFloat = 5.5;
  432. [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat];
  433. unsigned short testUShort = 6;
  434. [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat];
  435. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"];
  436. XCTAssertNotNil(rs);
  437. XCTAssertTrue([rs next]);
  438. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
  439. XCTAssertEqual([rs intForColumn:@"b"], -4);
  440. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
  441. XCTAssertTrue([rs next]);
  442. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
  443. XCTAssertEqual([rs intForColumn:@"b"], 6);
  444. XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
  445. [rs close];
  446. }
  447. - (void)testFormatStringParsingWithNilValue
  448. {
  449. XCTAssertTrue([self.db executeUpdate:@"create table tatwhat (a text)"]);
  450. BOOL worked = [self.db executeUpdateWithFormat:@"insert into tatwhat values(%@)", nil];
  451. XCTAssertTrue(worked);
  452. FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from tatwhat"];
  453. XCTAssertNotNil(rs);
  454. XCTAssertTrue([rs next]);
  455. XCTAssertTrue([rs columnIndexIsNull:0]);
  456. XCTAssertFalse([rs next]);
  457. }
  458. - (void)testUpdateWithErrorAndBindings
  459. {
  460. XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
  461. NSError *err = nil;
  462. BOOL result = [self.db executeUpdate:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]];
  463. XCTAssertTrue(result);
  464. }
  465. - (void)testSelectWithEmptyArgumentsArray
  466. {
  467. FMResultSet *rs = [self.db executeQuery:@"select * from test where a=?" withArgumentsInArray:@[]];
  468. XCTAssertNil(rs);
  469. }
  470. - (void)testDatabaseAttach
  471. {
  472. NSFileManager *fileManager = [NSFileManager new];
  473. [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil];
  474. FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"];
  475. XCTAssertTrue([dbB open]);
  476. XCTAssertTrue([dbB executeUpdate:@"create table attached (a text)"]);
  477. XCTAssertTrue(([dbB executeUpdate:@"insert into attached values (?)", @"test"]));
  478. XCTAssertTrue([dbB close]);
  479. [self.db executeUpdate:@"attach database '/tmp/attachme.db' as attack"];
  480. FMResultSet *rs = [self.db executeQuery:@"select * from attack.attached"];
  481. XCTAssertNotNil(rs);
  482. XCTAssertTrue([rs next]);
  483. [rs close];
  484. }
  485. - (void)testNamedParameters
  486. {
  487. // -------------------------------------------------------------------------------
  488. // Named parameters.
  489. XCTAssertTrue([self.db executeUpdate:@"create table namedparamtest (a text, b text, c integer, d double)"]);
  490. NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
  491. [dictionaryArgs setObject:@"Text1" forKey:@"a"];
  492. [dictionaryArgs setObject:@"Text2" forKey:@"b"];
  493. [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
  494. [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
  495. XCTAssertTrue([self.db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
  496. FMResultSet *rs = [self.db executeQuery:@"select * from namedparamtest"];
  497. XCTAssertNotNil(rs);
  498. XCTAssertTrue([rs next]);
  499. XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
  500. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  501. XCTAssertEqual([rs intForColumn:@"c"], 1);
  502. XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
  503. [rs close];
  504. dictionaryArgs = [NSMutableDictionary dictionary];
  505. [dictionaryArgs setObject:@"Text2" forKey:@"blah"];
  506. rs = [self.db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs];
  507. XCTAssertNotNil(rs);
  508. XCTAssertTrue([rs next]);
  509. XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
  510. [rs close];
  511. }
  512. - (void)testPragmaDatabaseList
  513. {
  514. FMResultSet *rs = [self.db executeQuery:@"pragma database_list"];
  515. int counter = 0;
  516. while ([rs next]) {
  517. counter++;
  518. XCTAssertEqualObjects([rs stringForColumn:@"file"], self.databasePath);
  519. }
  520. XCTAssertEqual(counter, 1, @"Only one database should be attached");
  521. }
  522. - (void)testCachedStatementsInUse
  523. {
  524. [self.db setShouldCacheStatements:true];
  525. [self.db executeUpdate:@"CREATE TABLE testCacheStatements(key INTEGER PRIMARY KEY, value INTEGER)"];
  526. [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (1, 2)"];
  527. [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (2, 4)"];
  528. XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
  529. XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
  530. }
  531. - (void)testStatementCachingWorks
  532. {
  533. [self.db executeUpdate:@"CREATE TABLE testStatementCaching ( value INTEGER )"];
  534. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
  535. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
  536. [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (2)"];
  537. [self.db setShouldCacheStatements:YES];
  538. // two iterations.
  539. // the first time through no statements will be from the cache.
  540. // the second time through all statements come from the cache.
  541. for (int i = 1; i <= 2; i++ ) {
  542. FMResultSet* rs1 = [self.db executeQuery: @"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @1]; // results in 2 rows...
  543. XCTAssertNotNil(rs1);
  544. XCTAssertTrue([rs1 next]);
  545. // confirm that we're seeing the benefits of caching.
  546. XCTAssertEqual([[rs1 statement] useCount], (long)i);
  547. FMResultSet* rs2 = [self.db executeQuery:@"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @2]; // results in 1 row
  548. XCTAssertNotNil(rs2);
  549. XCTAssertTrue([rs2 next]);
  550. XCTAssertEqual([[rs2 statement] useCount], (long)i);
  551. // 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.
  552. XCTAssertTrue([rs1 next]);
  553. [rs1 close];
  554. [rs2 close];
  555. }
  556. }
  557. /*
  558. Test the date format
  559. */
  560. - (void)testDateFormat
  561. {
  562. void (^testOneDateFormat)(FMDatabase *, NSDate *) = ^( FMDatabase *db, NSDate *testDate ){
  563. [db executeUpdate:@"DROP TABLE IF EXISTS test_format"];
  564. [db executeUpdate:@"CREATE TABLE test_format ( test TEXT )"];
  565. [db executeUpdate:@"INSERT INTO test_format(test) VALUES (?)", testDate];
  566. FMResultSet *rs = [db executeQuery:@"SELECT test FROM test_format"];
  567. XCTAssertNotNil(rs);
  568. XCTAssertTrue([rs next]);
  569. XCTAssertEqualObjects([rs dateForColumnIndex:0], testDate);
  570. [rs close];
  571. };
  572. NSDateFormatter *fmt = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  573. NSDate *testDate = [fmt dateFromString:@"2013-02-20 12:00:00"];
  574. // test timestamp dates (ensuring our change does not break those)
  575. testOneDateFormat(self.db,testDate);
  576. // now test the string-based timestamp
  577. [self.db setDateFormat:fmt];
  578. testOneDateFormat(self.db, testDate);
  579. }
  580. - (void)testColumnNameMap
  581. {
  582. XCTAssertTrue([self.db executeUpdate:@"create table colNameTest (a, b, c, d)"]);
  583. XCTAssertTrue([self.db executeUpdate:@"insert into colNameTest values (1, 2, 3, 4)"]);
  584. FMResultSet *ars = [self.db executeQuery:@"select * from colNameTest"];
  585. XCTAssertNotNil(ars);
  586. NSDictionary *d = [ars columnNameToIndexMap];
  587. XCTAssertEqual([d count], (NSUInteger)4);
  588. XCTAssertEqualObjects([d objectForKey:@"a"], @0);
  589. XCTAssertEqualObjects([d objectForKey:@"b"], @1);
  590. XCTAssertEqualObjects([d objectForKey:@"c"], @2);
  591. XCTAssertEqualObjects([d objectForKey:@"d"], @3);
  592. }
  593. - (void)testCustomFunction
  594. {
  595. [self.db executeUpdate:@"create table ftest (foo text)"];
  596. [self.db executeUpdate:@"insert into ftest values ('hello')"];
  597. [self.db executeUpdate:@"insert into ftest values ('hi')"];
  598. [self.db executeUpdate:@"insert into ftest values ('not h!')"];
  599. [self.db executeUpdate:@"insert into ftest values ('definitely not h!')"];
  600. [self.db makeFunctionNamed:@"StringStartsWithH" maximumArguments:1 withBlock:^(sqlite3_context *context, int aargc, sqlite3_value **aargv) {
  601. if (sqlite3_value_type(aargv[0]) == SQLITE_TEXT) {
  602. @autoreleasepool {
  603. const char *c = (const char *)sqlite3_value_text(aargv[0]);
  604. NSString *s = [NSString stringWithUTF8String:c];
  605. sqlite3_result_int(context, [s hasPrefix:@"h"]);
  606. }
  607. }
  608. else {
  609. XCTFail(@"Unknown format for StringStartsWithH (%d)", sqlite3_value_type(aargv[0]));
  610. sqlite3_result_null(context);
  611. }
  612. }];
  613. int rowCount = 0;
  614. FMResultSet *ars = [self.db executeQuery:@"select * from ftest where StringStartsWithH(foo)"];
  615. while ([ars next]) {
  616. rowCount++;
  617. }
  618. XCTAssertEqual(rowCount, 2);
  619. }
  620. - (void)testVersionNumber {
  621. XCTAssertTrue([FMDatabase FMDBVersion] == 0x0250); // this is going to break everytime we bump it.
  622. }
  623. - (void)testExecuteStatements
  624. {
  625. BOOL success;
  626. NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
  627. "create table bulktest2 (id integer primary key autoincrement, y text);"
  628. "create table bulktest3 (id integer primary key autoincrement, z text);"
  629. "insert into bulktest1 (x) values ('XXX');"
  630. "insert into bulktest2 (y) values ('YYY');"
  631. "insert into bulktest3 (z) values ('ZZZ');";
  632. success = [self.db executeStatements:sql];
  633. XCTAssertTrue(success, @"bulk create");
  634. sql = @"select count(*) as count from bulktest1;"
  635. "select count(*) as count from bulktest2;"
  636. "select count(*) as count from bulktest3;";
  637. success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
  638. NSInteger count = [dictionary[@"count"] integerValue];
  639. XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
  640. return 0;
  641. }];
  642. XCTAssertTrue(success, @"bulk select");
  643. sql = @"drop table bulktest1;"
  644. "drop table bulktest2;"
  645. "drop table bulktest3;";
  646. success = [self.db executeStatements:sql];
  647. XCTAssertTrue(success, @"bulk drop");
  648. }
  649. - (void)testCharAndBoolTypes
  650. {
  651. XCTAssertTrue([self.db executeUpdate:@"create table charBoolTest (a, b, c)"]);
  652. BOOL success = [self.db executeUpdate:@"insert into charBoolTest values (?, ?, ?)", @YES, @NO, @('x')];
  653. XCTAssertTrue(success, @"Unable to insert values");
  654. FMResultSet *rs = [self.db executeQuery:@"select * from charBoolTest"];
  655. XCTAssertNotNil(rs);
  656. XCTAssertTrue([rs next], @"Did not return row");
  657. XCTAssertEqual([rs boolForColumn:@"a"], true);
  658. XCTAssertEqualObjects([rs objectForColumnName:@"a"], @YES);
  659. XCTAssertEqual([rs boolForColumn:@"b"], false);
  660. XCTAssertEqualObjects([rs objectForColumnName:@"b"], @NO);
  661. XCTAssertEqual([rs intForColumn:@"c"], 'x');
  662. XCTAssertEqualObjects([rs objectForColumnName:@"c"], @('x'));
  663. [rs close];
  664. XCTAssertTrue([self.db executeUpdate:@"drop table charBoolTest"], @"Did not drop table");
  665. }
  666. @end