Browse Source

Implement properties

There were several properties that were implemented as simple getter and setter methods, with no formal property definitions, but with manually defined ivars. In Objective-C this isn't problematic (as the Objective-C "dot" notation is merely syntactic sugar), but it leads to an unintuitive interface for Swift. By shifting these manually implemented getter/setters with manual ivars to properties makes the code a little more intuitive, and leads to more natural looking code in Swift (e.g. rather than `let timeout = db.maxBusyRetryTimeInterval()` and `db.setMaxBusyRetryTimeInterval(value)`, we can just do more natural `let timeout = db.maxBusyRetryTimeoutInterval` and `db.maxBusyRetryTimeInterval = value`. Affected properties include `databasePath`, `maxBusyRetryTimeInterval`, `shouldCacheStatements`, `sqliteHandle`, `hasOpenResultSets`, `lastInsertRowId`, `changes`, `goodConnection`, `columnCount`, `resultDictionary`, `applicationID`, `applicationIDString`, `userVersion`, `countOfCheckedInDatabases`, `countOfCheckedOutDatabases`, and `countOfOpenDatabases`.

Also updated documentation for the file URL based methods.

Also a few deprecated methods have been updated with `__deprecated_msg` so that the app developers have a fighting chance to see what the replacement method should be.

Renamed `isInTransaction`.

Fixed a few nullability definitions, e.g. `stringForColumn` (and all the other similar ones that return pointers), `columnNameForIndex`, etc.

The `objectForColumn` (and the associated subscript operator) now returns `nil` if an invalid column name/index is passed to it. It used to return `NSNull`. I've created unit tests that test that.

Updated README
Robert M. Ryan 8 years ago
parent
commit
07e0362e60

+ 35 - 14
README.markdown

@@ -1,4 +1,4 @@
-# FMDB v2.7
+f# FMDB v2.7
 
 This is an Objective-C wrapper around SQLite: http://sqlite.org/
 
@@ -38,27 +38,43 @@ You can use either style in your Cocoa project.  FMDB will figure out which you
 
 ## What's New in FMDB 2.7
 
