소스 검색

Merge branch 'master' of git://github.com/robertmryan/fmdb into robertmryan-master

August Mueller 11 년 전
부모
커밋
5cd73023f1
8개의 변경된 파일335개의 추가작업 그리고 64개의 파일을 삭제
  1. 2 2
      CHANGES_AND_TODO_LIST.txt
  2. 39 4
      Tests/FMDatabaseTests.m
  3. 90 20
      src/fmdb/FMDatabase.h
  4. 122 29
      src/fmdb/FMDatabase.m
  5. 1 1
      src/fmdb/FMDatabaseAdditions.h
  6. 35 4
      src/fmdb/FMDatabasePool.h
  7. 5 0
      src/fmdb/FMDatabaseQueue.h
  8. 41 4
      src/sample/main.m

+ 2 - 2
CHANGES_AND_TODO_LIST.txt

@@ -41,11 +41,11 @@ If you would like to contribute some code- awesome!  I just ask that you make it
 
         - To build that HTML documentation, once you've installed `appledoc`, you issue the command:
 
-             appledoc --project-name FMDB --project-company ccgus --explicit-crossref --no-merge-categories --output ../Documentation .
+             appledoc --project-name FMDB --project-company ccgus --explicit-crossref --no-merge-categories --output ../Documentation --ignore *.m .
 
         - If you want online help integrated right into Xcode, you can issue the command:
 
-             appledoc --project-name FMDB --project-company ccgus --explicit-crossref --merge-categories --install-docset --output ../Documentation .
+             appledoc --project-name FMDB --project-company ccgus --explicit-crossref --merge-categories --install-docset --output --ignore *.m ../Documentation .
 
 2013.05.24
     Merged in Chris Wright's date format additions to FMDatabase.

+ 39 - 4
Tests/FMDatabaseTests.m

@@ -818,19 +818,54 @@ - (void)testApplicationID
 {
     uint32_t appID = NSHFSTypeCodeFromFileType(NSFileTypeForHFSTypeCode('fmdb'));
     
-    [db setApplicationID:appID];
+    [self.db setApplicationID:appID];
     
-    uint32_t rAppID = [db applicationID];
+    uint32_t rAppID = [self.db applicationID];
     
     XCTAssertEqual(rAppID, appID);
     
-    [db setApplicationIDString:@"acrn"];
+    [self.db setApplicationIDString:@"acrn"];
     
-    NSString *s = [db applicationIDString];
+    NSString *s = [self.db applicationIDString];
     
     XCTAssertEqualObjects(s, @"acrn");
 }
 #endif
 
+- (void)testBulkSQL
+{
+    BOOL success;
+
+    NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
+                     "create table bulktest2 (id integer primary key autoincrement, y text);"
+                     "create table bulktest3 (id integer primary key autoincrement, z text);"
+                     "insert into bulktest1 (x) values ('XXX');"
+                     "insert into bulktest2 (y) values ('YYY');"
+                     "insert into bulktest3 (z) values ('ZZZ');";
+
+    success = [self.db executeBulkSQL:sql];
+
+    XCTAssertTrue(success, @"bulk create");
+
+    sql = @"select count(*) as count from bulktest1;"
+           "select count(*) as count from bulktest2;"
+           "select count(*) as count from bulktest3;";
+
+    success = [self.db executeBulkSQL:sql block:^int(NSDictionary *dictionary) {
+        NSInteger count = [dictionary[@"count"] integerValue];
+        XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
+        return 0;
+    }];
+
+    XCTAssertTrue(success, @"bulk select");
+
+    sql = @"drop table bulktest1;"
+           "drop table bulktest2;"
+           "drop table bulktest3;";
+
+    success = [self.db executeBulkSQL:sql];
+
+    XCTAssertTrue(success, @"bulk drop");
+}
 
 @end

+ 90 - 20
src/fmdb/FMDatabase.h

@@ -38,6 +38,10 @@
     #define instancetype id
 #endif
 
