FMDatabaseTests.m 30 KB

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