+FMDB 2.7 attempts to support a more natural interface. This represents a fairly significant change for Swift developers (audited for nullability; shifted to properties in external interfaces where possible rather than methods; etc.). For Objective-C developers, this should be a fairly seamless transition (unless you were using the ivars that were previously exposed in the public interface, which you shouldn't have been doing, anyway!). 
+
 ### Nullability and Swift Optionals
 
-FMDB 2.8 is largely the same as prior versions, but has been audited for nullability. For Objective-C users, this simply means that you may receive more meaningful warnings if you attempt to pass `nil` to a method that does not accept `nil` values.
+FMDB 2.7 is largely the same as prior versions, but has been audited for nullability. For Objective-C users, this simply means that if you perform a static analysis of your FMDB-based project, you may receive more meaningful warnings as you review your project, but there are likely to be few, if any, changes necessary in your code.
 
-The main benefit is for Swift users, where the library will use optionals more judiciously, and only use where optionals are necessary.
+For Swift users, this nullability audit results in changes that are not entirely backward compatible with FMDB 2.6, but is a little more Swifty. Before FMDB was audited for nullability, Swift was forced to defensively assume that variables were optional, but the library now more accurately knows which properties and method parameters are optional, and which are not.
 
 This means, though, that Swift code written for FMDB 2.7 may require changes. For example, consider the following Swift 3 code written for FMDB 2.6:
 ```swift
+
+guard let queue = FMDatabaseQueue(path: fileURL.path) else {
+    print("Unable to create FMDatabaseQueue")
+    return
+}
+
 queue.inTransaction { db, rollback in
     do {
-        try db?.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
-        try db?.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [2])
+        guard let db == db else {
+            // handle error here
+            return
+        }
+
+        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
+        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [2])
     } catch {
         rollback?.pointee = true
     }
 }
 ```
 
-Because FMDB 2.6 was not audited for nullability, Swift assumed that the `db` and `rollback` were optionals. But, now, in FMDB 2.7, Swift now knows that neither `db` nor `rollback` can be `nil`, so they are not optionals any more. Thus it becomes:
+Because FMDB 2.6 was not audited for nullability, Swift inferred that `db` and `rollback` were optionals. But, now, in FMDB 2.7, Swift now knows that, for example, neither `db` nor `rollback` above can be `nil`, so they are no longer optionals. Thus it becomes:
 
 ```swift
+
+let queue = FMDatabaseQueue(url: fileURL)
+
 queue.inTransaction { db, rollback in
     do {
         try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
@@ -102,11 +118,19 @@ Note, the method `makeFunctionNamed:maximumArguments:withBlock:` has been rename
 
 ### API Changes
 
-In addition to the `makeFunctionNamed` noted above, there are two other API changes. Specifically, to become consistent with the rest of the API, the methods `objectForColumnName` and `UTF8StringForColumnName` have been renamed to `objectForColumn` and `UTF8StringForColumn`.
+In addition to the `makeFunctionNamed` noted above, there are a few other API changes. Specifically, 
+
+ - To become consistent with the rest of the API, the methods `objectForColumnName` and `UTF8StringForColumnName` have been renamed to `objectForColumn` and `UTF8StringForColumn`.
+
+ - Note, the `objectForColumn` (and the associted subscript operator) now returns `nil` if an invalid column name/index is passed to it. It used to return `NSNull`.
+
+ - To avoid confusion with `FMDatabaseQueue` method `inTransaction`, which performs transactions, the `FMDatabase` method to determine whether you are in a transaction or not, `inTransaction`, has been replaced with a read-only property, `isInTransaction`. 
+
+ - Several functions have been converted to properties, namely, `databasePath`, `maxBusyRetryTimeInterval`, `shouldCacheStatements`, `sqliteHandle`, `hasOpenResultSets`, `lastInsertRowId`, `changes`, `goodConnection`, `columnCount`, `resultDictionary`, `applicationID`, `applicationIDString`, `userVersion`, `countOfCheckedInDatabases`, `countOfCheckedOutDatabases`, and `countOfOpenDatabases`. For Objective-C users, this has little material impact, but for Swift users, it results in a slightly more natural interface. Note: For Objective-C developers, previously versions of FMDB exposed many ivars (but we hope you weren't using them directly, anyway!), but the implmentation details for these are no longer exposed.
 
 ### URL Methods
 
-Added `NSURL` renditions of the various `init` methods, previously only accepting paths. 
+In keeping with Apple's shift from paths to URLs, there are now `NSURL` renditions of the various `init` methods, previously only accepting paths. 
 
 ## Usage
 There are three main classes in FMDB:
@@ -182,8 +206,8 @@ if ([s next]) {
 - `dateForColumn:`
 - `dataForColumn:`
 - `dataNoCopyForColumn:`
-- `UTF8StringForColumnName:`
-- `objectForColumnName:`
+- `UTF8StringForColumn:`
+- `objectForColumn:`
 
 Each of these methods also has a `{type}ForColumnIndex:` variant that is used to retrieve the data based on the position of the column in the results, as opposed to the column's name.
 
@@ -398,10 +422,7 @@ let fileURL = try! FileManager.default
     .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
     .appendingPathComponent("test.sqlite")
 
-guard let database = FMDatabase(url: fileURL) else {
-    print("unable to create database")
-    return
-}
+let database = FMDatabase(url: fileURL)
 
 guard database.open() else {
     print("Unable to open database")

+ 33 - 5
Tests/FMDatabaseTests.m

@@ -184,17 +184,45 @@ - (void)testSelectByColumnName
     XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
 }
 
-- (void)testSelectWithIndexedAndKeyedSubscript
+- (void)testInvalidColumnNames
 {
     FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
     
     XCTAssertNotNil(rs, @"Should have a non-nil result set");
     
+    NSString *invalidColumnName = @"foobar";
+
+    while ([rs next]) {
+        XCTAssertNil(rs[invalidColumnName], @"Invalid column name should return nil");
+        XCTAssertNil([rs stringForColumn:invalidColumnName], @"Invalid column name should return nil");
+        XCTAssertEqual([rs UTF8StringForColumn:invalidColumnName], (const unsigned char *)0, @"Invalid column name should return nil");
+        XCTAssertNil([rs dateForColumn:invalidColumnName], @"Invalid column name should return nil");
+        XCTAssertNil([rs dataForColumn:invalidColumnName], @"Invalid column name should return nil");
+        XCTAssertNil([rs dataNoCopyForColumn:invalidColumnName], @"Invalid column name should return nil");
+        XCTAssertNil([rs objectForColumn:invalidColumnName], @"Invalid column name should return nil");
+    }
+    
+    [rs close];
+    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
+    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
+}
+
+- (void)testInvalidColumnIndexes
+{
+    FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
+    
+    XCTAssertNotNil(rs, @"Should have a non-nil result set");
+    
+    int invalidColumnIndex = 999;
+    
     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'");
+        XCTAssertNil(rs[invalidColumnIndex], @"Invalid column name should return nil");
+        XCTAssertNil([rs stringForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
+        XCTAssertEqual([rs UTF8StringForColumnIndex:invalidColumnIndex], (const unsigned char *)0, @"Invalid column name should return nil");
+        XCTAssertNil([rs dateForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
+        XCTAssertNil([rs dataForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
+        XCTAssertNil([rs dataNoCopyForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
+        XCTAssertNil([rs objectForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
     }
     
     [rs close];

+ 49 - 40
src/fmdb/FMDatabase.h

@@ -128,6 +128,32 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  */
 
 + (instancetype)databaseWithPath:(NSString * _Nullable)inPath;
+
+/** Create a `FMDatabase` object.
+ 
+ An `FMDatabase` is created with a path to a SQLite database file.  This path can be one of these three:
+ 
+ 1. A file system URL.  The file does not have to exist on disk.  If it does not exist, it is created for you.
+ 2. `nil`.  An in-memory database is created.  This database will be destroyed with the `FMDatabase` connection is closed.
+ 
+ For example, to create/open a database in your Mac OS X `tmp` folder:
+ 
+    FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"];
+ 
+ Or, in iOS, you might open a database in the app's `Documents` directory:
+ 
+    NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
+    NSString *dbPath   = [docsPath stringByAppendingPathComponent:@"test.db"];
+    FMDatabase *db     = [FMDatabase databaseWithPath:dbPath];
+ 
+ (For more information on temporary and in-memory databases, read the sqlite documentation on the subject: [http://www.sqlite.org/inmemorydb.html](http://www.sqlite.org/inmemorydb.html))
+ 
+ @param url The local file URL (not remote URL) of database file
+ 
+ @return `FMDatabase` object if successful; `nil` if failure.
+ 
+ */
+
 + (instancetype)databaseWithURL:(NSURL * _Nullable)url;
 
 /** Initialize a `FMDatabase` object.
@@ -162,9 +188,8 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  
  An `FMDatabase` is created with a local file URL to a SQLite database file.  This path can be one of these three:
  
- 1. A file system path.  The file does not have to exist on disk.  If it does not exist, it is created for you.
- 2. An empty string (`@""`).  An empty database is created at a temporary location.  This database is deleted with the `FMDatabase` connection is closed.
- 3. `nil`.  An in-memory database is created.  This database will be destroyed with the `FMDatabase` connection is closed.
+ 1. A file system URL.  The file does not have to exist on disk.  If it does not exist, it is created for you.
+ 2. `nil`.  An in-memory database is created.  This database will be destroyed with the `FMDatabase` connection is closed.
  
  For example, to create/open a database in your Mac OS X `tmp` folder:
  
@@ -276,7 +301,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @return `YES` if everything succeeds, `NO` on failure.
  */
 
-- (BOOL)goodConnection;
+@property (nonatomic, readonly) BOOL goodConnection;
 
 
 ///----------------------
@@ -312,7 +337,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @warning **Deprecated**: Please use `<executeUpdate:withErrorAndBindings>` instead.
  */
 
-- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError * _Nullable*)outErr, ... __attribute__ ((deprecated));
+- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError * _Nullable*)outErr, ...  __deprecated_msg("Use executeUpdate:withErrorAndBindings: instead");;
 
 /** Execute single update statement
 
@@ -500,7 +525,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
 
  */
 
-- (int64_t)lastInsertRowId;
+@property (nonatomic, readonly) int64_t lastInsertRowId;
 
 /** The number of rows changed by prior SQL statement.
  
@@ -512,7 +537,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  
  */
 
-- (int)changes;
+@property (nonatomic, readonly) int changes;
 
 
 ///-------------------------
@@ -653,7 +678,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @see commit
  @see rollback
  @see beginDeferredTransaction
- @see inTransaction
+ @see isInTransaction
  */
 
 - (BOOL)beginTransaction;
@@ -665,7 +690,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @see commit
  @see rollback
  @see beginTransaction
- @see inTransaction
+ @see isInTransaction
  */
 
 - (BOOL)beginDeferredTransaction;
@@ -679,7 +704,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @see beginTransaction
  @see beginDeferredTransaction
  @see rollback
- @see inTransaction
+ @see isInTransaction
  */
 
 - (BOOL)commit;
@@ -693,22 +718,22 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @see beginTransaction
  @see beginDeferredTransaction
  @see commit
- @see inTransaction
+ @see isInTransaction
  */
 
 - (BOOL)rollback;
 
 /** Identify whether currently in a transaction or not
- 
- @return `YES` if currently within transaction; `NO` if not.
- 
+  
  @see beginTransaction
  @see beginDeferredTransaction
  @see commit
  @see rollback
  */
 
-- (BOOL)inTransaction;
+@property (nonatomic, readonly) BOOL isInTransaction;
+
+- (BOOL)inTransaction __deprecated_msg("Use isInTransaction property instead");
 
 
 ///----------------------------------------
@@ -728,21 +753,12 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  @return `YES` if there are open result sets; `NO` if not.
  */
 
-- (BOOL)hasOpenResultSets;
+@property (nonatomic, readonly) BOOL hasOpenResultSets;
 
-/** Return whether should cache statements or not
- 
- @return `YES` if should cache statements; `NO` if not.
- */
-
-- (BOOL)shouldCacheStatements;
-
-/** Set whether should cache statements or not
- 
- @param value `YES` if should cache statements; `NO` if not.
- */
+/** Whether should cache statements or not
+  */
 
-- (void)setShouldCacheStatements:(BOOL)value;
+@property (nonatomic) BOOL shouldCacheStatements;
 
 /** Interupt pending database operation
  
@@ -816,20 +832,14 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
 ///------------------------------
 
 /** The path of the database file
- 
- @return path of database.
- 
  */
 
-- (NSString * _Nullable)databasePath;
+@property (nonatomic, readonly, nullable) NSString *databasePath;
 
 /** The file URL of the database file.
- 
- @return The file `NSURL` of database.
- 
  */
 
-- (NSURL * _Nullable)databaseURL;
+@property (nonatomic, readonly, nullable) NSURL *databaseURL;
 
 /** The underlying SQLite handle 
  
@@ -837,7 +847,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
  
  */
 
-- (void *)sqliteHandle;
+@property (nonatomic, readonly) void *sqliteHandle;
 
 
 ///-----------------------------
@@ -913,8 +923,7 @@ typedef int(^FMDBExecuteStatementsCallbackBlock)(NSDictionary *resultsDictionary
 
 
 // description forthcoming
-- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeoutInSeconds;
-- (NSTimeInterval)maxBusyRetryTimeInterval;
+@property (nonatomic) NSTimeInterval maxBusyRetryTimeInterval;
 
 
 ///------------------
@@ -1141,7 +1150,7 @@ typedef NS_ENUM(int, SqliteValueType) {
  @see storeableDateFormat:
  */
 
-- (NSDate *)dateFromString:(NSString *)s;
+- (NSDate * _Nullable)dateFromString:(NSString *)s;
 
 /** Convert the supplied NSDate to NSString, using the current database formatter.
  

+ 12 - 17
src/fmdb/FMDatabase.m

@@ -10,11 +10,7 @@
 
 @interface FMDatabase () {
     void*               _db;
-    NSString*           _databasePath;
-    BOOL                _shouldCacheStatements;
     BOOL                _isExecutingStatement;
-    BOOL                _inTransaction;
-    NSTimeInterval      _maxBusyRetryTimeInterval;
     NSTimeInterval      _startBusyRetryTime;
     
     NSMutableSet        *_openResultSets;
@@ -34,6 +30,13 @@ - (BOOL)executeUpdate:(NSString *)sql error:(NSError * _Nullable *)outErr withAr
 
 @implementation FMDatabase
 
+// Because these two properties have all of their accessor methods implemented,
+// we have to synthesize them to get the corresponding ivars. The rest of the
+// properties have their ivars synthesized automatically for us.
+
+@synthesize shouldCacheStatements = _shouldCacheStatements;
+@synthesize maxBusyRetryTimeInterval = _maxBusyRetryTimeInterval;
+
 #pragma mark FMDatabase instantiation and deallocation
 
 + (instancetype)databaseWithPath:(NSString *)aPath {
@@ -90,10 +93,6 @@ - (void)dealloc {
 #endif
 }
 
-- (NSString *)databasePath {
-    return _databasePath;
-}
-
 - (NSURL *)databaseURL {
     return _databasePath ? [NSURL fileURLWithPath:_databasePath] : nil;
 }
@@ -1297,7 +1296,7 @@ - (BOOL)rollback {
     BOOL b = [self executeUpdate:@"rollback transaction"];
     
     if (b) {
-        _inTransaction = NO;
+        _isInTransaction = NO;
     }
     
     return b;
@@ -1307,7 +1306,7 @@ - (BOOL)commit {
     BOOL b =  [self executeUpdate:@"commit transaction"];
     
     if (b) {
-        _inTransaction = NO;
+        _isInTransaction = NO;
     }
     
     return b;
@@ -1317,7 +1316,7 @@ - (BOOL)beginDeferredTransaction {
     
     BOOL b = [self executeUpdate:@"begin deferred transaction"];
     if (b) {
-        _inTransaction = YES;
+        _isInTransaction = YES;
     }
     
     return b;
@@ -1327,14 +1326,14 @@ - (BOOL)beginTransaction {
     
     BOOL b = [self executeUpdate:@"begin exclusive transaction"];
     if (b) {
-        _inTransaction = YES;
+        _isInTransaction = YES;
     }
     
     return b;
 }
 
 - (BOOL)inTransaction {
-    return _inTransaction;
+    return _isInTransaction;
 }
 
 - (BOOL)interrupt
@@ -1556,10 +1555,6 @@ - (void)resultErrorTooBigInContext:(void *)context {
 
 
 @implementation FMStatement
-@synthesize statement=_statement;
-@synthesize query=_query;
-@synthesize useCount=_useCount;
-@synthesize inUse=_inUse;
 
 #if ! __has_feature(objc_arc)
 - (void)finalize {

+ 5 - 36
src/fmdb/FMDatabaseAdditions.h

@@ -192,7 +192,7 @@ NS_ASSUME_NONNULL_BEGIN
  @warning Deprecated - use `<columnExists:inTableWithName:>` instead.
  */
 
-- (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated));
+- (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __deprecated_msg("Use columnExists:inTableWithName: instead");
 
 
 /** Validate SQL statement
@@ -221,36 +221,16 @@ NS_ASSUME_NONNULL_BEGIN
  @see setApplicationID:
  */
 
-- (uint32_t)applicationID;
-
-/** Set the application ID
-
- @param appID The `uint32_t` numeric value of the application ID.
- 
- @see applicationID
- */
-
-- (void)setApplicationID:(uint32_t)appID;
+@property (nonatomic) uint32_t applicationID;
 
 #if TARGET_OS_MAC && !TARGET_OS_IPHONE
-/** Retrieve application ID string
 
- @return The `NSString` value of the application ID.
+/** Retrieve application ID string
 
  @see setApplicationIDString:
  */
 
-
-- (NSString*)applicationIDString;
-
-/** Set the application ID string
-
- @param string The `NSString` value of the application ID.
-
- @see applicationIDString
- */
-
-- (void)setApplicationIDString:(NSString*)string;
+@property (nonatomic, retain) NSString *applicationIDString;
 
 #endif
 
@@ -260,21 +240,10 @@ NS_ASSUME_NONNULL_BEGIN
 
 /** Retrieve user version
  
- @return The `uint32_t` numeric value of the user version.
- 
  @see setUserVersion:
  */
 
-- (uint32_t)userVersion;
-
-/** Set the user-version
- 
- @param version The `uint32_t` numeric value of the user version.
- 
- @see userVersion
- */
-
-- (void)setUserVersion:(uint32_t)version;
+@property (nonatomic) uint32_t userVersion;
 
 @end
 

+ 5 - 11
src/fmdb/FMDatabasePool.h

@@ -78,7 +78,7 @@ NS_ASSUME_NONNULL_BEGIN
 /** 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
+ @param openFlags Flags passed to the openWithFlags method of the database.
  
  @return The `FMDatabasePool` object. `nil` on error.
  */
@@ -88,7 +88,7 @@ NS_ASSUME_NONNULL_BEGIN
 /** Create pool using file URL and specified flags
  
  @param url The file `NSURL` of the database.
- @param openFlags Flags passed to the openWithFlags method of the database
+ @param openFlags Flags passed to the openWithFlags method of the database.
  
  @return The `FMDatabasePool` object. `nil` on error.
  */
@@ -169,25 +169,19 @@ NS_ASSUME_NONNULL_BEGIN
 ///------------------------------------------------
 
 /** Number of checked-in databases in pool
- 
- @returns Number of databases
  */
 
-- (NSUInteger)countOfCheckedInDatabases;
+@property (nonatomic, readonly) NSUInteger countOfCheckedInDatabases;
 
 /** Number of checked-out databases in pool
-
- @returns Number of databases
  */
 
-- (NSUInteger)countOfCheckedOutDatabases;
+@property (nonatomic, readonly) NSUInteger countOfCheckedOutDatabases;
 
 /** Total number of databases in pool
-
- @returns Number of databases
  */
 
-- (NSUInteger)countOfOpenDatabases;
+@property (nonatomic, readonly) NSUInteger countOfOpenDatabases;
 
 /** Release all databases in pool */
 

+ 21 - 23
src/fmdb/FMResultSet.h

@@ -111,7 +111,7 @@ NS_ASSUME_NONNULL_BEGIN
  @return Integer value of the number of columns.
  */
 
-- (int)columnCount;
+@property (nonatomic, readonly) int columnCount;
 
 /** Column index for column name
 
@@ -129,7 +129,7 @@ NS_ASSUME_NONNULL_BEGIN
  @return columnName `NSString` value of the name of the column.
  */
 
-- (NSString*)columnNameForIndex:(int)columnIdx;
+- (NSString * _Nullable)columnNameForIndex:(int)columnIdx;
 
 /** Result set integer value for column.
 
@@ -245,17 +245,17 @@ NS_ASSUME_NONNULL_BEGIN
 
  @param columnName `NSString` value of the name of the column.
 
- @return `NSString` value of the result set's column.
+ @return String value of the result set's column.
  
  */
 
-- (NSString*)stringForColumn:(NSString*)columnName;
+- (NSString * _Nullable)stringForColumn:(NSString*)columnName;
 
 /** Result set `NSString` value for column.
 
  @param columnIdx Zero-based index for column.
 
- @return `NSString` value of the result set's column.
+ @return String value of the result set's column.
  */
 
 - (NSString * _Nullable)stringForColumnIndex:(int)columnIdx;
@@ -264,7 +264,7 @@ NS_ASSUME_NONNULL_BEGIN
 
  @param columnName `NSString` value of the name of the column.
 
- @return `NSDate` value of the result set's column.
+ @return Date value of the result set's column.
  */
 
 - (NSDate * _Nullable)dateForColumn:(NSString*)columnName;
@@ -273,7 +273,7 @@ NS_ASSUME_NONNULL_BEGIN
 
  @param columnIdx Zero-based index for column.
 
- @return `NSDate` value of the result set's column.
+ @return Date value of the result set's column.
  
  */
 
@@ -285,7 +285,7 @@ NS_ASSUME_NONNULL_BEGIN
 
  @param columnName `NSString` value of the name of the column.
 
- @return `NSData` value of the result set's column.
+ @return Data value of the result set's column.
  
  */
 
@@ -295,7 +295,7 @@ NS_ASSUME_NONNULL_BEGIN
 
  @param columnIdx Zero-based index for column.
 
- @return `NSData` value of the result set's column.
+ @return Data value of the result set's column.
  */
 
 - (NSData * _Nullable)dataForColumnIndex:(int)columnIdx;
@@ -307,9 +307,9 @@ NS_ASSUME_NONNULL_BEGIN
  @return `(const unsigned char *)` value of the result set's column.
  */
 
-- (const unsigned char *)UTF8StringForColumn:(NSString*)columnName;
+- (const unsigned char * _Nullable)UTF8StringForColumn:(NSString*)columnName;
 
-- (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName __deprecated_msg("Use UTF8StringForColumn instead");
+- (const unsigned char * _Nullable)UTF8StringForColumnName:(NSString*)columnName __deprecated_msg("Use UTF8StringForColumn instead");
 
 /** Result set `(const unsigned char *)` value for column.
 
@@ -322,16 +322,16 @@ NS_ASSUME_NONNULL_BEGIN
 
 /** Result set object for column.
 
- @param columnName `NSString` value of the name of the column.
+ @param columnName Name of the column.
 
  @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object.
 
  @see objectForKeyedSubscript:
  */
 
-- (id)objectForColumn:(NSString*)columnName;
+- (id _Nullable)objectForColumn:(NSString*)columnName;
 
-- (id)objectForColumnName:(NSString*)columnName __deprecated_msg("Use objectForColumn instead");
+- (id _Nullable)objectForColumnName:(NSString*)columnName __deprecated_msg("Use objectForColumn instead");
 
 /** Result set object for column.
 
@@ -342,7 +342,7 @@ NS_ASSUME_NONNULL_BEGIN
  @see objectAtIndexedSubscript:
  */
 
-- (id)objectForColumnIndex:(int)columnIdx;
+- (id _Nullable)objectForColumnIndex:(int)columnIdx;
 
 /** Result set object for column.
  
@@ -363,7 +363,7 @@ NS_ASSUME_NONNULL_BEGIN
  @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object.
  */
 
-- (id)objectForKeyedSubscript:(NSString *)columnName;
+- (id _Nullable)objectForKeyedSubscript:(NSString *)columnName;
 
 /** Result set object for column.
 
@@ -384,13 +384,13 @@ NS_ASSUME_NONNULL_BEGIN
  @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object.
  */
 
-- (id)objectAtIndexedSubscript:(int)columnIdx;
+- (id _Nullable)objectAtIndexedSubscript:(int)columnIdx;
 
 /** Result set `NSData` value for column.
 
  @param columnName `NSString` value of the name of the column.
 
- @return `NSData` value of the result set's column.
+ @return Data value of the result set's column.
 
  @warning If you are going to use this data after you iterate over the next row, or after you close the
 result set, make sure to make a copy of the data first (or just use `<dataForColumn:>`/`<dataForColumnIndex:>`)
@@ -404,7 +404,7 @@ If you don't, you're going to be in a world of hurt when you try and use the dat
 
  @param columnIdx Zero-based index for column.
 
- @return `NSData` value of the result set's column.
+ @return Data value of the result set's column.
 
  @warning If you are going to use this data after you iterate over the next row, or after you close the
  result set, make sure to make a copy of the data first (or just use `<dataForColumn:>`/`<dataForColumnIndex:>`)
@@ -435,12 +435,10 @@ If you don't, you're going to be in a world of hurt when you try and use the dat
 
 /** Returns a dictionary of the row results mapped to case sensitive keys of the column names. 
  
- @returns `NSDictionary` of the row results.
- 
  @warning The keys to the dictionary are case sensitive of the column names.
  */
 
-- (NSDictionary * _Nullable)resultDictionary;
+@property (nonatomic, readonly, nullable) NSDictionary *resultDictionary;
  
 /** Returns a dictionary of the row results
  
@@ -449,7 +447,7 @@ If you don't, you're going to be in a world of hurt when you try and use the dat
  @warning **Deprecated**: Please use `<resultDictionary>` instead.  Also, beware that `<resultDictionary>` is case sensitive! 
  */
 
-- (NSDictionary * _Nullable)resultDict  __attribute__ ((deprecated));
+- (NSDictionary * _Nullable)resultDict __deprecated_msg("Use resultDictionary instead");
 
 ///-----------------------------
 /// @name Key value coding magic

+ 9 - 7
src/fmdb/FMResultSet.m

@@ -230,8 +230,6 @@ - (int)columnIndexForName:(NSString*)columnName {
     return -1;
 }
 
-
-
 - (int)intForColumn:(NSString*)columnName {
     return [self intForColumnIndex:[self columnIndexForName:columnName]];
 }
@@ -282,7 +280,7 @@ - (double)doubleForColumnIndex:(int)columnIdx {
 
 - (NSString *)stringForColumnIndex:(int)columnIdx {
     
-    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0) || columnIdx >= sqlite3_column_count([_statement statement])) {
         return nil;
     }
     
@@ -306,7 +304,7 @@ - (NSDate*)dateForColumn:(NSString*)columnName {
 
 - (NSDate*)dateForColumnIndex:(int)columnIdx {
     
-    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0) || columnIdx >= sqlite3_column_count([_statement statement])) {
         return nil;
     }
     
@@ -320,7 +318,7 @@ - (NSData*)dataForColumn:(NSString*)columnName {
 
 - (NSData*)dataForColumnIndex:(int)columnIdx {
     
-    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0) || columnIdx >= sqlite3_column_count([_statement statement])) {
         return nil;
     }
     
@@ -341,7 +339,7 @@ - (NSData*)dataNoCopyForColumn:(NSString*)columnName {
 
 - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx {
     
-    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0) || columnIdx >= sqlite3_column_count([_statement statement])) {
         return nil;
     }
   
@@ -364,7 +362,7 @@ - (BOOL)columnIsNull:(NSString*)columnName {
 
 - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx {
     
-    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+    if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0) || columnIdx >= sqlite3_column_count([_statement statement])) {
         return nil;
     }
     
@@ -380,6 +378,10 @@ - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName {
 }
 
 - (id)objectForColumnIndex:(int)columnIdx {
+    if (columnIdx < 0 || columnIdx >= sqlite3_column_count([_statement statement])) {
+        return nil;
+    }
+    
     int columnType = sqlite3_column_type([_statement statement], columnIdx);
     
     id returnValue = nil;