+
+typedef int(^FMDBExecuteBulkSQLCallbackBlock)(NSDictionary *resultsDictionary);
+
+
 /** A SQLite ([http://sqlite.org/](http://sqlite.org/)) Objective-C wrapper.
  
  ### Usage
@@ -241,9 +245,11 @@
 /// @name Perform updates
 ///----------------------
 
-/** Execute update statement
+/** Execute single update statement
  
- 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 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.
+
+ 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.
 
  @param sql The SQL to be performed, with optional `?` placeholders.
  
@@ -261,9 +267,11 @@
 
 - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...;
 
-/** Execute update statement
+/** Execute single update statement
 
- 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 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.
+
+ 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.
  
  @param sql The SQL to be performed, with optional `?` placeholders.
 
@@ -275,14 +283,16 @@
  @see lastErrorCode
  @see lastErrorMessage
  @see [`sqlite3_bind`](http://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.
  */
 
 - (BOOL)executeUpdate:(NSString*)sql, ...;
 
-/** Execute update statement
+/** 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.
 
- Any sort of SQL statement which is not a `SELECT` statement qualifies as an update.  This includes `CREATE`, `UPDATE`, `INSERT`, `ALTER`, `COMMIT`, `BEGIN`, `DETACH`, `DELETE`, `DROP`, `END`, `EXPLAIN`, `VACUUM`, and `REPLACE` statements (plus many more).  Basically, if your SQL statement does not begin with `SELECT`, it is an update statement.
- 
  @param format The SQL to be performed, with `printf`-style escape sequences.
 
  @param ... Optional parameters to bind to use in conjunction with the `printf`-style escape sequences in the SQL statement.
@@ -299,9 +309,11 @@
 
 - (BOOL)executeUpdateWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
 
-/** Execute update statement
+/** 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.
 
- Any sort of SQL statement which is not a `SELECT` statement qualifies as an update.  This includes `CREATE`, `UPDATE`, `INSERT`, `ALTER`, `COMMIT`, `BEGIN`, `DETACH`, `DELETE`, `DROP`, `END`, `EXPLAIN`, `VACUUM`, and `REPLACE` statements (plus many more).  Basically, if your SQL statement does not begin with `SELECT`, it is an update statement.
+ 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.
 
  @param sql The SQL to be performed, with optional `?` placeholders.
 
@@ -316,9 +328,11 @@
 
 - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
 
-/** Execute update statement
+/** 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.
 
- Any sort of SQL statement which is not a `SELECT` statement qualifies as an update.  This includes `CREATE`, `UPDATE`, `INSERT`, `ALTER`, `COMMIT`, `BEGIN`, `DETACH`, `DELETE`, `DROP`, `END`, `EXPLAIN`, `VACUUM`, and `REPLACE` statements (plus many more).  Basically, if your SQL statement does not begin with `SELECT`, it is an update statement.
+ 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.
 
  @param sql The SQL to be performed, with optional `?` placeholders.
 
@@ -334,10 +348,60 @@
 - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
 
 
+/** 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.
+
+ 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.
+
+ @param sql The SQL to be performed, with optional `?` placeholders.
+
+ @param args A `va_list` of arguments.
+
+ @return `YES` upon success; `NO` upon failure. If failed, you can call `<lastError>`, `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
+
+ @see lastError
+ @see lastErrorCode
+ @see lastErrorMessage
+ */
 
-// Documentation forthcoming.
 - (BOOL)executeUpdate:(NSString*)sql withVAList: (va_list)args;
 
