#import #import "FMDatabase.h" #import "FMDatabaseAdditions.h" #import "FMDatabasePool.h" #import "FMDatabaseQueue.h" #define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) { NSLog(@"Failure on line %d", __LINE__); abort(); } } void testPool(NSString *dbPath); void testDateFormat(); void FMDBReportABugFunction(); int main (int argc, const char * argv[]) { @autoreleasepool { FMDBReportABugFunction(); NSString *dbPath = @"/tmp/tmp.db"; // delete the old db. NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager removeItemAtPath:dbPath error:nil]; FMDatabase *db = [FMDatabase databaseWithPath:dbPath]; NSLog(@"Is SQLite compiled with it's thread safe options turned on? %@!", [FMDatabase isSQLiteThreadSafe] ? @"Yes" : @"No"); { // ------------------------------------------------------------------------------- // Un-opened database check. FMDBQuickCheck([db executeQuery:@"select * from table"] == nil); NSLog(@"%d: %@", [db lastErrorCode], [db lastErrorMessage]); } if (![db open]) { NSLog(@"Could not open db."); return 0; } // kind of experimentalish. [db setShouldCacheStatements:YES]; // create a bad statement, just to test the error code. [db executeUpdate:@"blah blah blah"]; FMDBQuickCheck([db hadError]); if ([db hadError]) { NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]); } NSError *err = 0x00; FMDBQuickCheck(![db update:@"blah blah blah" withErrorAndBindings:&err]); FMDBQuickCheck(err != nil); FMDBQuickCheck([err code] == SQLITE_ERROR); NSLog(@"err: '%@'", err); // empty strings should still return a value. FMDBQuickCheck(([db boolForQuery:@"SELECT ? not null", @""])); // same with empty bits o' mutable data FMDBQuickCheck(([db boolForQuery:@"SELECT ? not null", [NSMutableData data]])); // same with empty bits o' data FMDBQuickCheck(([db boolForQuery:@"SELECT ? not null", [NSData data]])); // how do we do pragmas? Like so: FMResultSet *ps = [db executeQuery:@"PRAGMA journal_mode=delete"]; FMDBQuickCheck(![db hadError]); FMDBQuickCheck(ps); FMDBQuickCheck([ps next]); [ps close]; // oh, but some pragmas require updates? [db executeUpdate:@"PRAGMA page_size=2048"]; FMDBQuickCheck(![db hadError]); // what about a vacuum? [db executeUpdate:@"vacuum"]; FMDBQuickCheck(![db hadError]); // but of course, I don't bother checking the error codes below. // Bad programmer, no cookie. [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]; FMResultSet *rs = [db executeQuery:@"select rowid,* from test where a = ?", @"hi'"]; while ([rs next]) { // just print out what we've got in a number of formats. NSLog(@"%d %@ %@ %@ %@ %f %f", [rs intForColumn:@"c"], [rs stringForColumn:@"b"], [rs stringForColumn:@"a"], [rs stringForColumn:@"rowid"], [rs dateForColumn:@"d"], [rs doubleForColumn:@"d"], [rs doubleForColumn:@"e"]); if (!([[rs columnNameForIndex:0] isEqualToString:@"rowid"] && [[rs columnNameForIndex:1] isEqualToString:@"a"]) ) { NSLog(@"WHOA THERE BUDDY, columnNameForIndex ISN'T WORKING!"); return 7; } } // close the result set. // it'll also close when it's dealloc'd, but we're closing the database before // the autorelease pool closes, so sqlite will complain about it. [rs close]; FMDBQuickCheck(![db hasOpenResultSets]); rs = [db executeQuery:@"select rowid, a, b, c from test"]; while ([rs next]) { FMDBQuickCheck([rs[0] isEqualTo:rs[@"rowid"]]); FMDBQuickCheck([rs[1] isEqualTo:rs[@"a"]]); FMDBQuickCheck([rs[2] isEqualTo:rs[@"b"]]); FMDBQuickCheck([rs[3] isEqualTo:rs[@"c"]]); } [rs close]; [db executeUpdate:@"create table ull (a integer)"]; [db executeUpdate:@"insert into ull (a) values (?)" , [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]]; rs = [db executeQuery:@"select a from ull"]; while ([rs next]) { unsigned long long a = [rs unsignedLongLongIntForColumnIndex:0]; unsigned long long b = [rs unsignedLongLongIntForColumn:@"a"]; FMDBQuickCheck(a == ULLONG_MAX); FMDBQuickCheck(b == ULLONG_MAX); } // check case sensitive result dictionary. [db executeUpdate:@"create table cs (aRowName integer, bRowName text)"]; FMDBQuickCheck(![db hadError]); [db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)" , [NSNumber numberWithBool:1], @"hello"]; FMDBQuickCheck(![db hadError]); rs = [db executeQuery:@"select * from cs"]; while ([rs next]) { NSDictionary *d = [rs resultDictionary]; FMDBQuickCheck([d objectForKey:@"aRowName"]); FMDBQuickCheck(![d objectForKey:@"arowname"]); FMDBQuickCheck([d objectForKey:@"bRowName"]); FMDBQuickCheck(![d objectForKey:@"browname"]); } // check funky table names + getTableSchema [db executeUpdate:@"create table '234 fds' (foo text)"]; FMDBQuickCheck(![db hadError]); rs = [db getTableSchema:@"234 fds"]; FMDBQuickCheck([rs next]); [rs close]; { // ------------------------------------------------------------------------------- // Named parameters count test. FMDBQuickCheck([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"]; FMDBQuickCheck([db executeUpdate:@"insert into namedparamcounttest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]); rs = [db executeQuery:@"select * from namedparamcounttest"]; FMDBQuickCheck((rs != nil)); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"Text1"]); FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"Text2"]); FMDBQuickCheck([rs intForColumn:@"c"] == 1); FMDBQuickCheck([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 = [db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs]; FMDBQuickCheck((rs != nil)); FMDBQuickCheck([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"]; BOOL rc = [db executeUpdate:@"update namedparamcounttest set a = :a, b = :b where b = 'Text2'" withParameterDictionary:dictionaryArgs]; FMDBQuickCheck(rc); if (!rc) { NSLog(@"ERROR: %d - %@", db.lastErrorCode, db.lastErrorMessage); } } // ---------------------------------------------------------------------------------------- // blob support. [db executeUpdate:@"create table blobTable (a text, b blob)"]; // let's read in an image from safari's app bundle. NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"]; if (safariCompass) { [db executeUpdate:@"insert into blobTable (a, b) values (?,?)", @"safari's compass", safariCompass]; rs = [db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"]; if ([rs next]) { safariCompass = [rs dataForColumn:@"b"]; [safariCompass writeToFile:@"/tmp/compass.icns" atomically:NO]; // let's look at our fancy image that we just wrote out.. system("/usr/bin/open /tmp/compass.icns"); // ye shall read the header for this function, or suffer the consequences. safariCompass = [rs dataNoCopyForColumn:@"b"]; [safariCompass writeToFile:@"/tmp/compass_data_no_copy.icns" atomically:NO]; system("/usr/bin/open /tmp/compass_data_no_copy.icns"); } else { NSLog(@"Could not select image."); } [rs close]; } else { NSLog(@"Can't find compass image.."); } // test out the convenience methods in +Additions [db executeUpdate:@"create table t1 (a integer)"]; [db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]]; NSLog(@"Count of changes (should be 1): %d", [db changes]); FMDBQuickCheck([db changes] == 1); int ia = [db intForQuery:@"select a from t1 where a = ?", [NSNumber numberWithInt:5]]; if (ia != 5) { NSLog(@"intForQuery didn't work (a != 5)"); } // test the busy rety timeout schtuff. [db setBusyRetryTimeout:500]; FMDatabase *newDb = [FMDatabase databaseWithPath:dbPath]; [newDb open]; rs = [newDb executeQuery:@"select rowid,* from test where a = ?", @"hi'"]; [rs next]; // just grab one... which will keep the db locked. NSLog(@"Testing the busy timeout"); BOOL success = [db executeUpdate:@"insert into t1 values (5)"]; if (success) { NSLog(@"Whoa- the database didn't stay locked!"); return 7; } else { NSLog(@"Hurray, our timeout worked"); } [rs close]; [newDb close]; success = [db executeUpdate:@"insert into t1 values (5)"]; if (!success) { NSLog(@"Whoa- the database shouldn't be locked!"); return 8; } else { NSLog(@"Hurray, we can insert again!"); } // test some nullness. [db executeUpdate:@"create table t2 (a integer, b integer)"]; if (![db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]]) { NSLog(@"UH OH, can't insert a nil value for some reason..."); } rs = [db executeQuery:@"select * from t2"]; while ([rs next]) { NSString *aa = [rs stringForColumnIndex:0]; NSString *b = [rs stringForColumnIndex:1]; if (aa != nil) { NSLog(@"%s:%d", __FUNCTION__, __LINE__); NSLog(@"OH OH, PROBLEMO!"); return 10; } else { NSLog(@"YAY, NULL VALUES"); } if (![b isEqualToString:@"5"]) { NSLog(@"%s:%d", __FUNCTION__, __LINE__); NSLog(@"OH OH, PROBLEMO!"); return 10; } } // test some inner loop funkness. [db executeUpdate:@"create table t3 (a somevalue)"]; // do it again, just because [db beginTransaction]; i = 0; while (i++ < 20) { [db executeUpdate:@"insert into t3 (a) values (?)" , [NSNumber numberWithInt:i]]; } [db commit]; rs = [db executeQuery:@"select * from t3"]; while ([rs next]) { int foo = [rs intForColumnIndex:0]; int newVal = foo + 100; [db executeUpdate:@"update t3 set a = ? where a = ?" , [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]]; FMResultSet *rs2 = [db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]]; [rs2 next]; if ([rs2 intForColumnIndex:0] != newVal) { NSLog(@"Oh crap, our update didn't work out!"); return 9; } [rs2 close]; } // NSNull tests [db executeUpdate:@"create table nulltest (a text, b text)"]; [db executeUpdate:@"insert into nulltest (a, b) values (?, ?)" , [NSNull null], @"a"]; [db executeUpdate:@"insert into nulltest (a, b) values (?, ?)" , nil, @"b"]; rs = [db executeQuery:@"select * from nulltest"]; while ([rs next]) { NSString *a = [rs stringForColumnIndex:0]; NSString *b = [rs stringForColumnIndex:1]; if (!b) { NSLog(@"Oh crap, the nil / null inserts didn't work!"); return 10; } if (a) { NSLog(@"Oh crap, the nil / null inserts didn't work (son of error message)!"); return 11; } else { NSLog(@"HURRAH FOR NSNULL (and nil)!"); } } FMDBQuickCheck([db columnExists:@"a" inTableWithName:@"nulltest"]); FMDBQuickCheck([db columnExists:@"b" inTableWithName:@"nulltest"]); FMDBQuickCheck(![db columnExists:@"c" inTableWithName:@"nulltest"]); // null dates NSDate *date = [NSDate date]; [db executeUpdate:@"create table datetest (a double, b double, c double)"]; [db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date]; rs = [db executeQuery:@"select * from datetest"]; while ([rs next]) { NSDate *a = [rs dateForColumnIndex:0]; NSDate *b = [rs dateForColumnIndex:1]; NSDate *c = [rs dateForColumnIndex:2]; if (a) { NSLog(@"Oh crap, the null date insert didn't work!"); return 12; } if (!c) { NSLog(@"Oh crap, the 0 date insert didn't work!"); return 12; } NSTimeInterval dti = fabs([b timeIntervalSinceDate:date]); if (floor(dti) > 0.0) { NSLog(@"Date matches didn't really happen... time difference of %f", dti); return 13; } dti = fabs([c timeIntervalSinceDate:[NSDate dateWithTimeIntervalSince1970:0]]); if (floor(dti) > 0.0) { NSLog(@"Date matches didn't really happen... time difference of %f", dti); return 13; } } NSDate *foo = [db dateForQuery:@"select b from datetest where c = 0"]; assert(foo); NSTimeInterval dti = fabs([foo timeIntervalSinceDate:date]); if (floor(dti) > 0.0) { NSLog(@"Date matches didn't really happen... time difference of %f", dti); return 14; } [db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"]; // grab the data for this again, since we overwrote it with some memory that has since disapeared. safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"]; [db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]]; [db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]]; rs = [db executeQuery:@"select * from nulltest2"]; while ([rs next]) { i = [rs intForColumnIndex:2]; if (i == 12) { // it's the first row we inserted. FMDBQuickCheck(![rs columnIndexIsNull:0]); FMDBQuickCheck(![rs columnIndexIsNull:1]); FMDBQuickCheck(![rs columnIndexIsNull:2]); FMDBQuickCheck(![rs columnIndexIsNull:3]); FMDBQuickCheck(![rs columnIndexIsNull:4]); FMDBQuickCheck( [rs columnIndexIsNull:5]); FMDBQuickCheck([[rs dataForColumn:@"d"] length] == [safariCompass length]); FMDBQuickCheck(![rs dataForColumn:@"notthere"]); FMDBQuickCheck(![rs stringForColumnIndex:-2]); FMDBQuickCheck([rs boolForColumnIndex:4]); FMDBQuickCheck([rs boolForColumn:@"b"]); FMDBQuickCheck(fabs(4.4 - [rs doubleForColumn:@"f"]) < 0.0000001); FMDBQuickCheck(12 == [rs intForColumn:@"i"]); FMDBQuickCheck(12 == [rs intForColumnIndex:2]); FMDBQuickCheck(0 == [rs intForColumnIndex:12]); // there is no 12 FMDBQuickCheck(0 == [rs intForColumn:@"notthere"]); FMDBQuickCheck(12 == [rs longForColumn:@"i"]); FMDBQuickCheck(12 == [rs longLongIntForColumn:@"i"]); } else { // let's test various null things. FMDBQuickCheck([rs columnIndexIsNull:0]); FMDBQuickCheck([rs columnIndexIsNull:1]); FMDBQuickCheck([rs columnIndexIsNull:2]); FMDBQuickCheck([rs columnIndexIsNull:3]); FMDBQuickCheck([rs columnIndexIsNull:4]); FMDBQuickCheck([rs columnIndexIsNull:5]); FMDBQuickCheck(![rs dataForColumn:@"d"]); } } { [db executeUpdate:@"create table utest (a text)"]; [db executeUpdate:@"insert into utest values (?)", @"/übertest"]; rs = [db executeQuery:@"select * from utest where a = ?", @"/übertest"]; FMDBQuickCheck([rs next]); [rs close]; } { [db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"]; [db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]]; [db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]]; rs = [db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]]; FMDBQuickCheck([rs next]); FMDBQuickCheck([rs hasAnotherRow]); FMDBQuickCheck(![db hadError]); FMDBQuickCheck([[rs stringForColumnIndex:0] isEqualToString:@"one"]); FMDBQuickCheck([rs intForColumnIndex:1] == 2); FMDBQuickCheck([rs next]); FMDBQuickCheck([rs intForColumnIndex:1] == 3); FMDBQuickCheck(![rs next]); FMDBQuickCheck(![rs hasAnotherRow]); } { FMDBQuickCheck([db executeUpdate:@"create table t4 (a text, b text)"]); FMDBQuickCheck(([db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"])); rs = [db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"]; FMDBQuickCheck((rs != nil)); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"t4.a"] isEqualToString:@"one"]); FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"two"]); FMDBQuickCheck(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two") == 0); [rs close]; // let's try these again, with the withArgumentsInArray: variation FMDBQuickCheck([db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]); FMDBQuickCheck([db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]); FMDBQuickCheck(([db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]])); rs = [db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]]; FMDBQuickCheck((rs != nil)); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"t4.a"] isEqualToString:@"one"]); FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"two"]); FMDBQuickCheck(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two") == 0); [rs close]; } { FMDBQuickCheck([db tableExists:@"t4"]); FMDBQuickCheck(![db tableExists:@"thisdoesntexist"]); rs = [db getSchema]; while ([rs next]) { FMDBQuickCheck([[rs stringForColumn:@"type"] isEqualToString:@"table"]); } } { FMDBQuickCheck([db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]); FMDBQuickCheck(([db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234])); rs = [db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42]; FMDBQuickCheck((rs != nil)); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"text"]); FMDBQuickCheck(([rs intForColumn:@"b"] == 42)); FMDBQuickCheck([[rs stringForColumn:@"c"] isEqualToString:@"BLOB"]); FMDBQuickCheck([[rs stringForColumn:@"d"] isEqualToString:@"d"]); FMDBQuickCheck(([rs longLongIntForColumn:@"e"] == 12345678901234)); [rs close]; } { FMDBQuickCheck([db executeUpdate:@"create table t55 (a text, b int, c float)"]); short testShort = -4; float testFloat = 5.5; FMDBQuickCheck(([db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat])); unsigned short testUShort = 6; FMDBQuickCheck(([db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat])); rs = [db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"]; FMDBQuickCheck((rs != nil)); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"a"]); FMDBQuickCheck(([rs intForColumn:@"b"] == -4)); FMDBQuickCheck([[rs stringForColumn:@"c"] isEqualToString:@"5.5"]); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"a"]); FMDBQuickCheck(([rs intForColumn:@"b"] == 6)); FMDBQuickCheck([[rs stringForColumn:@"c"] isEqualToString:@"5.5"]); [rs close]; } { FMDBQuickCheck([db executeUpdate:@"create table tatwhat (a text)"]); BOOL worked = [db executeUpdateWithFormat:@"insert into tatwhat values(%@)", nil]; FMDBQuickCheck(worked); rs = [db executeQueryWithFormat:@"select * from tatwhat"]; FMDBQuickCheck((rs != nil)); FMDBQuickCheck(([rs next])); FMDBQuickCheck([rs columnIndexIsNull:0]); FMDBQuickCheck((![rs next])); } { FMDBQuickCheck(([db update:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]])); } // test attach for the heck of it. { //FMDatabase *dbA = [FMDatabase databaseWithPath:dbPath]; [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil]; FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"]; FMDBQuickCheck([dbB open]); FMDBQuickCheck([dbB executeUpdate:@"create table attached (a text)"]); FMDBQuickCheck(([dbB executeUpdate:@"insert into attached values (?)", @"test"])); FMDBQuickCheck([dbB close]); [db executeUpdate:@"attach database '/tmp/attachme.db' as attack"]; rs = [db executeQuery:@"select * from attack.attached"]; FMDBQuickCheck([rs next]); [rs close]; } { // ------------------------------------------------------------------------------- // Named parameters. FMDBQuickCheck([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"]; FMDBQuickCheck([db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]); rs = [db executeQuery:@"select * from namedparamtest"]; FMDBQuickCheck((rs != nil)); [rs next]; FMDBQuickCheck([[rs stringForColumn:@"a"] isEqualToString:@"Text1"]); FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"Text2"]); FMDBQuickCheck([rs intForColumn:@"c"] == 1); FMDBQuickCheck([rs doubleForColumn:@"d"] == 2.0); [rs close]; dictionaryArgs = [NSMutableDictionary dictionary]; [dictionaryArgs setObject:@"Text2" forKey:@"blah"]; rs = [db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs]; FMDBQuickCheck((rs != nil)); FMDBQuickCheck([rs next]); FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"Text2"]); [rs close]; } // just for fun. rs = [db executeQuery:@"PRAGMA database_list"]; while ([rs next]) { NSString *file = [rs stringForColumn:@"file"]; NSLog(@"database_list: %@", file); } // print out some stats if we are using cached statements. if ([db shouldCacheStatements]) { NSEnumerator *e = [[db cachedStatements] objectEnumerator];; FMStatement *statement; while ((statement = [e nextObject])) { NSLog(@"%@", statement); } } [db setShouldCacheStatements:true]; [db executeUpdate:@"CREATE TABLE testCacheStatements(key INTEGER PRIMARY KEY, value INTEGER)"]; [db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (1, 2)"]; [db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (2, 4)"]; FMDBQuickCheck([[db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]); FMDBQuickCheck([[db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]); [db close]; testPool(dbPath); testDateFormat(); FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; FMDBQuickCheck(queue); { [queue inDatabase:^(FMDatabase *adb) { [adb executeUpdate:@"create table qfoo (foo text)"]; [adb executeUpdate:@"insert into qfoo values ('hi')"]; [adb executeUpdate:@"insert into qfoo values ('hello')"]; [adb executeUpdate:@"insert into qfoo values ('not')"]; int count = 0; FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"]; while ([rsl next]) { count++; } FMDBQuickCheck(count == 2); count = 0; rsl = [adb executeQuery:@"select * from qfoo where foo like ?", @"h%"]; while ([rsl next]) { count++; } FMDBQuickCheck(count == 2); }]; } { // You should see pairs of numbers show up in stdout for this stuff: size_t ops = 16; dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(ops, dqueue, ^(size_t nby) { // just mix things up a bit for demonstration purposes. if (nby % 2 == 1) { [NSThread sleepForTimeInterval:.1]; [queue inTransaction:^(FMDatabase *adb, BOOL *rollback) { NSLog(@"Starting query %ld", nby); FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"]; while ([rsl next]) { ;// whatever. } NSLog(@"Ending query %ld", nby); }]; } if (nby % 3 == 1) { [NSThread sleepForTimeInterval:.1]; } [queue inTransaction:^(FMDatabase *adb, BOOL *rollback) { NSLog(@"Starting update %ld", nby); [adb executeUpdate:@"insert into qfoo values ('1')"]; [adb executeUpdate:@"insert into qfoo values ('2')"]; [adb executeUpdate:@"insert into qfoo values ('3')"]; NSLog(@"Ending update %ld", nby); }]; }); [queue close]; [queue inDatabase:^(FMDatabase *adb) { FMDBQuickCheck([adb executeUpdate:@"insert into qfoo values ('1')"]); }]; } { [queue inDatabase:^(FMDatabase *adb) { [adb executeUpdate:@"create table colNameTest (a, b, c, d)"]; FMDBQuickCheck([adb executeUpdate:@"insert into colNameTest values (1, 2, 3, 4)"]); FMResultSet *ars = [adb executeQuery:@"select * from colNameTest"]; NSDictionary *d = [ars columnNameToIndexMap]; FMDBQuickCheck([d count] == 4); FMDBQuickCheck([[d objectForKey:@"a"] intValue] == 0); FMDBQuickCheck([[d objectForKey:@"b"] intValue] == 1); FMDBQuickCheck([[d objectForKey:@"c"] intValue] == 2); FMDBQuickCheck([[d objectForKey:@"d"] intValue] == 3); [ars close]; }]; } { [queue inDatabase:^(FMDatabase *adb) { [adb executeUpdate:@"create table transtest (a integer)"]; FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (1)"]); FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (2)"]); int rowCount = 0; FMResultSet *ars = [adb executeQuery:@"select * from transtest"]; while ([ars next]) { rowCount++; } FMDBQuickCheck(rowCount == 2); }]; [queue inTransaction:^(FMDatabase *adb, BOOL *rollback) { FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (3)"]); if (YES) { // uh oh!, something went wrong (not really, this is just a test *rollback = YES; return; } FMDBQuickCheck([adb executeUpdate:@"insert into transtest values (4)"]); }]; [queue inDatabase:^(FMDatabase *adb) { int rowCount = 0; FMResultSet *ars = [adb executeQuery:@"select * from transtest"]; while ([ars next]) { rowCount++; } FMDBQuickCheck(![adb hasOpenResultSets]); NSLog(@"after rollback, rowCount is %d (should be 2)", rowCount); FMDBQuickCheck(rowCount == 2); }]; } // hey, let's make a custom function! [queue inDatabase:^(FMDatabase *adb) { [adb executeUpdate:@"create table ftest (foo text)"]; [adb executeUpdate:@"insert into ftest values ('hello')"]; [adb executeUpdate:@"insert into ftest values ('hi')"]; [adb executeUpdate:@"insert into ftest values ('not h!')"]; [adb executeUpdate:@"insert into ftest values ('definitely not h!')"]; [adb 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 { NSLog(@"Unknown formart for StringStartsWithH (%d) %s:%d", sqlite3_value_type(aargv[0]), __FUNCTION__, __LINE__); sqlite3_result_null(context); } }]; int rowCount = 0; FMResultSet *ars = [adb executeQuery:@"select * from ftest where StringStartsWithH(foo)"]; while ([ars next]) { rowCount++; NSLog(@"Does %@ start with 'h'?", [rs stringForColumnIndex:0]); } FMDBQuickCheck(rowCount == 2); }]; NSLog(@"That was version %@ of sqlite", [FMDatabase sqliteLibVersion]); }// this is the end of our @autorelease pool. return 0; } /* Test the various FMDatabasePool things. */ void testPool(NSString *dbPath) { FMDatabasePool *dbPool = [FMDatabasePool databasePoolWithPath:dbPath]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 0); __block FMDatabase *db1; [dbPool inDatabase:^(FMDatabase *db) { FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); FMDBQuickCheck([db tableExists:@"t4"]); db1 = db; }]; [dbPool inDatabase:^(FMDatabase *db) { FMDBQuickCheck(db1 == db); [dbPool inDatabase:^(FMDatabase *db2) { FMDBQuickCheck(db2 != db); }]; }]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 2); [dbPool inDatabase:^(FMDatabase *db) { [db executeUpdate:@"create table easy (a text)"]; [db executeUpdate:@"create table easy2 (a text)"]; }]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 2); [dbPool releaseAllDatabases]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 0); [dbPool inDatabase:^(FMDatabase *aDb) { FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1); FMDBQuickCheck([aDb tableExists:@"t4"]); FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1); FMDBQuickCheck(([aDb executeUpdate:@"insert into easy (a) values (?)", @"hi"])); // just for fun. FMResultSet *rs2 = [aDb executeQuery:@"select * from easy"]; FMDBQuickCheck([rs2 next]); while ([rs2 next]) { ; } // whatevers. FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1); }]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); { [dbPool inDatabase:^(FMDatabase *db) { [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1]]; [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:2]]; [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]; FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1); }]; } FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); [dbPool setMaximumNumberOfDatabasesToCreate:2]; [dbPool inDatabase:^(FMDatabase *db) { [dbPool inDatabase:^(FMDatabase *db2) { [dbPool inDatabase:^(FMDatabase *db3) { FMDBQuickCheck([dbPool countOfOpenDatabases] == 2); FMDBQuickCheck(!db3); }]; }]; }]; [dbPool setMaximumNumberOfDatabasesToCreate:0]; [dbPool releaseAllDatabases]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 0); [dbPool inDatabase:^(FMDatabase *db) { [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]; }]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); [dbPool inTransaction:^(FMDatabase *adb, BOOL *rollback) { [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]]; [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]]; [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 0); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 1); }]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 1); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 0); [dbPool inDatabase:^(FMDatabase *db) { FMResultSet *rs2 = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1001]]; FMDBQuickCheck([rs2 next]); FMDBQuickCheck(![rs2 next]); }]; [dbPool inDeferredTransaction:^(FMDatabase *adb, BOOL *rollback) { [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1004]]; [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1005]]; *rollback = YES; }]; FMDBQuickCheck([dbPool countOfOpenDatabases] == 1); FMDBQuickCheck([dbPool countOfCheckedInDatabases] == 1); FMDBQuickCheck([dbPool countOfCheckedOutDatabases] == 0); NSError *err = [dbPool inSavePoint:^(FMDatabase *db, BOOL *rollback) { [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1006]]; }]; FMDBQuickCheck(!err); { err = [dbPool inSavePoint:^(FMDatabase *adb, BOOL *rollback) { FMDBQuickCheck(![adb hadError]); [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1009]]; [adb inSavePoint:^(BOOL *arollback) { FMDBQuickCheck(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1010]])); *arollback = YES; }]; }]; FMDBQuickCheck(!err); [dbPool inDatabase:^(FMDatabase *db) { FMResultSet *rs2 = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1009]]; FMDBQuickCheck([rs2 next]); FMDBQuickCheck(![rs2 next]); // close it out. rs2 = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1010]]; FMDBQuickCheck(![rs2 next]); }]; } { [dbPool inDatabase:^(FMDatabase *db) { [db executeUpdate:@"create table likefoo (foo text)"]; [db executeUpdate:@"insert into likefoo values ('hi')"]; [db executeUpdate:@"insert into likefoo values ('hello')"]; [db executeUpdate:@"insert into likefoo values ('not')"]; int count = 0; FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"]; while ([rsl next]) { count++; } FMDBQuickCheck(count == 2); count = 0; rsl = [db executeQuery:@"select * from likefoo where foo like ?", @"h%"]; while ([rsl next]) { count++; } FMDBQuickCheck(count == 2); }]; } { size_t ops = 128; dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(ops, dqueue, ^(size_t nby) { // just mix things up a bit for demonstration purposes. if (nby % 2 == 1) { [NSThread sleepForTimeInterval:.1]; } [dbPool inDatabase:^(FMDatabase *db) { NSLog(@"Starting query %ld", nby); FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"]; while ([rsl next]) { if (nby % 3 == 1) { [NSThread sleepForTimeInterval:.05]; } } NSLog(@"Ending query %ld", nby); }]; }); NSLog(@"Number of open databases after crazy gcd stuff: %ld", [dbPool countOfOpenDatabases]); } // if you want to see a deadlock, just uncomment this line and run: //#define ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD 1 #ifdef ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD { int ops = 16; dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(ops, dqueue, ^(size_t nby) { // just mix things up a bit for demonstration purposes. if (nby % 2 == 1) { [NSThread sleepForTimeInterval:.1]; [dbPool inTransaction:^(FMDatabase *db, BOOL *rollback) { NSLog(@"Starting query %ld", nby); FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"]; while ([rsl next]) { ;// whatever. } NSLog(@"Ending query %ld", nby); }]; } if (nby % 3 == 1) { [NSThread sleepForTimeInterval:.1]; } [dbPool inTransaction:^(FMDatabase *db, BOOL *rollback) { NSLog(@"Starting update %ld", nby); [db executeUpdate:@"insert into likefoo values ('1')"]; [db executeUpdate:@"insert into likefoo values ('2')"]; [db executeUpdate:@"insert into likefoo values ('3')"]; NSLog(@"Ending update %ld", nby); }]; }); [dbPool releaseAllDatabases]; [dbPool inDatabase:^(FMDatabase *db) { FMDBQuickCheck([db executeUpdate:@"insert into likefoo values ('1')"]); }]; } #endif } /* Test the date format */ void testOneDateFormat( 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"]; if ([rs next]) { NSDate *found = [rs dateForColumnIndex:0]; if (NSOrderedSame != [testDate compare:found]) { NSLog(@"Did not get back what we stored."); } } else { NSLog(@"Insertion borked"); } [rs close]; } void testDateFormat() { FMDatabase *db = [FMDatabase databaseWithPath:nil]; // use in-memory DB [db open]; 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(db,testDate); // now test the string-based timestamp [db setDateFormat:fmt]; testOneDateFormat(db, testDate); [db close]; } /* What is this function for? Think of it as a template which a developer can use to report bugs. If you have a bug, make it reproduce in this function and then let the developer(s) know either via the github bug reporter or the mailing list. */ void FMDBReportABugFunction() { NSString *dbPath = @"/tmp/bugreportsample.db"; // delete the old db if it exists NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager removeItemAtPath:dbPath error:nil]; FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; [queue inDatabase:^(FMDatabase *db) { /* Change the contents of this block to suit your needs. */ BOOL worked = [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"]; FMDBQuickCheck(worked); worked = [db executeUpdate:@"insert into test values ('a', 'b', 1, 2.2, 2.3)"]; FMDBQuickCheck(worked); FMResultSet *rs = [db executeQuery:@"select * from test"]; FMDBQuickCheck([rs next]); [rs close]; }]; [queue close]; // uncomment the following line if you don't want to run through all the other tests. //exit(0); }