Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ccgus/fmdb.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorccgus <gus@flyingmeat.com>2013-12-10 22:16:53 +0400
committerccgus <gus@flyingmeat.com>2013-12-10 22:16:53 +0400
commit8d2dac4001b73f71ce7290757a6bb7c967bd9053 (patch)
treedf98d4c83c55c370668b03a25bd93c0704e2641f
parent2224f216e79096f92a8ce33b432bd8d75153afe2 (diff)
parentdd7da6f0a471c0777f94a2220eb97b925c06e24a (diff)
Merge branch 'master' of github.com:ccgus/fmdb into busyTimeout
-rw-r--r--.travis.yml5
-rw-r--r--README.markdown2
-rw-r--r--Tests/FMDBTempDBTests.h24
-rw-r--r--Tests/FMDBTempDBTests.m63
-rw-r--r--Tests/FMDatabaseAdditionsTests.m97
-rw-r--r--Tests/FMDatabasePoolTests.m293
-rw-r--r--Tests/FMDatabaseQueueTests.m176
-rw-r--r--Tests/FMDatabaseTests.m817
-rw-r--r--Tests/Schemes/Tests.xcscheme83
-rw-r--r--Tests/Tests-Info.plist22
-rw-r--r--Tests/Tests-Prefix.pch11
-rw-r--r--Tests/en.lproj/InfoPlist.strings2
-rw-r--r--fmdb.xcodeproj/project.pbxproj227
-rw-r--r--src/FMDatabase.h27
-rw-r--r--src/FMDatabasePool.h22
-rw-r--r--src/FMDatabasePool.m23
-rw-r--r--src/FMDatabaseQueue.h11
-rw-r--r--src/FMDatabaseQueue.m7
-rw-r--r--src/fmdb.m14
19 files changed, 1901 insertions, 25 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..7e21267
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: objective-c
+xcode_project: fmdb.xcodeproj
+xcode_scheme: Tests
+before_install:
+ - mkdir -p "fmdb.xcodeproj/xcshareddata/xcschemes" && cp Tests/Schemes/*.xcscheme "fmdb.xcodeproj/xcshareddata/xcschemes/"
diff --git a/README.markdown b/README.markdown
index 99a40f1..6d5b85d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,6 +1,8 @@
# FMDB
This is an Objective-C wrapper around SQLite: http://sqlite.org/
+[![Build Status](https://travis-ci.org/ccgus/fmdb.png?branch=master)](https://travis-ci.org/ccgus/fmdb)
+
## The FMDB Mailing List:
http://groups.google.com/group/fmdb
diff --git a/Tests/FMDBTempDBTests.h b/Tests/FMDBTempDBTests.h
new file mode 100644
index 0000000..c84ab8f
--- /dev/null
+++ b/Tests/FMDBTempDBTests.h
@@ -0,0 +1,24 @@
+//
+// FMDBTempDBTests.h
+// fmdb
+//
+// Created by Graham Dennis on 24/11/2013.
+//
+//
+
+#import <XCTest/XCTest.h>
+#import "FMDatabase.h"
+
+@protocol FMDBTempDBTests <NSObject>
+
+@optional
++ (void)populateDatabase:(FMDatabase *)database;
+
+@end
+
+@interface FMDBTempDBTests : XCTestCase <FMDBTempDBTests>
+
+@property FMDatabase *db;
+@property (readonly) NSString *databasePath;
+
+@end
diff --git a/Tests/FMDBTempDBTests.m b/Tests/FMDBTempDBTests.m
new file mode 100644
index 0000000..bb759df
--- /dev/null
+++ b/Tests/FMDBTempDBTests.m
@@ -0,0 +1,63 @@
+//
+// FMDBTempDBTests.m
+// fmdb
+//
+// Created by Graham Dennis on 24/11/2013.
+//
+//
+
+#import "FMDBTempDBTests.h"
+
+static NSString *const testDatabasePath = @"/tmp/tmp.db";
+static NSString *const populatedDatabasePath = @"/tmp/tmp-populated.db";
+
+@implementation FMDBTempDBTests
+
++ (void)setUp
+{
+ [super setUp];
+
+ // Delete old populated database
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ [fileManager removeItemAtPath:populatedDatabasePath error:NULL];
+
+ if ([self respondsToSelector:@selector(populateDatabase:)]) {
+ FMDatabase *db = [FMDatabase databaseWithPath:populatedDatabasePath];
+
+ [db open];
+ [self populateDatabase:db];
+ [db close];
+ }
+}
+
+- (void)setUp
+{
+ [super setUp];
+
+ // Delete the old database
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ [fileManager removeItemAtPath:testDatabasePath error:NULL];
+
+ if ([[self class] respondsToSelector:@selector(populateDatabase:)]) {
+ [fileManager copyItemAtPath:populatedDatabasePath toPath:testDatabasePath error:NULL];
+ }
+
+ self.db = [FMDatabase databaseWithPath:testDatabasePath];
+
+ XCTAssertTrue([self.db open], @"Wasn't able to open database");
+ [self.db setShouldCacheStatements:YES];
+}
+
+- (void)tearDown
+{
+ [super tearDown];
+
+ [self.db close];
+}
+
+- (NSString *)databasePath
+{
+ return testDatabasePath;
+}
+
+@end
diff --git a/Tests/FMDatabaseAdditionsTests.m b/Tests/FMDatabaseAdditionsTests.m
new file mode 100644
index 0000000..6164847
--- /dev/null
+++ b/Tests/FMDatabaseAdditionsTests.m
@@ -0,0 +1,97 @@
+//
+// FMDatabaseAdditionsTests.m
+// fmdb
+//
+// Created by Graham Dennis on 24/11/2013.
+//
+//
+
+#import <XCTest/XCTest.h>
+#import "FMDatabaseAdditions.h"
+
+@interface FMDatabaseAdditionsTests : FMDBTempDBTests
+
+@end
+
+@implementation FMDatabaseAdditionsTests
+
+- (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)testFunkyTableNames
+{
+ [self.db executeUpdate:@"create table '234 fds' (foo text)"];
+ XCTAssertFalse([self.db hadError], @"table creation should have succeeded");
+ FMResultSet *rs = [self.db getTableSchema:@"234 fds"];
+ XCTAssertTrue([rs next], @"Schema should have succeded");
+ [rs close];
+ XCTAssertFalse([self.db hadError], @"There shouldn't be any errors");
+}
+
+- (void)testBoolForQuery
+{
+ BOOL result = [self.db boolForQuery:@"SELECT ? not null", @""];
+ XCTAssertTrue(result, @"Empty strings should be considered true");
+
+ result = [self.db boolForQuery:@"SELECT ? not null", [NSMutableData data]];
+ XCTAssertTrue(result, @"Empty mutable data should be considered true");
+
+ result = [self.db boolForQuery:@"SELECT ? not null", [NSData data]];
+ XCTAssertTrue(result, @"Empty data should be considered true");
+}
+
+
+- (void)testIntForQuery
+{
+ [self.db executeUpdate:@"create table t1 (a integer)"];
+ [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
+
+ XCTAssertEqual([self.db changes], 1, @"There should only be one change");
+
+ int ia = [self.db intForQuery:@"select a from t1 where a = ?", [NSNumber numberWithInt:5]];
+ XCTAssertEqual(ia, 5, @"foo");
+}
+
+- (void)testDateForQuery
+{
+ 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];
+
+ NSDate *foo = [self.db dateForQuery:@"select b from datetest where c = 0"];
+ XCTAssertEqualWithAccuracy([foo timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second");
+}
+
+- (void)testTableExists
+{
+ XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]);
+
+ XCTAssertTrue([self.db tableExists:@"t4"]);
+ XCTAssertFalse([self.db tableExists:@"thisdoesntexist"]);
+
+ FMResultSet *rs = [self.db getSchema];
+ while ([rs next]) {
+ XCTAssertEqualObjects([rs stringForColumn:@"type"], @"table");
+ }
+
+}
+
+- (void)testColumnExists
+{
+ [self.db executeUpdate:@"create table nulltest (a text, b text)"];
+
+ XCTAssertTrue([self.db columnExists:@"a" inTableWithName:@"nulltest"]);
+ XCTAssertTrue([self.db columnExists:@"b" inTableWithName:@"nulltest"]);
+ XCTAssertFalse([self.db columnExists:@"c" inTableWithName:@"nulltest"]);
+}
+
+@end
diff --git a/Tests/FMDatabasePoolTests.m b/Tests/FMDatabasePoolTests.m
new file mode 100644
index 0000000..36183c5
--- /dev/null
+++ b/Tests/FMDatabasePoolTests.m
@@ -0,0 +1,293 @@
+//
+// FMDatabasePoolTests.m
+// fmdb
+//
+// Created by Graham Dennis on 24/11/2013.
+//
+//
+
+#import <XCTest/XCTest.h>
+
+@interface FMDatabasePoolTests : FMDBTempDBTests
+
+@property FMDatabasePool *pool;
+
+@end
+
+@implementation FMDatabasePoolTests
+
++ (void)populateDatabase:(FMDatabase *)db
+{
+ [db executeUpdate:@"create table easy (a text)"];
+ [db executeUpdate:@"create table easy2 (a text)"];
+
+ [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]];
+ [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]];
+ [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]];
+
+ [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')"];
+}
+
+- (void)setUp
+{
+ [super setUp];
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ self.pool = [FMDatabasePool databasePoolWithPath:self.databasePath];
+}
+
+- (void)tearDown
+{
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ [super tearDown];
+}
+
+- (void)testPoolIsInitiallyEmpty
+{
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"Pool should be empty on creation");
+}
+
+- (void)testDatabaseCreation
+{
+ __block FMDatabase *db1;
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1, @"Should only have one database at this point");
+
+ db1 = db;
+
+ }];
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+ XCTAssertEqualObjects(db, db1, @"We should get the same database back because there was no need to create a new one");
+
+ [self.pool inDatabase:^(FMDatabase *db2) {
+ XCTAssertNotEqualObjects(db2, db, @"We should get a different database because the first was in use.");
+ }];
+
+ }];
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2);
+
+ [self.pool releaseAllDatabases];
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"We should be back to zero databases again");
+}
+
+- (void)testCheckedInCheckoutOutCount
+{
+ [self.pool inDatabase:^(FMDatabase *aDb) {
+
+ XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0);
+ XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1);
+
+ XCTAssertTrue(([aDb executeUpdate:@"insert into easy (a) values (?)", @"hi"]));
+
+ // just for fun.
+ FMResultSet *rs = [aDb executeQuery:@"select * from easy"];
+ XCTAssertNotNil(rs);
+ XCTAssertTrue([rs next]);
+ while ([rs next]) { ; } // whatevers.
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
+ XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0);
+ XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1);
+ }];
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
+}
+
+- (void)testMaximumDatabaseLimit
+{
+ [self.pool setMaximumNumberOfDatabasesToCreate:2];
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+ [self.pool inDatabase:^(FMDatabase *db2) {
+ [self.pool inDatabase:^(FMDatabase *db3) {
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2);
+ XCTAssertNil(db3, @"The third database must be nil because we have a maximum of 2 databases in the pool");
+ }];
+
+ }];
+ }];
+}
+
+- (void)testTransaction
+{
+ [self.pool 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]];
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
+ XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0);
+ XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1);
+ }];
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
+ XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)1);
+ XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)0);
+}
+
+- (void)testSelect
+{
+ [self.pool inDatabase:^(FMDatabase *db) {
+ FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1001]];
+ XCTAssertNotNil(rs);
+ XCTAssertTrue ([rs next]);
+ XCTAssertFalse([rs next]);
+ }];
+}
+
+- (void)testTransactionRollback
+{
+ [self.pool inDeferredTransaction:^(FMDatabase *adb, BOOL *rollback) {
+ XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1004]]));
+ XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1005]]));
+ XCTAssertTrue([[adb executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should be in database");
+
+ *rollback = YES;
+ }];
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+ XCTAssertFalse([[db executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should not be in database");
+ }];
+
+ XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
+ XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)1);
+ XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)0);
+}
+
+- (void)testSavepoint
+{
+ NSError *err = [self.pool inSavePoint:^(FMDatabase *db, BOOL *rollback) {
+ [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1006]];
+ }];
+
+ XCTAssertNil(err);
+}
+
+- (void)testNestedSavepointRollback
+{
+ NSError *err = [self.pool inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
+ XCTAssertFalse([adb hadError]);
+ XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1009]]));
+
+ [adb inSavePoint:^(BOOL *arollback) {
+ XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1010]]));
+ *arollback = YES;
+ }];
+ }];
+
+
+ XCTAssertNil(err);
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+ FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1009]];
+ XCTAssertTrue ([rs next]);
+ XCTAssertFalse([rs next]); // close it out.
+
+ rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1010]];
+ XCTAssertFalse([rs next]);
+ }];
+}
+
+- (void)testLikeStringQuery
+{
+ [self.pool inDatabase:^(FMDatabase *db) {
+ int count = 0;
+ FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
+ while ([rsl next]) {
+ count++;
+ }
+
+ XCTAssertEqual(count, 2);
+
+ count = 0;
+ rsl = [db executeQuery:@"select * from likefoo where foo like ?", @"h%"];
+ while ([rsl next]) {
+ count++;
+ }
+
+ XCTAssertEqual(count, 2);
+
+ }];
+}
+
+- (void)testStressTest
+{
+ 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:.001];
+ }
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+ FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
+ XCTAssertNotNil(rsl);
+ int i = 0;
+ while ([rsl next]) {
+ i++;
+ if (nby % 3 == 1) {
+ [NSThread sleepForTimeInterval:.0005];
+ }
+ }
+ XCTAssertEqual(i, 2);
+ }];
+ });
+
+ XCTAssert([self.pool countOfOpenDatabases] < 64, @"There should be significantly less than 64 databases after that stress test");
+}
+
+- (void)testReadWriteStressTest
+{
+ 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:.01];
+
+ [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) {
+ FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
+ XCTAssertNotNil(rsl);
+ while ([rsl next]) {
+ ;// whatever.
+ }
+
+ }];
+
+ }
+
+ if (nby % 3 == 1) {
+ [NSThread sleepForTimeInterval:.01];
+ }
+
+ [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) {
+ XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]);
+ XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('2')"]);
+ XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('3')"]);
+ }];
+ });
+
+ [self.pool releaseAllDatabases];
+
+ [self.pool inDatabase:^(FMDatabase *db) {
+ XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]);
+ }];
+}
+
+@end
diff --git a/Tests/FMDatabaseQueueTests.m b/Tests/FMDatabaseQueueTests.m
new file mode 100644
index 0000000..cd7fb92
--- /dev/null
+++ b/Tests/FMDatabaseQueueTests.m
@@ -0,0 +1,176 @@
+//
+// FMDatabaseQueueTests.m
+// fmdb
+//
+// Created by Graham Dennis on 24/11/2013.
+//
+//
+
+#import <XCTest/XCTest.h>
+#import "FMDatabaseQueue.h"
+
+@interface FMDatabaseQueueTests : FMDBTempDBTests
+
+@property FMDatabaseQueue *queue;
+
+@end
+
+@implementation FMDatabaseQueueTests
+
++ (void)populateDatabase:(FMDatabase *)db
+{
+ [db executeUpdate:@"create table easy (a text)"];
+
+ [db executeUpdate:@"create table qfoo (foo text)"];
+ [db executeUpdate:@"insert into qfoo values ('hi')"];
+ [db executeUpdate:@"insert into qfoo values ('hello')"];
+ [db executeUpdate:@"insert into qfoo values ('not')"];
+}
+
+- (void)setUp
+{
+ [super setUp];
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ self.queue = [FMDatabaseQueue databaseQueueWithPath:self.databasePath];
+}
+
+- (void)tearDown
+{
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ [super tearDown];
+}
+
+- (void)testQueueSelect
+{
+ [self.queue inDatabase:^(FMDatabase *adb) {
+ int count = 0;
+ FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
+ while ([rsl next]) {
+ count++;
+ }
+
+ XCTAssertEqual(count, 2);
+
+ count = 0;
+ rsl = [adb executeQuery:@"select * from qfoo where foo like ?", @"h%"];
+ while ([rsl next]) {
+ count++;
+ }
+
+ XCTAssertEqual(count, 2);
+ }];
+}
+
+- (void)testReadOnlyQueue
+{
+ FMDatabaseQueue *queue2 = [FMDatabaseQueue databaseQueueWithPath:self.databasePath flags:SQLITE_OPEN_READONLY];
+ XCTAssertNotNil(queue2);
+
+ {
+ [queue2 inDatabase:^(FMDatabase *db2) {
+ FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"];
+ XCTAssertNotNil(rs1);
+
+ [rs1 close];
+
+ XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database");
+ }];
+
+ [queue2 close];
+
+ // Check that when we re-open the database, it's still read-only
+ [queue2 inDatabase:^(FMDatabase *db2) {
+ FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"];
+ XCTAssertNotNil(rs1);
+
+ [rs1 close];
+
+ XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database");
+ }];
+ }
+}
+
+- (void)testStressTest
+{
+ 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:.01];
+
+ [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
+ FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
+ while ([rsl next]) {
+ ;// whatever.
+ }
+ }];
+
+ }
+
+ if (nby % 3 == 1) {
+ [NSThread sleepForTimeInterval:.01];
+ }
+
+ [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
+ XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]);
+ XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('2')"]);
+ XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('3')"]);
+ }];
+ });
+
+ [self.queue close];
+
+ [self.queue inDatabase:^(FMDatabase *adb) {
+ XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]);
+ }];
+}
+
+- (void)testTransaction
+{
+ [self.queue inDatabase:^(FMDatabase *adb) {
+ [adb executeUpdate:@"create table transtest (a integer)"];
+ XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]);
+ XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]);
+
+ int rowCount = 0;
+ FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
+ while ([ars next]) {
+ rowCount++;
+ }
+
+ XCTAssertEqual(rowCount, 2);
+ }];
+
+ [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
+ XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]);
+
+ if (YES) {
+ // uh oh!, something went wrong (not really, this is just a test
+ *rollback = YES;
+ return;
+ }
+
+ XCTFail(@"This shouldn't be reached");
+ }];
+
+ [self.queue inDatabase:^(FMDatabase *adb) {
+
+ int rowCount = 0;
+ FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
+ while ([ars next]) {
+ rowCount++;
+ }
+
+ XCTAssertFalse([adb hasOpenResultSets]);
+
+ XCTAssertEqual(rowCount, 2);
+ }];
+
+}
+
+@end
diff --git a/Tests/FMDatabaseTests.m b/Tests/FMDatabaseTests.m
new file mode 100644
index 0000000..f3ef197
--- /dev/null
+++ b/Tests/FMDatabaseTests.m
@@ -0,0 +1,817 @@
+//
+// Tests.m
+// Tests
+//
+// Created by Graham Dennis on 24/11/2013.
+//
+//
+
+#import "FMDBTempDBTests.h"
+#import "FMDatabase.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)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 update:@"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 setBusyRetryTimeout:50];
+
+ 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)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 update:@"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);
+
+}
+
+
+#if SQLITE_VERSION_NUMBER >= 3007017
+- (void)testApplicationID
+{
+ uint32_t appID = NSHFSTypeCodeFromFileType(NSFileTypeForHFSTypeCode('fmdb'));
+
+ [db setApplicationID:appID];
+
+ uint32_t rAppID = [db applicationID];
+
+ XCTAssertEqual(rAppID, appID);
+
+ [db setApplicationIDString:@"acrn"];
+
+ NSString *s = [db applicationIDString];
+
+ XCTAssertEqualObjects(s, @"acrn");
+}
+#endif
+
+
+@end
diff --git a/Tests/Schemes/Tests.xcscheme b/Tests/Schemes/Tests.xcscheme
new file mode 100644
index 0000000..3ccd45a
--- /dev/null
+++ b/Tests/Schemes/Tests.xcscheme
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0500"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "EE4290EE12B42F870088BD94"
+ BuildableName = "libFMDB.a"
+ BlueprintName = "FMDB"
+ ReferencedContainer = "container:fmdb.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "NO"
+ buildForArchiving = "NO"
+ buildForAnalyzing = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "BF5D041518416BB2008C5AA9"
+ BuildableName = "Tests.xctest"
+ BlueprintName = "Tests"
+ ReferencedContainer = "container:fmdb.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ buildConfiguration = "Debug">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "BF5D041518416BB2008C5AA9"
+ BuildableName = "Tests.xctest"
+ BlueprintName = "Tests"
+ ReferencedContainer = "container:fmdb.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ </TestAction>
+ <LaunchAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Debug"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Release"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Tests/Tests-Info.plist b/Tests/Tests-Info.plist
new file mode 100644
index 0000000..30e7ee2
--- /dev/null
+++ b/Tests/Tests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>me.grahamdennis.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/Tests/Tests-Prefix.pch b/Tests/Tests-Prefix.pch
new file mode 100644
index 0000000..930e2c9
--- /dev/null
+++ b/Tests/Tests-Prefix.pch
@@ -0,0 +1,11 @@
+//
+// Prefix header
+//
+// The contents of this file are implicitly included at the beginning of every source file.
+//
+
+#ifdef __OBJC__
+ #import <Cocoa/Cocoa.h>
+ #import <XCTest/XCTest.h>
+ #import "FMDBTempDBTests.h"
+#endif
diff --git a/Tests/en.lproj/InfoPlist.strings b/Tests/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000..477b28f
--- /dev/null
+++ b/Tests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/fmdb.xcodeproj/project.pbxproj b/fmdb.xcodeproj/project.pbxproj
index 150d841..813465c 100644
--- a/fmdb.xcodeproj/project.pbxproj
+++ b/fmdb.xcodeproj/project.pbxproj
@@ -9,6 +9,16 @@
/* Begin PBXBuildFile section */
8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
8DD76F9F0486AA7600D96B5E /* fmdb.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* fmdb.1 */; };
+ BF5D041918416BB2008C5AA9 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5D041818416BB2008C5AA9 /* XCTest.framework */; };
+ BF5D041F18416BB2008C5AA9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF5D041D18416BB2008C5AA9 /* InfoPlist.strings */; };
+ BF5D042118416BB2008C5AA9 /* FMDatabaseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5D042018416BB2008C5AA9 /* FMDatabaseTests.m */; };
+ BF5D04281841702E008C5AA9 /* libFMDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EE4290EF12B42F870088BD94 /* libFMDB.a */; };
+ BF5D042918417159008C5AA9 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EE42910C12B42FFA0088BD94 /* libsqlite3.dylib */; };
+ BF940F5C18417D490001E077 /* FMDBTempDBTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BF940F5B18417D490001E077 /* FMDBTempDBTests.m */; };
+ BF940F5E18417DEA0001E077 /* FMDatabaseAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BF940F5D18417DEA0001E077 /* FMDatabaseAdditionsTests.m */; };
+ BFC152B118417F0D00605DF7 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
+ BFE55E131841C9A000CB3A63 /* FMDatabasePoolTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BFE55E121841C9A000CB3A63 /* FMDatabasePoolTests.m */; };
+ BFE55E151841D38800CB3A63 /* FMDatabaseQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BFE55E141841D38800CB3A63 /* FMDatabaseQueueTests.m */; };
CC47A00F148581E9002CCDAB /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; };
CC47A010148581E9002CCDAB /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
CC47A011148581E9002CCDAB /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
@@ -32,6 +42,16 @@
EE42910D12B42FFA0088BD94 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EE42910C12B42FFA0088BD94 /* libsqlite3.dylib */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ BF5D042318416BB2008C5AA9 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = EE4290EE12B42F870088BD94;
+ remoteInfo = FMDB;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXCopyFilesBuildPhase section */
8DD76F9E0486AA7600D96B5E /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
@@ -52,6 +72,17 @@
32A70AAB03705E1F00C91783 /* fmdb_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fmdb_Prefix.pch; sourceTree = "<group>"; };
831DE6FD175B7C9C001F7317 /* README.markdown */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.markdown; sourceTree = "<group>"; };
8DD76FA10486AA7600D96B5E /* fmdb */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fmdb; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF5D041618416BB2008C5AA9 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF5D041818416BB2008C5AA9 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+ BF5D041C18416BB2008C5AA9 /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
+ BF5D041E18416BB2008C5AA9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+ BF5D042018416BB2008C5AA9 /* FMDatabaseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseTests.m; sourceTree = "<group>"; };
+ BF5D042218416BB2008C5AA9 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
+ BF940F5A18417D490001E077 /* FMDBTempDBTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDBTempDBTests.h; sourceTree = "<group>"; };
+ BF940F5B18417D490001E077 /* FMDBTempDBTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDBTempDBTests.m; sourceTree = "<group>"; };
+ BF940F5D18417DEA0001E077 /* FMDatabaseAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseAdditionsTests.m; sourceTree = "<group>"; };
+ BFE55E121841C9A000CB3A63 /* FMDatabasePoolTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabasePoolTests.m; sourceTree = "<group>"; };
+ BFE55E141841D38800CB3A63 /* FMDatabaseQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseQueueTests.m; sourceTree = "<group>"; };
C6859EA3029092ED04C91782 /* fmdb.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = fmdb.1; sourceTree = "<group>"; };
CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMDatabaseQueue.h; path = src/FMDatabaseQueue.h; sourceTree = "<group>"; };
CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMDatabaseQueue.m; path = src/FMDatabaseQueue.m; sourceTree = "<group>"; };
@@ -84,6 +115,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ BF5D041318416BB2008C5AA9 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BF5D042918417159008C5AA9 /* libsqlite3.dylib in Frameworks */,
+ BF5D04281841702E008C5AA9 /* libFMDB.a in Frameworks */,
+ BF5D041918416BB2008C5AA9 /* XCTest.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
EE4290ED12B42F870088BD94 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -105,6 +146,8 @@
08FB7795FE84155DC02AAC07 /* Source */,
C6859EA2029092E104C91782 /* Documentation */,
08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */,
+ BF5D041A18416BB2008C5AA9 /* Tests */,
+ BF5D041718416BB2008C5AA9 /* Frameworks */,
1AB674ADFE9D54B511CA2CBB /* Products */,
EE42910C12B42FFA0088BD94 /* libsqlite3.dylib */,
);
@@ -145,10 +188,43 @@
children = (
8DD76FA10486AA7600D96B5E /* fmdb */,
EE4290EF12B42F870088BD94 /* libFMDB.a */,
+ BF5D041618416BB2008C5AA9 /* Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
+ BF5D041718416BB2008C5AA9 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ BF5D041818416BB2008C5AA9 /* XCTest.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ BF5D041A18416BB2008C5AA9 /* Tests */ = {
+ isa = PBXGroup;
+ children = (
+ BF940F5A18417D490001E077 /* FMDBTempDBTests.h */,
+ BF940F5B18417D490001E077 /* FMDBTempDBTests.m */,
+ BF5D042018416BB2008C5AA9 /* FMDatabaseTests.m */,
+ BFE55E121841C9A000CB3A63 /* FMDatabasePoolTests.m */,
+ BFE55E141841D38800CB3A63 /* FMDatabaseQueueTests.m */,
+ BF940F5D18417DEA0001E077 /* FMDatabaseAdditionsTests.m */,
+ BF5D041B18416BB2008C5AA9 /* Supporting Files */,
+ );
+ path = Tests;
+ sourceTree = "<group>";
+ };
+ BF5D041B18416BB2008C5AA9 /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ BF5D041C18416BB2008C5AA9 /* Tests-Info.plist */,
+ BF5D041D18416BB2008C5AA9 /* InfoPlist.strings */,
+ BF5D042218416BB2008C5AA9 /* Tests-Prefix.pch */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
C6859EA2029092E104C91782 /* Documentation */ = {
isa = PBXGroup;
children = (
@@ -193,6 +269,24 @@
productReference = 8DD76FA10486AA7600D96B5E /* fmdb */;
productType = "com.apple.product-type.tool";
};
+ BF5D041518416BB2008C5AA9 /* Tests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = BF5D042718416BB2008C5AA9 /* Build configuration list for PBXNativeTarget "Tests" */;
+ buildPhases = (
+ BF5D041218416BB2008C5AA9 /* Sources */,
+ BF5D041318416BB2008C5AA9 /* Frameworks */,
+ BF5D041418416BB2008C5AA9 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ BF5D042418416BB2008C5AA9 /* PBXTargetDependency */,
+ );
+ name = Tests;
+ productName = Tests;
+ productReference = BF5D041618416BB2008C5AA9 /* Tests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
EE4290EE12B42F870088BD94 /* FMDB */ = {
isa = PBXNativeTarget;
buildConfigurationList = EE42910012B42FA00088BD94 /* Build configuration list for PBXNativeTarget "FMDB" */;
@@ -217,6 +311,11 @@
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0460;
+ TargetAttributes = {
+ BF5D041518416BB2008C5AA9 = {
+ TestTargetID = EE4290EE12B42F870088BD94;
+ };
+ };
};
buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "fmdb" */;
compatibilityVersion = "Xcode 3.2";
@@ -227,6 +326,7 @@
Japanese,
French,
German,
+ en,
);
mainGroup = 08FB7794FE84155DC02AAC07 /* fmdb */;
projectDirPath = "";
@@ -234,10 +334,22 @@
targets = (
8DD76F960486AA7600D96B5E /* fmdb */,
EE4290EE12B42F870088BD94 /* FMDB */,
+ BF5D041518416BB2008C5AA9 /* Tests */,
);
};
/* End PBXProject section */
+/* Begin PBXResourcesBuildPhase section */
+ BF5D041418416BB2008C5AA9 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BF5D041F18416BB2008C5AA9 /* InfoPlist.strings in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
/* Begin PBXSourcesBuildPhase section */
8DD76F990486AA7600D96B5E /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -252,6 +364,19 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ BF5D041218416BB2008C5AA9 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BFC152B118417F0D00605DF7 /* FMDatabaseAdditions.m in Sources */,
+ BF940F5C18417D490001E077 /* FMDBTempDBTests.m in Sources */,
+ BF940F5E18417DEA0001E077 /* FMDatabaseAdditionsTests.m in Sources */,
+ BF5D042118416BB2008C5AA9 /* FMDatabaseTests.m in Sources */,
+ BFE55E131841C9A000CB3A63 /* FMDatabasePoolTests.m in Sources */,
+ BFE55E151841D38800CB3A63 /* FMDatabaseQueueTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
EE4290EC12B42F870088BD94 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -266,6 +391,25 @@
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ BF5D042418416BB2008C5AA9 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = EE4290EE12B42F870088BD94 /* FMDB */;
+ targetProxy = BF5D042318416BB2008C5AA9 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ BF5D041D18416BB2008C5AA9 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ BF5D041E18416BB2008C5AA9 /* en */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
/* Begin XCBuildConfiguration section */
1DEB927508733DD40010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
@@ -341,6 +485,80 @@
};
name = Release;
};
+ BF5D042518416BB2008C5AA9 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ COPY_PHASE_STRIP = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ "$(inherited)",
+ );
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_PEDANTIC = NO;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ MACOSX_DEPLOYMENT_TARGET = 10.8;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Debug;
+ };
+ BF5D042618416BB2008C5AA9 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ "$(inherited)",
+ );
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ MACOSX_DEPLOYMENT_TARGET = 10.8;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Release;
+ };
EE4290F012B42F880088BD94 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -386,6 +604,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ BF5D042718416BB2008C5AA9 /* Build configuration list for PBXNativeTarget "Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BF5D042518416BB2008C5AA9 /* Debug */,
+ BF5D042618416BB2008C5AA9 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
EE42910012B42FA00088BD94 /* Build configuration list for PBXNativeTarget "FMDB" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/src/FMDatabase.h b/src/FMDatabase.h
index 8169173..c538f58 100644
--- a/src/FMDatabase.h
+++ b/src/FMDatabase.h
@@ -24,25 +24,14 @@
#define FMDBRelease(__v)
- #if TARGET_OS_IPHONE
- // Compiling for iOS
- #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
- // iOS 6.0 or later
- #define FMDBDispatchQueueRelease(__v)
- #else
- // iOS 5.X or earlier
- #define FMDBDispatchQueueRelease(__v) (dispatch_release(__v));
- #endif
- #else
- // Compiling for Mac OS X
- #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
- // Mac OS X 10.8 or later
- #define FMDBDispatchQueueRelease(__v)
- #else
- // Mac OS X 10.7 or earlier
- #define FMDBDispatchQueueRelease(__v) (dispatch_release(__v));
- #endif
- #endif
+// If OS_OBJECT_USE_OBJC=1, then the dispatch objects will be treated like ObjC objects
+// and will participate in ARC.
+// See the section on "Dispatch Queues and Automatic Reference Counting" in "Grand Central Dispatch (GCD) Reference" for details.
+ #if OS_OBJECT_USE_OBJC
+ #define FMDBDispatchQueueRelease(__v)
+ #else
+ #define FMDBDispatchQueueRelease(__v) (dispatch_release(__v));
+ #endif
#endif
#if !__has_feature(objc_instancetype)
diff --git a/src/FMDatabasePool.h b/src/FMDatabasePool.h
index 5af0305..3e1d53b 100644
--- a/src/FMDatabasePool.h
+++ b/src/FMDatabasePool.h
@@ -40,11 +40,13 @@
__unsafe_unretained id _delegate;
NSUInteger _maximumNumberOfDatabasesToCreate;
+ int _openFlags;
}
@property (atomic, retain) NSString *path;
@property (atomic, assign) id delegate;
@property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate;
+@property (atomic, readonly) int openFlags;
///---------------------
/// @name Initialization
@@ -59,6 +61,16 @@
+ (instancetype)databasePoolWithPath:(NSString*)aPath;
+/** Create pool using path and specified flags
+
+ @param aPath The file path of the database.
+ @param openFlags Flags passed to the openWithFlags method of the database
+
+ @return The `FMDatabasePool` object. `nil` on error.
+ */
+
++ (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags;
+
/** Create pool using path.
@param aPath The file path of the database.
@@ -68,6 +80,16 @@
- (instancetype)initWithPath:(NSString*)aPath;
+/** Create pool using path and specified flags.
+
+ @param aPath The file path of the database.
+ @param openFlags Flags passed to the openWithFlags method of the database
+
+ @return The `FMDatabasePool` object. `nil` on error.
+ */
+
+- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags;
+
///------------------------------------------------
/// @name Keeping track of checked in/out databases
///------------------------------------------------
diff --git a/src/FMDatabasePool.m b/src/FMDatabasePool.m
index ee0611b..ccf4628 100644
--- a/src/FMDatabasePool.m
+++ b/src/FMDatabasePool.m
@@ -21,17 +21,18 @@
@synthesize path=_path;
@synthesize delegate=_delegate;
@synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate;
+@synthesize openFlags=_openFlags;
+ (instancetype)databasePoolWithPath:(NSString*)aPath {
return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
}
-- (instancetype)init {
- return [self initWithPath:nil];
++ (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags {
+ return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath flags:openFlags]);
}
-- (instancetype)initWithPath:(NSString*)aPath {
+- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags {
self = [super init];
@@ -40,11 +41,23 @@
_lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
_databaseInPool = FMDBReturnRetained([NSMutableArray array]);
_databaseOutPool = FMDBReturnRetained([NSMutableArray array]);
+ _openFlags = openFlags;
}
return self;
}
+- (instancetype)initWithPath:(NSString*)aPath
+{
+ // default flags for sqlite3_open
+ return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE];
+}
+
+- (instancetype)init {
+ return [self initWithPath:nil];
+}
+
+
- (void)dealloc {
_delegate = 0x00;
@@ -110,7 +123,11 @@
}
//This ensures that the db is opened before returning
+#if SQLITE_VERSION_NUMBER >= 3005000
+ if ([db openWithFlags:_openFlags]) {
+#else
if ([db open]) {
+#endif
if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) {
[db close];
db = 0x00;
diff --git a/src/FMDatabaseQueue.h b/src/FMDatabaseQueue.h
index 555c2d2..daf6520 100644
--- a/src/FMDatabaseQueue.h
+++ b/src/FMDatabaseQueue.h
@@ -69,7 +69,7 @@
}
@property (atomic, retain) NSString *path;
-@property (atomic) int openFlags;
+@property (atomic, readonly) int openFlags;
///----------------------------------------------------
/// @name Initialization, opening, and closing of queue
@@ -112,6 +112,15 @@
- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags;
+/** Returns the Class of 'FMDatabase' subclass, that will be used to instantiate database object.
+
+ Subclasses can override this method to return specified Class of 'FMDatabase' subclass.
+
+ @return The Class of 'FMDatabase' subclass, that will be used to instantiate database object.
+ */
+
++ (Class)databaseClass;
+
/** Close database used by queue. */
- (void)close;
diff --git a/src/FMDatabaseQueue.m b/src/FMDatabaseQueue.m
index 797a366..811a6d1 100644
--- a/src/FMDatabaseQueue.m
+++ b/src/FMDatabaseQueue.m
@@ -40,6 +40,9 @@
return q;
}
++ (Class)databaseClass {
+ return [FMDatabase class];
+}
- (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags {
@@ -47,7 +50,7 @@
if (self != nil) {
- _db = [FMDatabase databaseWithPath:aPath];
+ _db = [[[self class] databaseClass] databaseWithPath:aPath];
FMDBRetain(_db);
#if SQLITE_VERSION_NUMBER >= 3005000
@@ -111,7 +114,7 @@
#if SQLITE_VERSION_NUMBER >= 3005000
if (![_db openWithFlags:_openFlags]) {
#else
- if (![db open])
+ if (![db open]) {
#endif
NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path);
FMDBRelease(_db);
diff --git a/src/fmdb.m b/src/fmdb.m
index c593f38..83a3d64 100644
--- a/src/fmdb.m
+++ b/src/fmdb.m
@@ -1377,6 +1377,20 @@ void testPool(NSString *dbPath) {
NSLog(@"Number of open databases after crazy gcd stuff: %ld", [dbPool countOfOpenDatabases]);
}
+ FMDatabasePool *dbPool2 = [FMDatabasePool databasePoolWithPath:dbPath flags:SQLITE_OPEN_READONLY];
+
+ FMDBQuickCheck(dbPool2);
+ {
+ [dbPool2 inDatabase:^(FMDatabase *db2) {
+ FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM test"];
+ FMDBQuickCheck(rs1 != nil);
+ [rs1 close];
+
+ BOOL ok = [db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]];
+ FMDBQuickCheck(!ok);
+ }];
+ }
+
// 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