+/** Execute multiple SQL statements
+ 
+ This executes a series of SQL statements that are combined in a single string (e.g. the SQL generated by the `sqlite3` command line `.dump` command). This accepts no value parameters, but rather simply expects a single string with multiple SQL statements, each terminated with a semicolon. This uses `sqlite3_exec`. 
+
+ @param  sql  The SQL to be performed
+ 
+ @return      `YES` upon success; `NO` upon failure. If failed, you can call `<lastError>`, `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
+
+ @see executeBulkSQL:block:
+ @see [sqlite3_exec()](http://sqlite.org/c3ref/exec.html)
+
+ */
+
+- (BOOL)executeBulkSQL:(NSString *)sql;
+
+/** Execute multiple SQL statements with callback handler
+ 
+ This executes a series of SQL statements that are combined in a single string (e.g. the SQL generated by the `sqlite3` command line `.dump` command). This accepts no value parameters, but rather simply expects a single string with multiple SQL statements, each terminated with a semicolon. This uses `sqlite3_exec`.
+
+ @param sql       The SQL to be performed.
+ @param block     A block that will be called for any result sets returned by any SQL statements. 
+                  Note, if you supply this block, it must return integer value, zero upon success,
+                  non-zero value upon failure (which will stop the bulk execution of the SQL. This block
+                  takes two parameters, the `void *userInfo` and a `NSDictionary *resultsDictionary`. 
+                  This may be `nil`.
+
+ @return          `YES` upon success; `NO` upon failure. If failed, you can call `<lastError>`,
+                  `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
+ 
+ @see executeBulkSQL:
+ @see [sqlite3_exec()](http://sqlite.org/c3ref/exec.html)
+
+ */
+
+- (BOOL)executeBulkSQL:(NSString *)sql block:(FMDBExecuteBulkSQLCallbackBlock)block;
 
 /** Last insert rowid
  
@@ -345,6 +409,8 @@
  
  This routine returns the rowid of the most recent successful `INSERT` into the database from the database connection in the first argument. As of SQLite version 3.7.7, this routines records the last insert rowid of both ordinary tables and virtual tables. If no successful `INSERT`s have ever occurred on that database connection, zero is returned.
  
+ @return The rowid of the last inserted row.
+ 
  @see [sqlite3_last_insert_rowid()](http://sqlite.org/c3ref/last_insert_rowid.html)
 
  */
@@ -355,6 +421,8 @@
  
  This function returns the number of database rows that were changed or inserted or deleted by the most recently completed SQL statement on the database connection specified by the first parameter. Only changes that are directly specified by the INSERT, UPDATE, or DELETE statement are counted.
  
