// // Tests.m // Tests // // Created by Graham Dennis on 24/11/2013. // // #import "FMDBTempDBTests.h" #import "FMDatabase.h" #import "FMDatabaseAdditions.h" @interface FMDatabaseTests : FMDBTempDBTests @end @implementation FMDatabaseTests + (void)populateDatabase:(FMDatabase *)db { [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"]; [db beginTransaction]; int i = 0; while (i++ < 20) { [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" , @"hi'", // look! I put in a ', and I'm not escaping it! [NSString stringWithFormat:@"number %d", i], [NSNumber numberWithInt:i], [NSDate date], [NSNumber numberWithFloat:2.2f]]; } [db commit]; // do it again, just because [db beginTransaction]; i = 0; while (i++ < 20) { [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" , @"hi again'", // look! I put in a ', and I'm not escaping it! [NSString stringWithFormat:@"number %d", i], [NSNumber numberWithInt:i], [NSDate date], [NSNumber numberWithFloat:2.2f]]; } [db commit]; [db executeUpdate:@"create table t3 (a somevalue)"]; [db beginTransaction]; for (int i=0; i < 20; i++) { [db executeUpdate:@"insert into t3 (a) values (?)", [NSNumber numberWithInt:i]]; } [db commit]; } - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testOpenWithVFS { // create custom vfs sqlite3_vfs vfs = *sqlite3_vfs_find(NULL); vfs.zName = "MyCustomVFS"; XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0)); // use custom vfs to open a in memory database FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"]; [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"]; XCTAssertFalse([db hadError], @"Open with a custom VFS should have succeeded"); } - (void)testFailOnOpenWithUnknownVFS { FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"]; [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"UnknownVFS"]; XCTAssertTrue([db hadError], @"Should have failed"); } - (void)testFailOnUnopenedDatabase { [self.db close]; XCTAssertNil([self.db executeQuery:@"select * from table"], @"Shouldn't get results from an empty table"); XCTAssertTrue([self.db hadError], @"Should have failed"); } - (void)testFailOnBadStatement { XCTAssertFalse([self.db executeUpdate:@"blah blah blah"], @"Invalid statement should fail"); XCTAssertTrue([self.db hadError], @"Should have failed"); } - (void)testFailOnBadStatementWithError { NSError *error = nil; XCTAssertFalse([self.db executeUpdate:@"blah blah blah" withErrorAndBindings:&error], @"Invalid statement should fail"); XCTAssertNotNil(error, @"Should have a non-nil NSError"); XCTAssertEqual([error code], (NSInteger)SQLITE_ERROR, @"Error should be SQLITE_ERROR"); } - (void)testPragmaJournalMode { FMResultSet *ps = [self.db executeQuery:@"pragma journal_mode=delete"]; XCTAssertFalse([self.db hadError], @"pragma should have succeeded"); XCTAssertNotNil(ps, @"Result set should be non-nil"); XCTAssertTrue([ps next], @"Result set should have a next result"); [ps close]; } - (void)testPragmaPageSize { [self.db executeUpdate:@"PRAGMA page_size=2048"]; XCTAssertFalse([self.db hadError], @"pragma should have succeeded"); } - (void)testVacuum { [self.db executeUpdate:@"VACUUM"]; XCTAssertFalse([self.db hadError], @"VACUUM should have succeeded"); } - (void)testSelectULL { // Unsigned long long [self.db executeUpdate:@"create table ull (a integer)"]; [self.db executeUpdate:@"insert into ull (a) values (?)", [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]]; XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); FMResultSet *rs = [self.db executeQuery:@"select a from ull"]; while ([rs next]) { XCTAssertEqual([rs unsignedLongLongIntForColumnIndex:0], ULLONG_MAX, @"Result should be ULLONG_MAX"); XCTAssertEqual([rs unsignedLongLongIntForColumn:@"a"], ULLONG_MAX, @"Result should be ULLONG_MAX"); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testSelectByColumnName { FMResultSet *rs = [self.db executeQuery:@"select rowid,* from test where a = ?", @"hi"]; XCTAssertNotNil(rs, @"Should have a non-nil result set"); while ([rs next]) { [rs intForColumn:@"c"]; XCTAssertNotNil([rs stringForColumn:@"b"], @"Should have non-nil string for 'b'"); XCTAssertNotNil([rs stringForColumn:@"a"], @"Should have non-nil string for 'a'"); XCTAssertNotNil([rs stringForColumn:@"rowid"], @"Should have non-nil string for 'rowid'"); XCTAssertNotNil([rs dateForColumn:@"d"], @"Should have non-nil date for 'd'"); [rs doubleForColumn:@"d"]; [rs doubleForColumn:@"e"]; XCTAssertEqualObjects([rs columnNameForIndex:0], @"rowid", @"Wrong column name for result set column number"); XCTAssertEqualObjects([rs columnNameForIndex:1], @"a", @"Wrong column name for result set column number"); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testSelectWithIndexedAndKeyedSubscript { FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"]; XCTAssertNotNil(rs, @"Should have a non-nil result set"); while ([rs next]) { XCTAssertEqualObjects(rs[0], rs[@"rowid"], @"Column zero should be equal to 'rowid'"); XCTAssertEqualObjects(rs[1], rs[@"a"], @"Column 1 should be equal to 'a'"); XCTAssertEqualObjects(rs[2], rs[@"b"], @"Column 2 should be equal to 'b'"); XCTAssertEqualObjects(rs[3], rs[@"c"], @"Column 3 should be equal to 'c'"); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testBusyRetryTimeout { [self.db executeUpdate:@"create table t1 (a integer)"]; [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]]; [self.db setMaxBusyRetryTimeInterval:2]; FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath]; [newDB open]; FMResultSet *rs = [newDB executeQuery:@"select rowid,* from test where a = ?", @"hi'"]; [rs next]; // just grab one... which will keep the db locked XCTAssertFalse([self.db executeUpdate:@"insert into t1 values (5)"], @"Insert should fail because the db is locked by a read"); XCTAssertEqual([self.db lastErrorCode], SQLITE_BUSY, @"SQLITE_BUSY should be the last error"); [rs close]; [newDB close]; XCTAssertTrue([self.db executeUpdate:@"insert into t1 values (5)"], @"The database shouldn't be locked at this point"); } - (void)testCaseSensitiveResultDictionary { // case sensitive result dictionary test [self.db executeUpdate:@"create table cs (aRowName integer, bRowName text)"]; [self.db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)", [NSNumber numberWithBool:1], @"hello"]; XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); FMResultSet *rs = [self.db executeQuery:@"select * from cs"]; while ([rs next]) { NSDictionary *d = [rs resultDictionary]; XCTAssertNotNil([d objectForKey:@"aRowName"], @"aRowName should be non-nil"); XCTAssertNil([d objectForKey:@"arowname"], @"arowname should be nil"); XCTAssertNotNil([d objectForKey:@"bRowName"], @"bRowName should be non-nil"); XCTAssertNil([d objectForKey:@"browname"], @"browname should be nil"); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testBoolInsert { [self.db executeUpdate:@"create table btest (aRowName integer)"]; [self.db executeUpdate:@"insert into btest (aRowName) values (?)", [NSNumber numberWithBool:12]]; XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); FMResultSet *rs = [self.db executeQuery:@"select * from btest"]; while ([rs next]) { XCTAssertTrue([rs boolForColumnIndex:0], @"first column should be true."); XCTAssertTrue([rs intForColumnIndex:0] == 1, @"first column should be equal to 1 - it was %d.", [rs intForColumnIndex:0]); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testNamedParametersCount { XCTAssertTrue([self.db executeUpdate:@"create table namedparamcounttest (a text, b text, c integer, d double)"]); NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary]; [dictionaryArgs setObject:@"Text1" forKey:@"a"]; [dictionaryArgs setObject:@"Text2" forKey:@"b"]; [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"]; [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"]; XCTAssertTrue([self.db executeUpdate:@"insert into namedparamcounttest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]); FMResultSet *rs = [self.db executeQuery:@"select * from namedparamcounttest"]; XCTAssertNotNil(rs); [rs next]; XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1"); XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2"); XCTAssertEqual([rs intForColumn:@"c"], 1); XCTAssertEqual([rs doubleForColumn:@"d"], 2.0); [rs close]; // note that at this point, dictionaryArgs has way more values than we need, but the query should still work since // a is in there, and that's all we need. rs = [self.db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); [rs close]; // ***** Please note the following codes ***** dictionaryArgs = [NSMutableDictionary dictionary]; [dictionaryArgs setObject:@"NewText1" forKey:@"a"]; [dictionaryArgs setObject:@"NewText2" forKey:@"b"]; [dictionaryArgs setObject:@"OneMoreText" forKey:@"OneMore"]; XCTAssertTrue([self.db executeUpdate:@"update namedparamcounttest set a = :a, b = :b where b = 'Text2'" withParameterDictionary:dictionaryArgs]); } - (void)testBlobs { [self.db executeUpdate:@"create table blobTable (a text, b blob)"]; // let's read an image from safari's app bundle. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"]; if (safariCompass) { [self.db executeUpdate:@"insert into blobTable (a, b) values (?, ?)", @"safari's compass", safariCompass]; FMResultSet *rs = [self.db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"]; XCTAssertTrue([rs next]); NSData *readData = [rs dataForColumn:@"b"]; XCTAssertEqualObjects(readData, safariCompass); // ye shall read the header for this function, or suffer the consequences. NSData *readDataNoCopy = [rs dataNoCopyForColumn:@"b"]; XCTAssertEqualObjects(readDataNoCopy, safariCompass); [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } } - (void)testNullValues { [self.db executeUpdate:@"create table t2 (a integer, b integer)"]; BOOL result = [self.db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]]; XCTAssertTrue(result, @"Failed to insert a nil value"); FMResultSet *rs = [self.db executeQuery:@"select * from t2"]; while ([rs next]) { XCTAssertNil([rs stringForColumnIndex:0], @"Wasn't able to retrieve a null string"); XCTAssertEqualObjects([rs stringForColumnIndex:1], @"5"); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testNestedResultSets { FMResultSet *rs = [self.db executeQuery:@"select * from t3"]; while ([rs next]) { int foo = [rs intForColumnIndex:0]; int newVal = foo + 100; [self.db executeUpdate:@"update t3 set a = ? where a = ?", [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]]; FMResultSet *rs2 = [self.db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]]; [rs2 next]; XCTAssertEqual([rs2 intForColumnIndex:0], newVal); [rs2 close]; } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testNSNullInsertion { [self.db executeUpdate:@"create table nulltest (a text, b text)"]; [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", [NSNull null], @"a"]; [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", nil, @"b"]; FMResultSet *rs = [self.db executeQuery:@"select * from nulltest"]; while ([rs next]) { XCTAssertNil([rs stringForColumnIndex:0]); XCTAssertNotNil([rs stringForColumnIndex:1]); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testNullDates { NSDate *date = [NSDate date]; [self.db executeUpdate:@"create table datetest (a double, b double, c double)"]; [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date]; FMResultSet *rs = [self.db executeQuery:@"select * from datetest"]; XCTAssertNotNil(rs); while ([rs next]) { NSDate *b = [rs dateForColumnIndex:1]; NSDate *c = [rs dateForColumnIndex:2]; XCTAssertNil([rs dateForColumnIndex:0]); XCTAssertNotNil(c, @"zero date shouldn't be nil"); XCTAssertEqualWithAccuracy([b timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second"); XCTAssertEqualWithAccuracy([c timeIntervalSince1970], 0.0, 1.0, @"Dates should be the same to within a second"); } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testLotsOfNULLs { NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"]; if (!safariCompass) return; [self.db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"]; [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]]; [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]]; FMResultSet *rs = [self.db executeQuery:@"select * from nulltest2"]; while ([rs next]) { int i = [rs intForColumnIndex:2]; if (i == 12) { // it's the first row we inserted. XCTAssertFalse([rs columnIndexIsNull:0]); XCTAssertFalse([rs columnIndexIsNull:1]); XCTAssertFalse([rs columnIndexIsNull:2]); XCTAssertFalse([rs columnIndexIsNull:3]); XCTAssertFalse([rs columnIndexIsNull:4]); XCTAssertTrue( [rs columnIndexIsNull:5]); XCTAssertEqualObjects([rs dataForColumn:@"d"], safariCompass); XCTAssertNil([rs dataForColumn:@"notthere"]); XCTAssertNil([rs stringForColumnIndex:-2], @"Negative columns should return nil results"); XCTAssertTrue([rs boolForColumnIndex:4]); XCTAssertTrue([rs boolForColumn:@"b"]); XCTAssertEqualWithAccuracy(4.4, [rs doubleForColumn:@"f"], 0.0000001, @"Saving a float and returning it as a double shouldn't change the result much"); XCTAssertEqual([rs intForColumn:@"i"], 12); XCTAssertEqual([rs intForColumnIndex:2], 12); XCTAssertEqual([rs intForColumnIndex:12], 0, @"Non-existent columns should return zero for ints"); XCTAssertEqual([rs intForColumn:@"notthere"], 0, @"Non-existent columns should return zero for ints"); XCTAssertEqual([rs longForColumn:@"i"], 12l); XCTAssertEqual([rs longLongIntForColumn:@"i"], 12ll); } else { // let's test various null things. XCTAssertTrue([rs columnIndexIsNull:0]); XCTAssertTrue([rs columnIndexIsNull:1]); XCTAssertTrue([rs columnIndexIsNull:2]); XCTAssertTrue([rs columnIndexIsNull:3]); XCTAssertTrue([rs columnIndexIsNull:4]); XCTAssertTrue([rs columnIndexIsNull:5]); XCTAssertNil([rs dataForColumn:@"d"]); } } [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testUTF8Strings { [self.db executeUpdate:@"create table utest (a text)"]; [self.db executeUpdate:@"insert into utest values (?)", @"/übertest"]; FMResultSet *rs = [self.db executeQuery:@"select * from utest where a = ?", @"/übertest"]; XCTAssertTrue([rs next]); [rs close]; XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets"); XCTAssertFalse([self.db hadError], @"Shouldn't have any errors"); } - (void)testArgumentsInArray { [self.db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"]; [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]]; [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]]; FMResultSet *rs = [self.db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]]; XCTAssertTrue([rs next]); XCTAssertTrue([rs hasAnotherRow]); XCTAssertFalse([self.db hadError]); XCTAssertEqualObjects([rs stringForColumnIndex:0], @"one"); XCTAssertEqual([rs intForColumnIndex:1], 2); XCTAssertTrue([rs next]); XCTAssertEqual([rs intForColumnIndex:1], 3); XCTAssertFalse([rs next]); XCTAssertFalse([rs hasAnotherRow]); } - (void)testColumnNamesContainingPeriods { XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]); [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"]; FMResultSet *rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one"); XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two"); XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two"), 0, @"String comparison should return zero"); [rs close]; // let's try these again, with the withArgumentsInArray: variation XCTAssertTrue([self.db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]); XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]); [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]]; rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one"); XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two"); XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two"), 0, @"String comparison should return zero"); [rs close]; } - (void)testFormatStringParsing { XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]); [self.db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234ll]; FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"a"], @"text"); XCTAssertEqual([rs intForColumn:@"b"], 42); XCTAssertEqualObjects([rs stringForColumn:@"c"], @"BLOB"); XCTAssertEqualObjects([rs stringForColumn:@"d"], @"d"); XCTAssertEqual([rs longLongIntForColumn:@"e"], 12345678901234ll); [rs close]; } - (void)testFormatStringParsingWithSizePrefixes { XCTAssertTrue([self.db executeUpdate:@"create table t55 (a text, b int, c float)"]); short testShort = -4; float testFloat = 5.5; [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat]; unsigned short testUShort = 6; [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat]; FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a"); XCTAssertEqual([rs intForColumn:@"b"], -4); XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5"); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a"); XCTAssertEqual([rs intForColumn:@"b"], 6); XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5"); [rs close]; } - (void)testFormatStringParsingWithNilValue { XCTAssertTrue([self.db executeUpdate:@"create table tatwhat (a text)"]); BOOL worked = [self.db executeUpdateWithFormat:@"insert into tatwhat values(%@)", nil]; XCTAssertTrue(worked); FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from tatwhat"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertTrue([rs columnIndexIsNull:0]); XCTAssertFalse([rs next]); } - (void)testUpdateWithErrorAndBindings { XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]); NSError *err = nil; BOOL result = [self.db executeUpdate:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]]; XCTAssertTrue(result); } - (void)testSelectWithEmptyArgumentsArray { FMResultSet *rs = [self.db executeQuery:@"select * from test where a=?" withArgumentsInArray:@[]]; XCTAssertNil(rs); } - (void)testDatabaseAttach { NSFileManager *fileManager = [NSFileManager new]; [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil]; FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"]; XCTAssertTrue([dbB open]); XCTAssertTrue([dbB executeUpdate:@"create table attached (a text)"]); XCTAssertTrue(([dbB executeUpdate:@"insert into attached values (?)", @"test"])); XCTAssertTrue([dbB close]); [self.db executeUpdate:@"attach database '/tmp/attachme.db' as attack"]; FMResultSet *rs = [self.db executeQuery:@"select * from attack.attached"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); [rs close]; } - (void)testNamedParameters { // ------------------------------------------------------------------------------- // Named parameters. XCTAssertTrue([self.db executeUpdate:@"create table namedparamtest (a text, b text, c integer, d double)"]); NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary]; [dictionaryArgs setObject:@"Text1" forKey:@"a"]; [dictionaryArgs setObject:@"Text2" forKey:@"b"]; [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"]; [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"]; XCTAssertTrue([self.db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]); FMResultSet *rs = [self.db executeQuery:@"select * from namedparamtest"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1"); XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2"); XCTAssertEqual([rs intForColumn:@"c"], 1); XCTAssertEqual([rs doubleForColumn:@"d"], 2.0); [rs close]; dictionaryArgs = [NSMutableDictionary dictionary]; [dictionaryArgs setObject:@"Text2" forKey:@"blah"]; rs = [self.db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2"); [rs close]; } - (void)testPragmaDatabaseList { FMResultSet *rs = [self.db executeQuery:@"pragma database_list"]; int counter = 0; while ([rs next]) { counter++; XCTAssertEqualObjects([rs stringForColumn:@"file"], self.databasePath); } XCTAssertEqual(counter, 1, @"Only one database should be attached"); } - (void)testCachedStatementsInUse { [self.db setShouldCacheStatements:true]; [self.db executeUpdate:@"CREATE TABLE testCacheStatements(key INTEGER PRIMARY KEY, value INTEGER)"]; [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (1, 2)"]; [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (2, 4)"]; XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]); XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]); } - (void)testStatementCachingWorks { [self.db executeUpdate:@"CREATE TABLE testStatementCaching ( value INTEGER )"]; [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"]; [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"]; [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (2)"]; [self.db setShouldCacheStatements:YES]; // two iterations. // the first time through no statements will be from the cache. // the second time through all statements come from the cache. for (int i = 1; i <= 2; i++ ) { FMResultSet* rs1 = [self.db executeQuery: @"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @1]; // results in 2 rows... XCTAssertNotNil(rs1); XCTAssertTrue([rs1 next]); // confirm that we're seeing the benefits of caching. XCTAssertEqual([[rs1 statement] useCount], (long)i); FMResultSet* rs2 = [self.db executeQuery:@"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @2]; // results in 1 row XCTAssertNotNil(rs2); XCTAssertTrue([rs2 next]); XCTAssertEqual([[rs2 statement] useCount], (long)i); // 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. XCTAssertTrue([rs1 next]); [rs1 close]; [rs2 close]; } } /* Test the date format */ - (void)testDateFormat { void (^testOneDateFormat)(FMDatabase *, NSDate *) = ^( FMDatabase *db, NSDate *testDate ){ [db executeUpdate:@"DROP TABLE IF EXISTS test_format"]; [db executeUpdate:@"CREATE TABLE test_format ( test TEXT )"]; [db executeUpdate:@"INSERT INTO test_format(test) VALUES (?)", testDate]; FMResultSet *rs = [db executeQuery:@"SELECT test FROM test_format"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next]); XCTAssertEqualObjects([rs dateForColumnIndex:0], testDate); [rs close]; }; NSDateFormatter *fmt = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"]; NSDate *testDate = [fmt dateFromString:@"2013-02-20 12:00:00"]; // test timestamp dates (ensuring our change does not break those) testOneDateFormat(self.db,testDate); // now test the string-based timestamp [self.db setDateFormat:fmt]; testOneDateFormat(self.db, testDate); } - (void)testColumnNameMap { XCTAssertTrue([self.db executeUpdate:@"create table colNameTest (a, b, c, d)"]); XCTAssertTrue([self.db executeUpdate:@"insert into colNameTest values (1, 2, 3, 4)"]); FMResultSet *ars = [self.db executeQuery:@"select * from colNameTest"]; XCTAssertNotNil(ars); NSDictionary *d = [ars columnNameToIndexMap]; XCTAssertEqual([d count], (NSUInteger)4); XCTAssertEqualObjects([d objectForKey:@"a"], @0); XCTAssertEqualObjects([d objectForKey:@"b"], @1); XCTAssertEqualObjects([d objectForKey:@"c"], @2); XCTAssertEqualObjects([d objectForKey:@"d"], @3); } - (void)testCustomFunction { [self.db executeUpdate:@"create table ftest (foo text)"]; [self.db executeUpdate:@"insert into ftest values ('hello')"]; [self.db executeUpdate:@"insert into ftest values ('hi')"]; [self.db executeUpdate:@"insert into ftest values ('not h!')"]; [self.db executeUpdate:@"insert into ftest values ('definitely not h!')"]; [self.db makeFunctionNamed:@"StringStartsWithH" maximumArguments:1 withBlock:^(sqlite3_context *context, int aargc, sqlite3_value **aargv) { if (sqlite3_value_type(aargv[0]) == SQLITE_TEXT) { @autoreleasepool { const char *c = (const char *)sqlite3_value_text(aargv[0]); NSString *s = [NSString stringWithUTF8String:c]; sqlite3_result_int(context, [s hasPrefix:@"h"]); } } else { XCTFail(@"Unknown format for StringStartsWithH (%d)", sqlite3_value_type(aargv[0])); sqlite3_result_null(context); } }]; int rowCount = 0; FMResultSet *ars = [self.db executeQuery:@"select * from ftest where StringStartsWithH(foo)"]; while ([ars next]) { rowCount++; } XCTAssertEqual(rowCount, 2); } - (void)testVersionNumber { XCTAssertTrue([FMDatabase FMDBVersion] == 0x0250); // this is going to break everytime we bump it. } - (void)testExecuteStatements { BOOL success; NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);" "create table bulktest2 (id integer primary key autoincrement, y text);" "create table bulktest3 (id integer primary key autoincrement, z text);" "insert into bulktest1 (x) values ('XXX');" "insert into bulktest2 (y) values ('YYY');" "insert into bulktest3 (z) values ('ZZZ');"; success = [self.db executeStatements:sql]; XCTAssertTrue(success, @"bulk create"); sql = @"select count(*) as count from bulktest1;" "select count(*) as count from bulktest2;" "select count(*) as count from bulktest3;"; success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) { NSInteger count = [dictionary[@"count"] integerValue]; XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary); return 0; }]; XCTAssertTrue(success, @"bulk select"); sql = @"drop table bulktest1;" "drop table bulktest2;" "drop table bulktest3;"; success = [self.db executeStatements:sql]; XCTAssertTrue(success, @"bulk drop"); } - (void)testCharAndBoolTypes { XCTAssertTrue([self.db executeUpdate:@"create table charBoolTest (a, b, c)"]); BOOL success = [self.db executeUpdate:@"insert into charBoolTest values (?, ?, ?)", @YES, @NO, @('x')]; XCTAssertTrue(success, @"Unable to insert values"); FMResultSet *rs = [self.db executeQuery:@"select * from charBoolTest"]; XCTAssertNotNil(rs); XCTAssertTrue([rs next], @"Did not return row"); XCTAssertEqual([rs boolForColumn:@"a"], true); XCTAssertEqualObjects([rs objectForColumnName:@"a"], @YES); XCTAssertEqual([rs boolForColumn:@"b"], false); XCTAssertEqualObjects([rs objectForColumnName:@"b"], @NO); XCTAssertEqual([rs intForColumn:@"c"], 'x'); XCTAssertEqualObjects([rs objectForColumnName:@"c"], @('x')); [rs close]; XCTAssertTrue([self.db executeUpdate:@"drop table charBoolTest"], @"Did not drop table"); } @end