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:
authorAugust Mueller <gus@flyingmeat.com>2020-05-08 00:07:14 +0300
committerAugust Mueller <gus@flyingmeat.com>2020-05-08 00:07:14 +0300
commitabbaa01a1ebd95cfbde10cda1b2e6bf51c032108 (patch)
tree5d0b3b0a843c9c1732a709ab47560dafbc850a2b
parent48594601589ec26d848906e2ffa568e52590c056 (diff)
parent4009be0a6f9de7cc6d6a59cf8ff820269015a828 (diff)
Merge branch 'robertmryan-robertmryan/allow-rebinding'
-rw-r--r--CHANGES_AND_TODO_LIST.txt5
-rw-r--r--README.markdown17
-rw-r--r--Tests/FMDatabaseTests.m186
-rw-r--r--src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h2
-rw-r--r--src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m2
-rw-r--r--src/fmdb/FMDatabase.h90
-rw-r--r--src/fmdb/FMDatabase.m426
-rw-r--r--src/fmdb/FMDatabaseAdditions.h4
-rw-r--r--src/fmdb/FMDatabaseAdditions.m4
-rw-r--r--src/fmdb/FMResultSet.h43
-rw-r--r--src/fmdb/FMResultSet.m51
11 files changed, 466 insertions, 364 deletions
diff --git a/CHANGES_AND_TODO_LIST.txt b/CHANGES_AND_TODO_LIST.txt
index d644967..0783142 100644
--- a/CHANGES_AND_TODO_LIST.txt
+++ b/CHANGES_AND_TODO_LIST.txt
@@ -3,8 +3,11 @@ Zip, nada, zilch. Got any ideas?
If you would like to contribute some code ... awesome! I just ask that you make it conform to the coding conventions already set in here, and to add the necessary of tests for your new code to tests target. And of course, the code should be of general use to more than just a couple of folks. Send your patches to gus@flyingmeat.com.
+2020.05.06 Version 2.7.7
+ Add `prepare` and `bind` methods so you can prepare a statement once and bind values repeatedly.
+
2020.04.23 Version 2.7.6
-A new tag for the Swift Package Manager.
+ A new tag for the Swift Package Manager.
2018.10.23 Version 2.7.5
Xcode 10 support. Probably some other stuff over the past year as well.
diff --git a/README.markdown b/README.markdown
index 012c524..fb72d0d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -3,15 +3,15 @@
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FMDB.svg)](https://img.shields.io/cocoapods/v/FMDB.svg)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
-This is an Objective-C wrapper around SQLite: http://sqlite.org/
+This is an Objective-C wrapper around [SQLite](https://sqlite.org/).
## The FMDB Mailing List:
-http://groups.google.com/group/fmdb
+https://groups.google.com/group/fmdb
## Read the SQLite FAQ:
-http://www.sqlite.org/faq.html
+https://www.sqlite.org/faq.html
-Since FMDB is built on top of SQLite, you're going to want to read this page top to bottom at least once. And while you're there, make sure to bookmark the SQLite Documentation page: http://www.sqlite.org/docs.html
+Since FMDB is built on top of SQLite, you're going to want to read this page top to bottom at least once. And while you're there, make sure to bookmark the SQLite Documentation page: https://www.sqlite.org/docs.html
## Contributing
Do you have an awesome idea that deserves to be in FMDB? You might consider pinging ccgus first to make sure he hasn't already ruled it out for some reason. Otherwise pull requests are great, and make sure you stick to the local coding conventions. However, please be patient and if you haven't heard anything from ccgus for a week or more, you might want to send a note asking what's up.
@@ -20,9 +20,6 @@ Do you have an awesome idea that deserves to be in FMDB? You might consider pin
### CocoaPods
-[![Dependency Status](https://www.versioneye.com/objective-c/fmdb/2.3/badge.svg?style=flat)](https://www.versioneye.com/objective-c/fmdb/2.3)
-[![Reference Status](https://www.versioneye.com/objective-c/fmdb/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/fmdb/references)
-
FMDB can be installed using [CocoaPods](https://cocoapods.org/).
If you haven't done so already, you might want to initialize the project, to have it produce a `Podfile` template for you:
@@ -75,7 +72,7 @@ $ carthage update
You can then configure your project as outlined in Carthage's [Getting Started](https://github.com/Carthage/Carthage#getting-started) (i.e. for iOS, adding the framework to the "Link Binary with Libraries" in your target and adding the `copy-frameworks` script; in macOS, adding the framework to the list of "Embedded Binaries").
## FMDB Class Reference:
-http://ccgus.github.io/fmdb/html/index.html
+https://ccgus.github.io/fmdb/html/index.html
## Automatic Reference Counting (ARC) or Manual Memory Management?
You can use either style in your Cocoa project. FMDB will figure out which you are using at compile time and do the right thing.
@@ -182,7 +179,7 @@ An `FMDatabase` is created with a path to a SQLite database file. This path can
2. An empty string (`@""`). An empty database is created at a temporary location. This database is deleted when the `FMDatabase` connection is closed.
3. `NULL`. An in-memory database is created. This database will be destroyed when the `FMDatabase` connection is closed.
-(For more information on temporary and in-memory databases, read the sqlite documentation on the subject: http://www.sqlite.org/inmemorydb.html)
+(For more information on temporary and in-memory databases, read the sqlite documentation on the subject: https://www.sqlite.org/inmemorydb.html)
```objc
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
@@ -436,7 +433,7 @@ To do this, you must:
1. Copy the relevant `.m` and `.h` files from the FMDB `src` folder into your project.
- You can copy all of them (which is easiest), or only the ones you need. Likely you will need [`FMDatabase`](http://ccgus.github.io/fmdb/html/Classes/FMDatabase.html) and [`FMResultSet`](http://ccgus.github.io/fmdb/html/Classes/FMResultSet.html) at a minimum. [`FMDatabaseAdditions`](http://ccgus.github.io/fmdb/html/Categories/FMDatabase+FMDatabaseAdditions.html) provides some very useful convenience methods, so you will likely want that, too. If you are doing multithreaded access to a database, [`FMDatabaseQueue`](http://ccgus.github.io/fmdb/html/Classes/FMDatabaseQueue.html) is quite useful, too. If you choose to not copy all of the files from the `src` directory, though, you may want to update `FMDB.h` to only reference the files that you included in your project.
+ You can copy all of them (which is easiest), or only the ones you need. Likely you will need [`FMDatabase`](https://ccgus.github.io/fmdb/html/Classes/FMDatabase.html) and [`FMResultSet`](https://ccgus.github.io/fmdb/html/Classes/FMResultSet.html) at a minimum. [`FMDatabaseAdditions`](https://ccgus.github.io/fmdb/html/Categories/FMDatabase+FMDatabaseAdditions.html) provides some very useful convenience methods, so you will likely want that, too. If you are doing multithreaded access to a database, [`FMDatabaseQueue`](https://ccgus.github.io/fmdb/html/Classes/FMDatabaseQueue.html) is quite useful, too. If you choose to not copy all of the files from the `src` directory, though, you may want to update `FMDB.h` to only reference the files that you included in your project.
Note, if you're copying all of the files from the `src` folder into to your project (which is recommended), you may want to drag the individual files into your project, not the folder, itself, because if you drag the folder, you won't be prompted to add the bridging header (see next point).
diff --git a/Tests/FMDatabaseTests.m b/Tests/FMDatabaseTests.m
index b9e1f8a..8ca2df5 100644
--- a/Tests/FMDatabaseTests.m
+++ b/Tests/FMDatabaseTests.m
@@ -1124,7 +1124,12 @@
}
- (void)testVersionNumber {
- XCTAssertEqual([FMDatabase FMDBVersion], 0x0276); // this is going to break everytime we bump it.
+ XCTAssertEqual([FMDatabase FMDBVersion], 0x0277); // this is going to break everytime we bump it.
+}
+
+- (void)testUserVersion {
+ NSComparisonResult result = [[FMDatabase FMDBUserVersion] compare:@"2.7.7" options:NSNumericSearch];
+ XCTAssertEqual(result, NSOrderedSame);
}
- (void)testVersionStringAboveRequired {
@@ -1551,4 +1556,183 @@
[manager removeItemAtURL:fileURL2 error:nil];
}
+// These three utility methods used by `testTransient`, to illustrate dangers of SQLITE_STATIC
+
+- (BOOL)utility1ForTestTransient:(FMDatabase *)db withValue:(long)value {
+ @autoreleasepool {
+ NSString *string = [[NSString alloc] initWithFormat:@"value %@", @(value)];
+ return [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", [string dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+}
+
+- (FMResultSet *)utility2ForTestTransient:(FMDatabase *)db withValue:(long)value {
+ @autoreleasepool {
+ NSString *string = [[NSString alloc] initWithFormat:@"value %@", @(value)];
+ return [db executeQuery:@"SELECT * FROM foo WHERE bar = ?", [string dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+}
+
+- (BOOL)utility3ForTestTransient:(FMResultSet *)rs withValue:(long)value {
+ @autoreleasepool {
+ NSString *string = [[NSString alloc] initWithFormat:@"xxxxx %@", @(value + 1)];
+ XCTAssertEqualObjects(string, @"xxxxx 43"); // Just to ensure the above isn't optimized out
+ return [rs next];
+ }
+}
+
+- (void)testTransient {
+ NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ NSFileManager *manager = [NSFileManager defaultManager];
+
+ // ok, first create one database
+
+ FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
+ BOOL success = [db open];
+ XCTAssert(success, @"Database not created correctly for purposes of test");
+ success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (bar BLOB)"];
+ XCTAssert(success, @"Table created correctly for purposes of test");
+
+ long value = 42;
+ success = [self utility1ForTestTransient:db withValue:value];
+ XCTAssert(success, @"INSERT failed");
+
+ FMResultSet *rs = [self utility2ForTestTransient:db withValue:value];
+ XCTAssert(rs, @"Creating SELECT failed");
+
+ // the following is the key test, namely if FMDB uses SQLITE_STATIC, the following may fail, but SQLITE_TRANSIENT ensures it will succeed
+
+ success = [self utility3ForTestTransient:rs withValue:value];
+ XCTAssert(success, @"Performing SELECT failed");
+
+ // let's clean up
+
+ [rs close];
+ [db close];
+ [manager removeItemAtURL:fileURL error:nil];
+}
+
+- (void)testBindFailure {
+ NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ NSFileManager *manager = [NSFileManager defaultManager];
+
+ // ok, first create one database
+
+ FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
+ BOOL success = [db open];
+ XCTAssert(success, @"Database not created correctly for purposes of test");
+ success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (bar BLOB)"];
+ XCTAssert(success, @"Table created correctly for purposes of test");
+
+ NSUInteger limit = (NSUInteger)[db limitFor:SQLITE_LIMIT_LENGTH value:-1] + 1;
+ NSLog(@"%lu", (unsigned long)limit);
+ NSData *data = [NSMutableData dataWithLength:limit];
+ success = [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", data];
+ XCTAssertFalse(success, @"Table created correctly for purposes of test");
+
+ // let's clean up
+
+ [db close];
+ [manager removeItemAtURL:fileURL error:nil];
+}
+
+- (void)testRebindingWithDictionary {
+ NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ NSFileManager *manager = [NSFileManager defaultManager];
+ [manager removeItemAtURL:fileURL error:nil];
+
+ // ok, first create one database
+
+ FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
+ BOOL success = [db open];
+ XCTAssert(success, @"Database not created correctly for purposes of test");
+ success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY, bar TEXT)"];
+ XCTAssert(success, @"Table created correctly for purposes of test");
+
+ FMResultSet *rs = [db prepare:@"INSERT INTO foo (bar) VALUES (:bar)"];
+ XCTAssert(rs, @"INSERT statement not prepared %@", [db lastErrorMessage]);
+
+ NSString *value1 = @"foo";
+ XCTAssert([rs bindWithDictionary:@{@"bar": value1}], @"Unable to bind");
+ XCTAssert([rs step], @"Performing query failed");
+
+ NSString *value2 = @"bar";
+ XCTAssert([rs bindWithDictionary:@{@"bar": value2}], @"Unable to bind");
+ XCTAssert([rs step], @"Performing query failed");
+
+ XCTAssert([rs bindWithDictionary:@{@"bar": value2}], @"Unable to bind");
+ XCTAssert([rs step], @"Performing query failed");
+
+ [rs close];
+
+ rs = [db prepare:@"SELECT bar FROM foo WHERE bar = :bar"];
+ XCTAssert([rs bindWithDictionary:@{@"bar": value1}], @"Unable to bind");
+ XCTAssert([rs next], @"No record found");
+ XCTAssertEqualObjects([rs stringForColumnIndex:0], value1);
+ XCTAssertFalse([rs next], @"There should have been only one record");
+
+ XCTAssert([rs bindWithDictionary:@{@"bar": value2}], @"Unable to bind");
+ XCTAssert([rs next], @"No record found");
+ XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
+ XCTAssert([rs next], @"No record found");
+ XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
+ XCTAssertFalse([rs next], @"There should have been only two records");
+
+ // let's clean up
+
+ [rs close];
+ [db close];
+ [manager removeItemAtURL:fileURL error:nil];
+}
+
+- (void)testRebindingWithArray {
+ NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
+ NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
+ NSFileManager *manager = [NSFileManager defaultManager];
+
+ // ok, first create one database
+
+ FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
+ BOOL success = [db open];
+ XCTAssert(success, @"Database not created correctly for purposes of test");
+ success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY, bar TEXT)"];
+ XCTAssert(success, @"Table created correctly for purposes of test");
+
+ FMResultSet *rs = [db prepare:@"INSERT INTO foo (bar) VALUES (?)"];
+ XCTAssert(rs, @"INSERT statement not prepared %@", [db lastErrorMessage]);
+
+ NSString *value1 = @"foo";
+ XCTAssert([rs bindWithArray:@[value1]], @"Unable to bind");
+ XCTAssert([rs step], @"Performing INSERT 1 failed");
+
+ NSString *value2 = @"bar";
+ XCTAssert([rs bindWithArray:@[value2]], @"Unable to bind");
+ XCTAssert([rs step], @"Performing INSERT 2 failed");
+ XCTAssert([rs bindWithArray:@[value2]], @"Unable to bind");
+ XCTAssert([rs step], @"Performing INSERT 2 failed");
+
+ [rs close];
+
+ rs = [db prepare:@"SELECT bar FROM foo WHERE bar = ?"];
+ XCTAssert([rs bindWithArray:@[value1]], @"Unable to bind");
+ XCTAssert([rs next], @"No record found");
+ XCTAssertEqualObjects([rs stringForColumnIndex:0], value1);
+ XCTAssertFalse([rs next], @"There should have been only one record");
+
+ XCTAssert([rs bindWithArray:@[value2]], @"Unable to bind");
+ XCTAssert([rs next], @"No record found");
+ XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
+ XCTAssert([rs next], @"No record found");
+ XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
+ XCTAssertFalse([rs next], @"There should have been only two records");
+
+ // let's clean up
+
+ [rs close];
+ [db close];
+ [manager removeItemAtURL:fileURL error:nil];
+}
+
@end
diff --git a/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h b/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h
index 84a87c0..3837940 100644
--- a/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h
+++ b/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h
@@ -12,7 +12,7 @@
// support for this functionality via its "Backup" API. Here, we extend the FMBD wrapper
// to include this functionality.
//
-// http://www.sqlite.org/backup.html
+// https://sqlite.org/backup.html
#import "FMDatabase.h"
diff --git a/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m b/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m
index 48d398d..c4d15af 100644
--- a/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m
+++ b/src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m
@@ -2,7 +2,7 @@
#import <sqlite3.h>
-// http://www.sqlite.org/backup.html
+// https://sqlite.org/backup.html
static
int loadOrSaveDb(sqlite3 *pInMemory, const char *zFilename, int isSave)
{
diff --git a/src/fmdb/FMDatabase.h b/src/fmdb/FMDatabase.h
index 982c264..472def9 100644
--- a/src/fmdb/FMDatabase.h
+++ b/src/fmdb/FMDatabase.h
@@ -48,7 +48,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
FMDBCheckpointModeTruncate = 3 // SQLITE_CHECKPOINT_TRUNCATE
};
-/** A SQLite ([http://sqlite.org/](http://sqlite.org/)) Objective-C wrapper.
+/** A SQLite ([https://sqlite.org/](https://sqlite.org/)) Objective-C wrapper.
### Usage
The three main classes in FMDB are:
@@ -65,9 +65,9 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
### External links
- [FMDB on GitHub](https://github.com/ccgus/fmdb) including introductory documentation
- - [SQLite web site](http://sqlite.org/)
+ - [SQLite web site](https://sqlite.org/)
- [FMDB mailing list](http://groups.google.com/group/fmdb)
- - [SQLite FAQ](http://www.sqlite.org/faq.html)
+ - [SQLite FAQ](https://sqlite.org/faq.html)
@warning Do not instantiate a single `FMDatabase` object and use it across multiple threads. Instead, use `<FMDatabaseQueue>`.
@@ -125,7 +125,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
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))
+ (For more information on temporary and in-memory databases, read the sqlite documentation on the subject: [https://sqlite.org/inmemorydb.html](https://sqlite.org/inmemorydb.html))
@param inPath Path of database file
@@ -152,7 +152,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
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))
+ (For more information on temporary and in-memory databases, read the sqlite documentation on the subject: [https://sqlite.org/inmemorydb.html](https://sqlite.org/inmemorydb.html))
@param url The local file URL (not remote URL) of database file
@@ -180,7 +180,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
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))
+ (For more information on temporary and in-memory databases, read the sqlite documentation on the subject: [https://sqlite.org/inmemorydb.html](https://sqlite.org/inmemorydb.html))
@param path Path of database file.
@@ -207,7 +207,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
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))
+ (For more information on temporary and in-memory databases, read the sqlite documentation on the subject: [https://sqlite.org/inmemorydb.html](https://sqlite.org/inmemorydb.html))
@param url The file `NSURL` of database file.
@@ -231,7 +231,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `YES` if successful, `NO` on error.
- @see [sqlite3_open()](http://sqlite.org/c3ref/open.html)
+ @see [sqlite3_open()](https://sqlite.org/c3ref/open.html)
@see openWithFlags:
@see close
*/
@@ -256,7 +256,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `YES` if successful, `NO` on error.
- @see [sqlite3_open_v2()](http://sqlite.org/c3ref/open.html)
+ @see [sqlite3_open_v2()](https://sqlite.org/c3ref/open.html)
@see open
@see close
*/
@@ -283,7 +283,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `YES` if successful, `NO` on error.
- @see [sqlite3_open_v2()](http://sqlite.org/c3ref/open.html)
+ @see [sqlite3_open_v2()](https://sqlite.org/c3ref/open.html)
@see open
@see close
*/
@@ -294,7 +294,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `YES` if success, `NO` on error.
- @see [sqlite3_close()](http://sqlite.org/c3ref/close.html)
+ @see [sqlite3_close()](https://sqlite.org/c3ref/close.html)
@see open
@see openWithFlags:
*/
@@ -320,7 +320,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html), [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html) to bind values to `?` placeholders in the SQL with the optional list of parameters, and [`sqlite_step`](http://sqlite.org/c3ref/step.html) to perform the update.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html), [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html) to bind values to `?` placeholders in the SQL with the optional list of parameters, and [`sqlite_step`](https://sqlite.org/c3ref/step.html) to perform the update.
The optional values provided to this method should be objects (e.g. `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects), not fundamental data types (e.g. `int`, `long`, `NSInteger`, etc.). This method automatically handles the aforementioned object types, and all other object types will be interpreted as text values using the object's `description` method.
@@ -335,7 +335,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@see lastError
@see lastErrorCode
@see lastErrorMessage
- @see [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html)
+ @see [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html)
*/
- (BOOL)executeUpdate:(NSString*)sql withErrorAndBindings:(NSError * _Nullable __autoreleasing *)outErr, ...;
@@ -351,7 +351,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html), [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html) to bind values to `?` placeholders in the SQL with the optional list of parameters, and [`sqlite_step`](http://sqlite.org/c3ref/step.html) to perform the update.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html), [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html) to bind values to `?` placeholders in the SQL with the optional list of parameters, and [`sqlite_step`](https://sqlite.org/c3ref/step.html) to perform the update.
The optional values provided to this method should be objects (e.g. `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects), not fundamental data types (e.g. `int`, `long`, `NSInteger`, etc.). This method automatically handles the aforementioned object types, and all other object types will be interpreted as text values using the object's `description` method.
@@ -364,7 +364,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@see lastError
@see lastErrorCode
@see lastErrorMessage
- @see [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html)
+ @see [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html)
@note This technique supports the use of `?` placeholders in the SQL, automatically binding any supplied value parameters to those placeholders. This approach is more robust than techniques that entail using `stringWithFormat` to manually build SQL statements, which can be problematic if the values happened to include any characters that needed to be quoted.
@@ -375,7 +375,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html) and [`sqlite_step`](http://sqlite.org/c3ref/step.html) to perform the update. Unlike the other `executeUpdate` methods, this uses printf-style formatters (e.g. `%s`, `%d`, etc.) to build the SQL. Do not use `?` placeholders in the SQL if you use this method.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html) and [`sqlite_step`](https://sqlite.org/c3ref/step.html) to perform the update. Unlike the other `executeUpdate` methods, this uses printf-style formatters (e.g. `%s`, `%d`, etc.) to build the SQL. Do not use `?` placeholders in the SQL if you use this method.
@param format The SQL to be performed, with `printf`-style escape sequences.
@@ -403,7 +403,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html) and [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html) binding any `?` placeholders in the SQL with the optional list of parameters.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html) and [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html) binding any `?` placeholders in the SQL with the optional list of parameters.
The optional values provided to this method should be objects (e.g. `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects), not fundamental data types (e.g. `int`, `long`, `NSInteger`, etc.). This method automatically handles the aforementioned object types, and all other object types will be interpreted as text values using the object's `description` method.
@@ -423,7 +423,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html) and [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html) binding any `?` placeholders in the SQL with the optional list of parameters.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html) and [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html) binding any `?` placeholders in the SQL with the optional list of parameters.
The optional values provided to this method should be objects (e.g. `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects), not fundamental data types (e.g. `int`, `long`, `NSInteger`, etc.). This method automatically handles the aforementioned object types, and all other object types will be interpreted as text values using the object's `description` method.
@@ -451,7 +451,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html) and [`sqlite_step`](http://sqlite.org/c3ref/step.html) to perform the update. Unlike the other `executeUpdate` methods, this uses printf-style formatters (e.g. `%s`, `%d`, etc.) to build the SQL.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html) and [`sqlite_step`](https://sqlite.org/c3ref/step.html) to perform the update. Unlike the other `executeUpdate` methods, this uses printf-style formatters (e.g. `%s`, `%d`, etc.) to build the SQL.
The optional values provided to this method should be objects (e.g. `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects), not fundamental data types (e.g. `int`, `long`, `NSInteger`, etc.). This method automatically handles the aforementioned object types, and all other object types will be interpreted as text values using the object's `description` method.
@@ -471,7 +471,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
/** Execute single update statement
- This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](http://sqlite.org/c3ref/prepare.html) and [`sqlite_step`](http://sqlite.org/c3ref/step.html) to perform the update. Unlike the other `executeUpdate` methods, this uses printf-style formatters (e.g. `%s`, `%d`, etc.) to build the SQL.
+ This method executes a single SQL update statement (i.e. any SQL that does not return results, such as `UPDATE`, `INSERT`, or `DELETE`. This method employs [`sqlite3_prepare_v2`](https://sqlite.org/c3ref/prepare.html) and [`sqlite_step`](https://sqlite.org/c3ref/step.html) to perform the update. Unlike the other `executeUpdate` methods, this uses printf-style formatters (e.g. `%s`, `%d`, etc.) to build the SQL.
The optional values provided to this method should be objects (e.g. `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects), not fundamental data types (e.g. `int`, `long`, `NSInteger`, etc.). This method automatically handles the aforementioned object types, and all other object types will be interpreted as text values using the object's `description` method.
@@ -497,7 +497,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `YES` upon success; `NO` upon failure. If failed, you can call `<lastError>`, `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
@see executeStatements:withResultBlock:
- @see [sqlite3_exec()](http://sqlite.org/c3ref/exec.html)
+ @see [sqlite3_exec()](https://sqlite.org/c3ref/exec.html)
*/
@@ -517,7 +517,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
`<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
@see executeStatements:
- @see [sqlite3_exec()](http://sqlite.org/c3ref/exec.html)
+ @see [sqlite3_exec()](https://sqlite.org/c3ref/exec.html)
*/
@@ -531,7 +531,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return The rowid of the last inserted row.
- @see [sqlite3_last_insert_rowid()](http://sqlite.org/c3ref/last_insert_rowid.html)
+ @see [sqlite3_last_insert_rowid()](https://sqlite.org/c3ref/last_insert_rowid.html)
*/
@@ -543,7 +543,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return The number of rows changed by prior SQL statement.
- @see [sqlite3_changes()](http://sqlite.org/c3ref/changes.html)
+ @see [sqlite3_changes()](https://sqlite.org/c3ref/changes.html)
*/
@@ -560,7 +560,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
In order to iterate through the results of your query, you use a `while()` loop. You also need to "step" (via `<[FMResultSet next]>`) from one record to the other.
- This method employs [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html) for any optional value parameters. This properly escapes any characters that need escape sequences (e.g. quotation marks), which eliminates simple SQL errors as well as protects against SQL injection attacks. This method natively handles `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects. All other object types will be interpreted as text values using the object's `description` method.
+ This method employs [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html) for any optional value parameters. This properly escapes any characters that need escape sequences (e.g. quotation marks), which eliminates simple SQL errors as well as protects against SQL injection attacks. This method natively handles `NSString`, `NSNumber`, `NSNull`, `NSDate`, and `NSData` objects. All other object types will be interpreted as text values using the object's `description` method.
@param sql The SELECT statement to be performed, with optional `?` placeholders.
@@ -570,7 +570,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@see FMResultSet
@see [`FMResultSet next`](<[FMResultSet next]>)
- @see [`sqlite3_bind`](http://sqlite.org/c3ref/bind_blob.html)
+ @see [`sqlite3_bind`](https://sqlite.org/c3ref/bind_blob.html)
@note You cannot use this method from Swift due to incompatibilities between Swift and Objective-C variadic implementations. Consider using `<executeQuery:values:>` instead.
*/
@@ -677,6 +677,12 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
// Documentation forthcoming.
- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withVAList:(va_list)args;
+/// Prepare SQL statement.
+///
+/// @param sql SQL statement to prepare, generally with `?` placeholders.
+
+- (FMResultSet *)prepare:(NSString *)sql;
+
///-------------------
/// @name Transactions
///-------------------
@@ -903,7 +909,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `NSString` of the last error message.
- @see [sqlite3_errmsg()](http://sqlite.org/c3ref/errcode.html)
+ @see [sqlite3_errmsg()](https://sqlite.org/c3ref/errcode.html)
@see lastErrorCode
@see lastError
@@ -917,7 +923,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return Integer value of the last error code.
- @see [sqlite3_errcode()](http://sqlite.org/c3ref/errcode.html)
+ @see [sqlite3_errcode()](https://sqlite.org/c3ref/errcode.html)
@see lastErrorMessage
@see lastError
@@ -931,9 +937,9 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return Integer value of the last extended error code.
- @see [sqlite3_errcode()](http://sqlite.org/c3ref/errcode.html)
- @see [2. Primary Result Codes versus Extended Result Codes](http://sqlite.org/rescode.html#primary_result_codes_versus_extended_result_codes)
- @see [5. Extended Result Code List](http://sqlite.org/rescode.html#extrc)
+ @see [sqlite3_errcode()](https://sqlite.org/c3ref/errcode.html)
+ @see [2. Primary Result Codes versus Extended Result Codes](https://sqlite.org/rescode.html#primary_result_codes_versus_extended_result_codes)
+ @see [5. Extended Result Code List](https://sqlite.org/rescode.html#extrc)
@see lastErrorMessage
@see lastError
@@ -1071,16 +1077,28 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@return `NO` if and only if SQLite was compiled with mutexing code omitted due to the SQLITE_THREADSAFE compile-time option being set to 0.
- @see [sqlite3_threadsafe()](http://sqlite.org/c3ref/threadsafe.html)
+ @see [sqlite3_threadsafe()](https://sqlite.org/c3ref/threadsafe.html)
*/
+ (BOOL)isSQLiteThreadSafe;
+/** Examine/set limits
+
+ @param type The type of limit. See https://sqlite.org/c3ref/c_limit_attached.html
+ @param newLimit The new limit value. Use -1 if you don't want to change the limit, but rather only want to check it.
+
+ @return Regardless, returns previous value.
+
+ @see [sqlite3_limit()](https://sqlite.org/c3ref/limit.html)
+*/
+
+- (int)limitFor:(int)type value:(int)newLimit;
+
/** Run-time library version numbers
@return The sqlite library version string.
- @see [sqlite3_libversion()](http://sqlite.org/c3ref/libversion.html)
+ @see [sqlite3_libversion()](https://sqlite.org/c3ref/libversion.html)
*/
+ (NSString*)sqliteLibVersion;
@@ -1149,7 +1167,7 @@ typedef NS_ENUM(int, FMDBCheckpointMode) {
@param block The block of code for the function.
- @see [sqlite3_create_function()](http://sqlite.org/c3ref/create_function.html)
+ @see [sqlite3_create_function()](https://sqlite.org/c3ref/create_function.html)
*/
- (void)makeFunctionNamed:(NSString *)name arguments:(int)arguments block:(void (^)(void *context, int argc, void * _Nonnull * _Nonnull argv))block;
@@ -1410,7 +1428,7 @@ typedef NS_ENUM(int, SqliteValueType) {
- `<FMDatabase>`
- `<FMResultSet>`
- - [`sqlite3_stmt`](http://www.sqlite.org/c3ref/stmt.html)
+ - [`sqlite3_stmt`](https://sqlite.org/c3ref/stmt.html)
*/
@interface FMStatement : NSObject {
@@ -1434,7 +1452,7 @@ typedef NS_ENUM(int, SqliteValueType) {
/** SQLite sqlite3_stmt
- @see [`sqlite3_stmt`](http://www.sqlite.org/c3ref/stmt.html)
+ @see [`sqlite3_stmt`](https://sqlite.org/c3ref/stmt.html)
*/
@property (atomic, assign) void *statement;
diff --git a/src/fmdb/FMDatabase.m b/src/fmdb/FMDatabase.m
index 49fffc2..7c1eab6 100644
--- a/src/fmdb/FMDatabase.m
+++ b/src/fmdb/FMDatabase.m
@@ -8,6 +8,10 @@
#import <sqlite3.h>
#endif
+// MARK: - FMDatabase Private Extension
+
+NS_ASSUME_NONNULL_BEGIN
+
@interface FMDatabase () {
void* _db;
BOOL _isExecutingStatement;
@@ -19,15 +23,23 @@
NSDateFormatter *_dateFormat;
}
-NS_ASSUME_NONNULL_BEGIN
-
-- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray * _Nullable)arrayArgs orDictionary:(NSDictionary * _Nullable)dictionaryArgs orVAList:(va_list)args;
+- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray * _Nullable)arrayArgs orDictionary:(NSDictionary * _Nullable)dictionaryArgs orVAList:(va_list)args shouldBind:(BOOL)shouldBind;
- (BOOL)executeUpdate:(NSString *)sql error:(NSError * _Nullable __autoreleasing *)outErr withArgumentsInArray:(NSArray * _Nullable)arrayArgs orDictionary:(NSDictionary * _Nullable)dictionaryArgs orVAList:(va_list)args;
-NS_ASSUME_NONNULL_END
+@end
+
+// MARK: - FMResultSet Private Extension
+
+@interface FMResultSet ()
+
+- (int)internalStepWithError:(NSError * _Nullable __autoreleasing *)outErr;
@end
+NS_ASSUME_NONNULL_END
+
+// MARK: - FMDatabase
+
@implementation FMDatabase
// Because these two properties have all of their accessor methods implemented,
@@ -99,7 +111,7 @@ NS_ASSUME_NONNULL_END
}
+ (NSString*)FMDBUserVersion {
- return @"2.7.6";
+ return @"2.7.7";
}
+ (SInt32)FMDBVersion {
@@ -158,6 +170,10 @@ NS_ASSUME_NONNULL_END
}
+- (int)limitFor:(int)type value:(int)newLimit {
+ return sqlite3_limit(_db, type, newLimit);
+}
+
#pragma mark Open and close database
- (BOOL)open {
@@ -250,6 +266,7 @@ NS_ASSUME_NONNULL_END
while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
NSLog(@"Closing leaked statement");
sqlite3_finalize(pStmt);
+ pStmt = 0x00;
retry = YES;
}
}
@@ -619,10 +636,10 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
#pragma mark SQL manipulation
-- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
+- (int)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
if ((!obj) || ((NSNull *)obj == [NSNull null])) {
- sqlite3_bind_null(pStmt, idx);
+ return sqlite3_bind_null(pStmt, idx);
}
// FIXME - someday check the return codes on these binds.
@@ -633,62 +650,61 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
// Don't pass a NULL pointer, or sqlite will bind a SQL null instead of a blob.
bytes = "";
}
- sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
+ return sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_TRANSIENT);
}
else if ([obj isKindOfClass:[NSDate class]]) {
if (self.hasDateFormatter)
- sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String], -1, SQLITE_STATIC);
+ return sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String], -1, SQLITE_TRANSIENT);
else
- sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
+ return sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
}
else if ([obj isKindOfClass:[NSNumber class]]) {
if (strcmp([obj objCType], @encode(char)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj charValue]);
+ return sqlite3_bind_int(pStmt, idx, [obj charValue]);
}
else if (strcmp([obj objCType], @encode(unsigned char)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
+ return sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
}
else if (strcmp([obj objCType], @encode(short)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj shortValue]);
+ return sqlite3_bind_int(pStmt, idx, [obj shortValue]);
}
else if (strcmp([obj objCType], @encode(unsigned short)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
+ return sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
}
else if (strcmp([obj objCType], @encode(int)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj intValue]);
+ return sqlite3_bind_int(pStmt, idx, [obj intValue]);
}
else if (strcmp([obj objCType], @encode(unsigned int)) == 0) {
- sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
+ return sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
}
else if (strcmp([obj objCType], @encode(long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, [obj longValue]);
+ return sqlite3_bind_int64(pStmt, idx, [obj longValue]);
}
else if (strcmp([obj objCType], @encode(unsigned long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
+ return sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
}
else if (strcmp([obj objCType], @encode(long long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
+ return sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
}
else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
+ return sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
}
else if (strcmp([obj objCType], @encode(float)) == 0) {
- sqlite3_bind_double(pStmt, idx, [obj floatValue]);
+ return sqlite3_bind_double(pStmt, idx, [obj floatValue]);
}
else if (strcmp([obj objCType], @encode(double)) == 0) {
- sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
+ return sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
}
else if (strcmp([obj objCType], @encode(BOOL)) == 0) {
- sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
+ return sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
}
else {
- sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
+ return sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_TRANSIENT);
}
}
- else {
- sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
- }
+
+ return sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_TRANSIENT);
}
- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments {
@@ -814,11 +830,10 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
#pragma mark Execute queries
- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments {
- return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil];
+ return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil shouldBind:true];
}
-- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
-
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args shouldBind:(BOOL)shouldBind {
if (![self databaseExists]) {
return 0x00;
}
@@ -846,7 +861,6 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
}
if (!pStmt) {
-
rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
if (SQLITE_OK != rc) {
@@ -862,35 +876,79 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
}
sqlite3_finalize(pStmt);
+ pStmt = 0x00;
_isExecutingStatement = NO;
return nil;
}
}
+
+ if (shouldBind) {
+ BOOL success = [self bindStatement:pStmt WithArgumentsInArray:arrayArgs orDictionary:dictionaryArgs orVAList:args];
+ if (!success) {
+ return nil;
+ }
+ }
+
+ FMDBRetain(statement); // to balance the release below
+ if (!statement) {
+ statement = [[FMStatement alloc] init];
+ [statement setStatement:pStmt];
+
+ if (_shouldCacheStatements && sql) {
+ [self setCachedStatement:statement forQuery:sql];
+ }
+ }
+
+ // the statement gets closed in rs's dealloc or [rs close];
+ // we should only autoclose if we're binding automatically when the statement is prepared
+ rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self shouldAutoClose:shouldBind];
+ [rs setQuery:sql];
+
+ NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
+ [_openResultSets addObject:openResultSet];
+
+ [statement setUseCount:[statement useCount] + 1];
+
+ FMDBRelease(statement);
+
+ _isExecutingStatement = NO;
+
+ return rs;
+}
+
+- (BOOL)bindStatement:(sqlite3_stmt *)pStmt WithArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
-
+
// If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
if (dictionaryArgs) {
-
+
for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
-
+
// Prefix the key with a colon.
NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
-
+
if (_traceExecution) {
NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
}
-
+
// Get the index for the parameter name.
int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
-
+
FMDBRelease(parameterName);
-
+
if (namedIdx > 0) {
// Standard binding from here.
- [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
+ int rc = [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
+ if (rc != SQLITE_OK) {
+ NSLog(@"Error: unable to bind (%d, %s", rc, sqlite3_errmsg(_db));
+ sqlite3_finalize(pStmt);
+ pStmt = 0x00;
+ _isExecutingStatement = NO;
+ return false;
+ }
// increment the binding count, so our check below works out
idx++;
}
@@ -900,9 +958,7 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
}
}
else {
-
while (idx < queryCount) {
-
if (arrayArgs && idx < (int)[arrayArgs count]) {
obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
}
@@ -913,7 +969,7 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
//We ran out of arguments
break;
}
-
+
if (_traceExecution) {
if ([obj isKindOfClass:[NSData class]]) {
NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
@@ -922,52 +978,36 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
NSLog(@"obj: %@", obj);
}
}
-
+
idx++;
-
- [self bindObject:obj toColumn:idx inStatement:pStmt];
+
+ int rc = [self bindObject:obj toColumn:idx inStatement:pStmt];
+ if (rc != SQLITE_OK) {
+ NSLog(@"Error: unable to bind (%d, %s", rc, sqlite3_errmsg(_db));
+ sqlite3_finalize(pStmt);
+ pStmt = 0x00;
+ _isExecutingStatement = NO;
+ return false;
+ }
}
}
-
+
if (idx != queryCount) {
NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
+ pStmt = 0x00;
_isExecutingStatement = NO;
- return nil;
+ return false;
}
-
- FMDBRetain(statement); // to balance the release below
-
- if (!statement) {
- statement = [[FMStatement alloc] init];
- [statement setStatement:pStmt];
-
- if (_shouldCacheStatements && sql) {
- [self setCachedStatement:statement forQuery:sql];
- }
- }
-
- // the statement gets closed in rs's dealloc or [rs close];
- rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
- [rs setQuery:sql];
-
- NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
- [_openResultSets addObject:openResultSet];
-
- [statement setUseCount:[statement useCount] + 1];
-
- FMDBRelease(statement);
-
- _isExecutingStatement = NO;
-
- return rs;
+
+ return true;
}
- (FMResultSet *)executeQuery:(NSString*)sql, ... {
va_list args;
va_start(args, sql);
- id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args];
+ id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args shouldBind:true];
va_end(args);
return result;
@@ -987,11 +1027,11 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
}
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments {
- return [self executeQuery:sql withArgumentsInArray:arguments orDictionary:nil orVAList:nil];
+ return [self executeQuery:sql withArgumentsInArray:arguments orDictionary:nil orVAList:nil shouldBind:true];
}
- (FMResultSet *)executeQuery:(NSString *)sql values:(NSArray *)values error:(NSError * __autoreleasing *)error {
- FMResultSet *rs = [self executeQuery:sql withArgumentsInArray:values orDictionary:nil orVAList:nil];
+ FMResultSet *rs = [self executeQuery:sql withArgumentsInArray:values orDictionary:nil orVAList:nil shouldBind:true];
if (!rs && error) {
*error = [self lastError];
}
@@ -999,234 +1039,22 @@ static int FMDBDatabaseBusyHandler(void *f, int count) {
}
- (FMResultSet *)executeQuery:(NSString*)sql withVAList:(va_list)args {
- return [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args];
+ return [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args shouldBind:true];
}
#pragma mark Execute updates
- (BOOL)executeUpdate:(NSString*)sql error:(NSError * _Nullable __autoreleasing *)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
-
- if (![self databaseExists]) {
- return NO;
- }
-
- if (_isExecutingStatement) {
- [self warnInUse];
- return NO;
- }
-
- _isExecutingStatement = YES;
-
- int rc = 0x00;
- sqlite3_stmt *pStmt = 0x00;
- FMStatement *cachedStmt = 0x00;
-
- if (_traceExecution && sql) {
- NSLog(@"%@ executeUpdate: %@", self, sql);
- }
-
- if (_shouldCacheStatements) {
- cachedStmt = [self cachedStatementForQuery:sql];
- pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
- [cachedStmt reset];
- }
-
- if (!pStmt) {
- rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
-
- if (SQLITE_OK != rc) {
- if (_logsErrors) {
- NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
- NSLog(@"DB Query: %@", sql);
- NSLog(@"DB Path: %@", _databasePath);
- }
-
- if (_crashOnErrors) {
- NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
- abort();
- }
-
- if (outErr) {
- *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
- }
-
- sqlite3_finalize(pStmt);
-
- _isExecutingStatement = NO;
- return NO;
- }
- }
-
- id obj;
- int idx = 0;
- int queryCount = sqlite3_bind_parameter_count(pStmt);
-
- // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
- if (dictionaryArgs) {
-
- for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
-
- // Prefix the key with a colon.
- NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
-
- if (_traceExecution) {
- NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
- }
- // Get the index for the parameter name.
- int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
-
- FMDBRelease(parameterName);
-
- if (namedIdx > 0) {
- // Standard binding from here.
- [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
-
- // increment the binding count, so our check below works out
- idx++;
- }
- else {
- NSString *message = [NSString stringWithFormat:@"Could not find index for %@", dictionaryKey];
-
- if (_logsErrors) {
- NSLog(@"%@", message);
- }
- if (outErr) {
- *outErr = [self errorWithMessage:message];
- }
- }
- }
- }
- else {
-
- while (idx < queryCount) {
-
- if (arrayArgs && idx < (int)[arrayArgs count]) {
- obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
- }
- else if (args) {
- obj = va_arg(args, id);
- }
- else {
- //We ran out of arguments
- break;
- }
-
- if (_traceExecution) {
- if ([obj isKindOfClass:[NSData class]]) {
- NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
- }
- else {
- NSLog(@"obj: %@", obj);
- }
- }
-
- idx++;
-
- [self bindObject:obj toColumn:idx inStatement:pStmt];
- }
- }
-
-
- if (idx != queryCount) {
- NSString *message = [NSString stringWithFormat:@"Error: the bind count (%d) is not correct for the # of variables in the query (%d) (%@) (executeUpdate)", idx, queryCount, sql];
- if (_logsErrors) {
- NSLog(@"%@", message);
- }
+ FMResultSet *rs = [self executeQuery:sql withArgumentsInArray:arrayArgs orDictionary:dictionaryArgs orVAList:args shouldBind:true];
+ if (!rs) {
if (outErr) {
- *outErr = [self errorWithMessage:message];
+ *outErr = [self lastError];
}
-
- sqlite3_finalize(pStmt);
- _isExecutingStatement = NO;
- return NO;
+ return false;
}
-
- /* Call sqlite3_step() to run the virtual machine. Since the SQL being
- ** executed is not a SELECT statement, we assume no data will be returned.
- */
-
- rc = sqlite3_step(pStmt);
-
- if (SQLITE_DONE == rc) {
- // all is well, let's return.
- }
- else if (SQLITE_INTERRUPT == rc) {
- if (_logsErrors) {
- NSLog(@"Error calling sqlite3_step. Query was interrupted (%d: %s) SQLITE_INTERRUPT", rc, sqlite3_errmsg(_db));
- NSLog(@"DB Query: %@", sql);
- }
- }
- else if (rc == SQLITE_ROW) {
- NSString *message = [NSString stringWithFormat:@"A executeUpdate is being called with a query string '%@'", sql];
- if (_logsErrors) {
- NSLog(@"%@", message);
- NSLog(@"DB Query: %@", sql);
- }
- if (outErr) {
- *outErr = [self errorWithMessage:message];
- }
- }
- else {
- if (outErr) {
- *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
- }
-
- if (SQLITE_ERROR == rc) {
- if (_logsErrors) {
- NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
- NSLog(@"DB Query: %@", sql);
- }
- }
- else if (SQLITE_MISUSE == rc) {
- // uh oh.
- if (_logsErrors) {
- NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
- NSLog(@"DB Query: %@", sql);
- }
- }
- else {
- // wtf?
- if (_logsErrors) {
- NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
- NSLog(@"DB Query: %@", sql);
- }
- }
- }
-
- if (_shouldCacheStatements && !cachedStmt) {
- cachedStmt = [[FMStatement alloc] init];
-
- [cachedStmt setStatement:pStmt];
-
- [self setCachedStatement:cachedStmt forQuery:sql];
-
- FMDBRelease(cachedStmt);
- }
-
- int closeErrorCode;
-
- if (cachedStmt) {
- [cachedStmt setUseCount:[cachedStmt useCount] + 1];
- closeErrorCode = sqlite3_reset(pStmt);
- }
- else {
- /* Finalize the virtual machine. This releases all memory and other
- ** resources allocated by the sqlite3_prepare() call above.
- */
- closeErrorCode = sqlite3_finalize(pStmt);
- }
-
- if (closeErrorCode != SQLITE_OK) {
- if (_logsErrors) {
- NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db));
- NSLog(@"DB Query: %@", sql);
- }
- }
-
- _isExecutingStatement = NO;
- return (rc == SQLITE_DONE || rc == SQLITE_OK);
-}
+ return [rs internalStepWithError:outErr] == SQLITE_DONE;
+}
- (BOOL)executeUpdate:(NSString*)sql, ... {
va_list args;
@@ -1337,6 +1165,12 @@ int FMDBExecuteBulkSQLCallback(void *theBlockAsVoid, int columns, char **values,
#pragma clang diagnostic pop
+#pragma mark Prepare
+
+- (FMResultSet *)prepare:(NSString *)sql {
+ return [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:nil shouldBind:false];
+}
+
#pragma mark Transactions
- (BOOL)rollback {
@@ -1650,7 +1484,7 @@ void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3
@end
-
+// MARK: - FMStatement
@implementation FMStatement
diff --git a/src/fmdb/FMDatabaseAdditions.h b/src/fmdb/FMDatabaseAdditions.h
index b890c93..2c5467b 100644
--- a/src/fmdb/FMDatabaseAdditions.h
+++ b/src/fmdb/FMDatabaseAdditions.h
@@ -139,7 +139,7 @@ NS_ASSUME_NONNULL_BEGIN
@return `FMResultSet` of schema; `nil` on error.
- @see [SQLite File Format](http://www.sqlite.org/fileformat.html)
+ @see [SQLite File Format](https://sqlite.org/fileformat.html)
*/
- (FMResultSet * _Nullable)getSchema;
@@ -163,7 +163,7 @@ NS_ASSUME_NONNULL_BEGIN
@return `FMResultSet` of schema; `nil` on error.
- @see [table_info](http://www.sqlite.org/pragma.html#pragma_table_info)
+ @see [table_info](https://sqlite.org/pragma.html#pragma_table_info)
*/
- (FMResultSet * _Nullable)getTableSchema:(NSString*)tableName;
diff --git a/src/fmdb/FMDatabaseAdditions.m b/src/fmdb/FMDatabaseAdditions.m
index 24a0ebf..2289c13 100644
--- a/src/fmdb/FMDatabaseAdditions.m
+++ b/src/fmdb/FMDatabaseAdditions.m
@@ -17,7 +17,7 @@
#endif
@interface FMDatabase (PrivateStuff)
-- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray * _Nullable)arrayArgs orDictionary:(NSDictionary * _Nullable)dictionaryArgs orVAList:(va_list)args;
+- (FMResultSet * _Nullable)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray * _Nullable)arrayArgs orDictionary:(NSDictionary * _Nullable)dictionaryArgs orVAList:(va_list)args shouldBind:(BOOL)shouldBind;
@end
@implementation FMDatabase (FMDatabaseAdditions)
@@ -25,7 +25,7 @@
#define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \
va_list args; \
va_start(args, query); \
-FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \
+FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args shouldBind:true]; \
va_end(args); \
if (![resultSet next]) { return (type)0; } \
type ret = [resultSet sel:0]; \
diff --git a/src/fmdb/FMResultSet.h b/src/fmdb/FMResultSet.h
index b765b7a..b48dfd6 100644
--- a/src/fmdb/FMResultSet.h
+++ b/src/fmdb/FMResultSet.h
@@ -57,7 +57,7 @@ NS_ASSUME_NONNULL_BEGIN
@return A `FMResultSet` on success; `nil` on failure
*/
-+ (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB;
++ (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB shouldAutoClose:(BOOL)shouldAutoClose;
/** Close result set */
@@ -91,10 +91,30 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)nextWithError:(NSError * _Nullable __autoreleasing *)outErr;
+/** Perform SQL statement.
+
+ @return 'YES' if successful; 'NO' if not.
+
+ @see hasAnotherRow
+*/
+
+- (BOOL)step;
+
+/** Perform SQL statement.
+
+ @param outErr A 'NSError' object to receive any error object (if any).
+
+ @return 'YES' if successful; 'NO' if not.
+
+ @see hasAnotherRow
+*/
+
+- (BOOL)stepWithError:(NSError * _Nullable __autoreleasing *)outErr;
+
/** Did the last call to `<next>` succeed in retrieving another row?
- @return `YES` if the last call to `<next>` succeeded in retrieving another record; `NO` if not.
-
+ @return 'YES' if there is another row; 'NO' if not.
+
@see next
@warning The `hasAnotherRow` method must follow a call to `<next>`. If the previous database interaction was something other than a call to `next`, then this method may return `NO`, whether there is another row of data or not.
@@ -461,7 +481,22 @@ If you don't, you're going to be in a world of hurt when you try and use the dat
- (void)kvcMagic:(id)object;
-
+///-----------------------------
+/// @name Binding values
+///-----------------------------
+
+/// Bind array of values to prepared statement.
+///
+/// @param array Array of values to bind to SQL statement.
+
+- (BOOL)bindWithArray:(NSArray*)array;
+
+/// Bind dictionary of values to prepared statement.
+///
+/// @param dictionary Dictionary of values to bind to SQL statement.
+
+- (BOOL)bindWithDictionary:(NSDictionary *)dictionary;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/src/fmdb/FMResultSet.m b/src/fmdb/FMResultSet.m
index 4cb6525..1f32473 100644
--- a/src/fmdb/FMResultSet.m
+++ b/src/fmdb/FMResultSet.m
@@ -8,23 +8,31 @@
#import <sqlite3.h>
#endif
+// MARK: - FMDatabase Private Extension
+
@interface FMDatabase ()
- (void)resultSetDidClose:(FMResultSet *)resultSet;
+- (BOOL)bindStatement:(sqlite3_stmt *)pStmt WithArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
@end
+// MARK: - FMResultSet Private Extension
+
@interface FMResultSet () {
NSMutableDictionary *_columnNameToIndexMap;
}
+@property (nonatomic) BOOL shouldAutoClose;
@end
+// MARK: - FMResultSet
+
@implementation FMResultSet
-+ (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB {
-
++ (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB shouldAutoClose:(BOOL)shouldAutoClose {
FMResultSet *rs = [[FMResultSet alloc] init];
[rs setStatement:statement];
[rs setParentDB:aDB];
+ [rs setShouldAutoClose:shouldAutoClose];
NSParameterAssert(![statement inUse]);
[statement setInUse:YES]; // weak reference
@@ -153,15 +161,25 @@
return nil;
}
-
-
-
- (BOOL)next {
return [self nextWithError:nil];
}
- (BOOL)nextWithError:(NSError * _Nullable __autoreleasing *)outErr {
-
+ int rc = [self internalStepWithError:outErr];
+ return rc == SQLITE_ROW;
+}
+
+- (BOOL)step {
+ return [self stepWithError:nil];
+}
+
+- (BOOL)stepWithError:(NSError * _Nullable __autoreleasing *)outErr {
+ int rc = [self internalStepWithError:outErr];
+ return rc == SQLITE_DONE;
+}
+
+- (int)internalStepWithError:(NSError * _Nullable __autoreleasing *)outErr {
int rc = sqlite3_step([_statement statement]);
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
@@ -203,13 +221,12 @@
*outErr = [_parentDB lastError];
}
}
-
-
- if (rc != SQLITE_ROW) {
+
+ if (rc != SQLITE_ROW && _shouldAutoClose) {
[self close];
}
- return (rc == SQLITE_ROW);
+ return rc;
}
- (BOOL)hasAnotherRow {
@@ -428,5 +445,19 @@
return [self objectForColumn:columnName];
}
+// MARK: Bind
+
+- (BOOL)bindWithArray:(NSArray*)array orDictionary:(NSDictionary *)dictionary orVAList:(va_list)args {
+ [_statement reset];
+ return [_parentDB bindStatement:_statement.statement WithArgumentsInArray:array orDictionary:dictionary orVAList:args];
+}
+
+- (BOOL)bindWithArray:(NSArray*)array {
+ return [self bindWithArray:array orDictionary:nil orVAList:nil];
+}
+
+- (BOOL)bindWithDictionary:(NSDictionary *)dictionary {
+ return [self bindWithArray:nil orDictionary:dictionary orVAList:nil];
+}
@end