+ @return The number of rows changed by prior SQL statement.
+ 
  @see [sqlite3_changes()](http://sqlite.org/c3ref/changes.html)
  
  */
@@ -449,8 +517,6 @@
 // Documentation forthcoming.
 - (FMResultSet *)executeQuery:(NSString*)sql withVAList: (va_list)args;
 
-
-
 ///-------------------
 /// @name Transactions
 ///-------------------
@@ -640,7 +706,7 @@
  
  Returns the English-language text that describes the most recent failed SQLite API call associated with a database connection. If a prior API call failed but the most recent API call succeeded, this return value is undefined.
 
- @returns `NSString` of the last error message.
+ @return `NSString` of the last error message.
  
  @see [sqlite3_errmsg()](http://sqlite.org/c3ref/errcode.html)
  @see lastErrorCode
@@ -654,7 +720,7 @@
  
  Returns the numeric result code or extended result code for the most recent failed SQLite API call associated with a database connection. If a prior API call failed but the most recent API call succeeded, this return value is undefined.
 
- @returns Integer value of the last error code.
+ @return Integer value of the last error code.
 
  @see [sqlite3_errcode()](http://sqlite.org/c3ref/errcode.html)
  @see lastErrorMessage
@@ -746,7 +812,7 @@
 
  @param block Block of code to perform from within save point.
  
- @return `YES` on success; `NO` on failure. If failed, you can call `<lastError>`, `<lastErrorCode>`, or `<lastErrorMessage>` for diagnostic information regarding the failure.
+ @return The NSError corresponding to the error, if any. If no error, returns `nil`.
 
  @see startSavePointWithName:error:
  @see releaseSavePointWithName:error:
@@ -764,7 +830,7 @@
 
 /** Test to see if the library is threadsafe
 
- @return Zero if and only if SQLite was compiled with mutexing code omitted due to the SQLITE_THREADSAFE compile-time option being set to 0.
+ @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)
  */
@@ -773,6 +839,8 @@
 
 /** Run-time library version numbers
  
+ @return The sqlite library version string.
+ 
  @see [sqlite3_libversion()](http://sqlite.org/c3ref/libversion.html)
  */
 
@@ -890,7 +958,7 @@
  
  @param s `NSString` to convert to `NSDate`.
  
- @return `nil` if no formatter is set.
+ @return The `NSDate` object; or `nil` if no formatter is set.
  
  @see hasDateFormatter
  @see setDateFormat:
@@ -905,7 +973,7 @@
  
  @param date `NSDate` of date to convert to `NSString`.
 
- @return `nil` if no formatter is set.
+ @return The `NSString` representation of the date; `nil` if no formatter is set.
  
  @see hasDateFormatter
  @see setDateFormat:
@@ -956,6 +1024,8 @@
 
 @property (atomic, assign) sqlite3_stmt *statement;
 
+/** Indication of whether the statement is in use */
+
 @property (atomic, assign) BOOL inUse;
 
 ///----------------------------

+ 122 - 29
src/fmdb/FMDatabase.m

@@ -2,10 +2,14 @@
 #import "unistd.h"
 #import <objc/runtime.h>
 
+
+static FMDBExecuteBulkSQLCallbackBlock execCallbackBlock;
+
 @interface FMDatabase ()
 
 - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
 - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
+
 @end
 
 @implementation FMDatabase
@@ -15,19 +19,12 @@ @implementation FMDatabase
 @synthesize checkedOut=_checkedOut;
 @synthesize traceExecution=_traceExecution;
 
+#pragma mark FMDatabase instantiation and deallocation
+
 + (instancetype)databaseWithPath:(NSString*)aPath {
     return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
 }
 
-+ (NSString*)sqliteLibVersion {
-    return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
-}
-
-+ (BOOL)isSQLiteThreadSafe {
-    // make sure to read the sqlite headers on this guy!
-    return sqlite3_threadsafe() != 0;
-}
-
 - (instancetype)init {
     return [self initWithPath:nil];
 }
@@ -45,6 +42,7 @@ - (instancetype)initWithPath:(NSString*)aPath {
         _logsErrors                 = YES;
         _crashOnErrors              = NO;
         _maxBusyRetryTimeInterval   = 2;
+        execCallbackBlock           = nil;
     }
     
     return self;
@@ -72,6 +70,17 @@ - (NSString *)databasePath {
     return _databasePath;
 }
 
+#pragma mark SQLite information
+
++ (NSString*)sqliteLibVersion {
+    return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
+}
+
++ (BOOL)isSQLiteThreadSafe {
+    // make sure to read the sqlite headers on this guy!
+    return sqlite3_threadsafe() != 0;
+}
+
 - (sqlite3*)sqliteHandle {
     return _db;
 }
@@ -90,6 +99,8 @@ - (const char*)sqlitePath {
     
 }
 
+#pragma mark Open and close database
+
 - (BOOL)open {
     if (_db) {
         return YES;
@@ -169,9 +180,19 @@ - (BOOL)close {
     return YES;
 }
 
+#pragma mark Busy handler routines
 
-static int FMDatabaseBusyHandler(void *f, int count) {
-    
+// NOTE: appledoc seems to choke on this function for some reason;
+//       so when generating documentation, you might want to ignore the
+//       .m files so that it only documents the public interfaces outlined
+//       in the .h files.
+//
+//       This is a known appledoc bug that it has problems with C functions
+//       within a class implementation, but for some reason, only this
+//       C function causes problems; the rest don't. Anyway, ignoring the .m
+//       files with appledoc will prevent this problem from occurring.
+
+static int FMDBDatabaseBusyHandler(void *f, int count) {
     FMDatabase *self = (__bridge FMDatabase*)f;
     
     if (count == 0) {
@@ -198,7 +219,7 @@ - (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout {
     }
     
     if (timeout > 0) {
-        sqlite3_busy_handler(_db, &FMDatabaseBusyHandler, (__bridge void *)(self));
+        sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self));
     }
     else {
         // turn it off otherwise
@@ -225,17 +246,7 @@ - (void)setBusyRetryTimeout:(int)i {
     NSLog(@"FMDB: setBusyRetryTimeout does nothing, please use setRetryTimeout:");
 }
 
-
-
-
-- (void)clearCachedStatements {
-    
-    for (NSMutableSet *statements in [_cachedStatements objectEnumerator]) {
-        [statements makeObjectsPerformSelector:@selector(close)];
-    }
-    
-    [_cachedStatements removeAllObjects];
-}
+#pragma mark Result set functions
 
 - (BOOL)hasOpenResultSets {
     return [_openResultSets count] > 0;
@@ -261,6 +272,17 @@ - (void)resultSetDidClose:(FMResultSet *)resultSet {
     [_openResultSets removeObject:setValue];
 }
 
+#pragma mark Cached statements
+
+- (void)clearCachedStatements {
+    
+    for (NSMutableSet *statements in [_cachedStatements objectEnumerator]) {
+        [statements makeObjectsPerformSelector:@selector(close)];
+    }
+    
+    [_cachedStatements removeAllObjects];
+}
+
 - (FMStatement*)cachedStatementForQuery:(NSString*)query {
     
     NSMutableSet* statements = [_cachedStatements objectForKey:query];
@@ -291,6 +313,8 @@ - (void)setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query {
     FMDBRelease(query);
 }
 
+#pragma mark Key routines
+
 - (BOOL)rekey:(NSString*)key {
     NSData *keyData = [NSData dataWithBytes:(void *)[key UTF8String] length:(NSUInteger)strlen([key UTF8String])];
     
@@ -336,6 +360,8 @@ - (BOOL)setKeyWithData:(NSData *)keyData {
 #endif
 }
 
+#pragma mark Date routines
+
 + (NSDateFormatter *)storeableDateFormat:(NSString *)format {
     
     NSDateFormatter *result = FMDBReturnAutoreleased([[NSDateFormatter alloc] init]);
@@ -363,6 +389,7 @@ - (NSString *)stringFromDate:(NSDate *)date {
     return [_dateFormat stringFromDate:date];
 }
 
+#pragma mark State of database
 
 - (BOOL)goodConnection {
     
@@ -410,6 +437,8 @@ - (BOOL)databaseExists {
     return YES;
 }
 
+#pragma mark Error routines
+
 - (NSString*)lastErrorMessage {
     return [NSString stringWithUTF8String:sqlite3_errmsg(_db)];
 }
@@ -424,7 +453,6 @@ - (int)lastErrorCode {
     return sqlite3_errcode(_db);
 }
 
-
 - (NSError*)errorWithMessage:(NSString*)message {
     NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey];
     
@@ -435,6 +463,8 @@ - (NSError*)lastError {
    return [self errorWithMessage:[self lastErrorMessage]];
 }
 
+#pragma mark Update information routines
+
 - (sqlite_int64)lastInsertRowId {
     
     if (_isExecutingStatement) {
@@ -466,6 +496,8 @@ - (int)changes {
     return ret;
 }
 
+#pragma mark SQL manipulation
+
 - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
     
     if ((!obj) || ((NSNull *)obj == [NSNull null])) {
@@ -658,6 +690,8 @@ - (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMut
     }
 }
 
+#pragma mark Execute queries
+
 - (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments {
     return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil];
 }
@@ -835,6 +869,8 @@ - (FMResultSet *)executeQuery:(NSString*)sql withVAList:(va_list)args {
     return [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args];
 }
 
+#pragma mark Execute updates
+
 - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
     
     if (![self databaseExists]) {
@@ -1060,6 +1096,59 @@ - (BOOL)executeUpdateWithFormat:(NSString*)format, ... {
     return [self executeUpdate:sql withArgumentsInArray:arguments];
 }
 
+int FMDBExecuteBulkSQLCallback(void *userInfo, int columns, char **values, char**names)
+{
+    if (!execCallbackBlock) {
+        return 0;
+    }
+
+    NSString *key;
+    id        value;
+
+    NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:columns];
+    for (NSInteger i = 0; i < columns; i++) {
+        key = [NSString stringWithUTF8String:names[i]];
+
+        if (values[i] == NULL)
+            value = [NSNull null];
+        else
+            value = [NSString stringWithUTF8String:values[i]];
+
+        [dictionary setObject:value forKey:key];
+    }
+
+    return execCallbackBlock(dictionary);
+}
+
+- (BOOL)executeBulkSQL:(NSString *)sql
+{
+    return [self executeBulkSQL:sql block:nil];
+}
+
+- (BOOL)executeBulkSQL:(NSString *)sql block:(FMDBExecuteBulkSQLCallbackBlock)block
+{
+    int rc;
+
+    if (execCallbackBlock) {
+        if (_logsErrors) {
+            NSLog(@"Currently already executing sqlite3_exec");
+        }
+        return NO;
+    }
+
+    execCallbackBlock = block;
+
+    if (execCallbackBlock) {
+        rc = sqlite3_exec(self.sqliteHandle, [sql UTF8String], FMDBExecuteBulkSQLCallback, NULL, NULL);
+    } else {
+        rc = sqlite3_exec(self.sqliteHandle, [sql UTF8String], NULL, NULL, NULL);
+    }
+
+    execCallbackBlock = nil;
+
+    return (rc == SQLITE_OK);
+}
+
 - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ... {
     va_list args;
     va_start(args, outErr);
@@ -1070,6 +1159,8 @@ - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ... {
     return result;
 }
 
+#pragma mark Transactions
+
 - (BOOL)rollback {
     BOOL b = [self executeUpdate:@"rollback transaction"];
     
@@ -1116,7 +1207,7 @@ - (BOOL)inTransaction {
 
 #if SQLITE_VERSION_NUMBER >= 3007000
 
-static NSString *FMEscapeSavePointName(NSString *savepointName) {
+static NSString *FMDBEscapeSavePointName(NSString *savepointName) {
     return [savepointName stringByReplacingOccurrencesOfString:@"'" withString:@"''"];
 }
 
@@ -1124,7 +1215,7 @@ - (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr {
     
     NSParameterAssert(name);
     
-    NSString *sql = [NSString stringWithFormat:@"savepoint '%@';", FMEscapeSavePointName(name)];
+    NSString *sql = [NSString stringWithFormat:@"savepoint '%@';", FMDBEscapeSavePointName(name)];
     
     if (![self executeUpdate:sql]) {
 
@@ -1142,7 +1233,7 @@ - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr {
     
     NSParameterAssert(name);
     
-    NSString *sql = [NSString stringWithFormat:@"release savepoint '%@';", FMEscapeSavePointName(name)];
+    NSString *sql = [NSString stringWithFormat:@"release savepoint '%@';", FMDBEscapeSavePointName(name)];
     BOOL worked = [self executeUpdate:sql];
     
     if (!worked && outErr) {
@@ -1156,7 +1247,7 @@ - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr {
     
     NSParameterAssert(name);
     
-    NSString *sql = [NSString stringWithFormat:@"rollback transaction to savepoint '%@';", FMEscapeSavePointName(name)];
+    NSString *sql = [NSString stringWithFormat:@"rollback transaction to savepoint '%@';", FMDBEscapeSavePointName(name)];
     BOOL worked = [self executeUpdate:sql];
     
     if (!worked && outErr) {
@@ -1192,6 +1283,7 @@ - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block {
 
 #endif
 
+#pragma mark Cache statements
 
 - (BOOL)shouldCacheStatements {
     return _shouldCacheStatements;
@@ -1210,7 +1302,8 @@ - (void)setShouldCacheStatements:(BOOL)value {
     }
 }
 
-void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv);
+#pragma mark Callback function
+
 void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv) {
 #if ! __has_feature(objc_arc)
     void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (id)sqlite3_user_data(context);

+ 1 - 1
src/fmdb/FMDatabaseAdditions.h

@@ -257,7 +257,7 @@
 
 /** Set the user-version
  
- @param appID The `uint32_t` numeric value of the user version.
+ @param version The `uint32_t` numeric value of the user version.
  
  @see userVersion
  */

+ 35 - 4
src/fmdb/FMDatabasePool.h

@@ -43,11 +43,23 @@
     int                 _openFlags;
 }
 
+/** Database path */
+
 @property (atomic, retain) NSString *path;
+
+/** Delegate object */
+
 @property (atomic, assign) id delegate;
+
+/** Maximum number of databases to create */
+
 @property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate;
+
+/** Open flags */
+
 @property (atomic, readonly) int openFlags;
 
+
 ///---------------------
 /// @name Initialization
 ///---------------------
@@ -63,8 +75,8 @@
 
 /** 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 aPath The file path of the database.
+ @param openFlags Flags passed to the openWithFlags method of the database
 
  @return The `FMDatabasePool` object. `nil` on error.
  */
@@ -149,6 +161,8 @@
 /** Synchronously perform database operations in pool using save point.
 
  @param block The code to be run on the `FMDatabasePool` pool.
+ 
+ @return `NSError` object if error; `nil` if successful.
 
  @warning You can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. If you need to nest, use `<[FMDatabase startSavePointWithName:error:]>` instead.
 */
@@ -159,13 +173,30 @@
 @end
 
 
+/** FMDatabasePool delegate category
+ 
+ This is a category that defines the protocol for the FMDatabasePool delegate
+ */
+
 @interface NSObject (FMDatabasePoolDelegate)
 
-/** Asks the delegate whether database should be added to the pool. */
+/** Asks the delegate whether database should be added to the pool. 
+ 
+ @param pool     The `FMDatabasePool` object.
+ @param database The `FMDatabase` object.
+ 
+ @return `YES` if it should add database to pool; `NO` if not.
+ 
+ */
 
 - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database;
 
-/** Tells the delegate that database was added to the pool. */
+/** Tells the delegate that database was added to the pool.
+ 
+ @param pool     The `FMDatabasePool` object.
+ @param database The `FMDatabase` object.
+
+ */
 
 - (void)databasePool:(FMDatabasePool*)pool didAddDatabase:(FMDatabase*)database;
 

+ 5 - 0
src/fmdb/FMDatabaseQueue.h

@@ -68,7 +68,12 @@
     int                 _openFlags;
 }
 
+/** Path of database */
+
 @property (atomic, retain) NSString *path;
+
+/** Open flags */
+
 @property (atomic, readonly) int openFlags;
 
 ///----------------------------------------------------

+ 41 - 4
src/sample/main.m

@@ -232,13 +232,50 @@ int main (int argc, const char * argv[]) {
     }
     
 #endif
-    
-    
-    
-    
+
+    // -------------------------------------------------------------------------------
+    // executeBulkSQL tests.
+
+    {
+        NSString *sql = @"create table if not exists test1 (id integer primary key autoincrement, x text);"
+                         "create table if not exists test2 (id integer primary key autoincrement, y text);"
+                         "create table if not exists test3 (id integer primary key autoincrement, z text);"
+                         "insert into test1 (x) values ('XXX');"
+                         "insert into test2 (y) values ('YYY');"
+                         "insert into test3 (z) values ('ZZZ');";
+
+        FMDBQuickCheck([db executeBulkSQL:sql]);
+    }
+
+    {
+        NSString *sql = @"select count(*) as count from test1;"
+                         "select count(*) as count from test2;"
+                         "select count(*) as count from test3;";
+
+        FMDBQuickCheck([db executeBulkSQL:sql block:^int(NSDictionary *dictionary) {
+            NSInteger count = [dictionary[@"count"] integerValue];
+            if (count == 0) {
+                NSLog(@"executeBulkSQL: error: was expecting non-zero number of records; dictionary = %@", dictionary);
+            } else {
+                NSLog(@"executeBulkSQL: everything ok: dictionary = %@", dictionary);
+            }
+            return 0;
+        }]);
+    }
+
+    {
+        NSString *sql = @"drop table test1;"
+                         "drop table test2;"
+                         "drop table test3;";
+
+        FMDBQuickCheck([db executeBulkSQL:sql]);
+    }
+
+
     {
         // -------------------------------------------------------------------------------
         // Named parameters count test.
+
         FMDBQuickCheck([db executeUpdate:@"create table namedparamcounttest (a text, b text, c integer, d double)"]);
         NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
         [dictionaryArgs setObject:@"Text1" forKey:@"a"];