FMDatabaseTests.m 57 KB

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