Forráskód Böngészése

Add `executeBulkSQL` methods

robertmryan 11 éve
szülő
commit
0f707ec168
3 módosított fájl, 143 hozzáadás és 8 törlés
  1. 43 4
      src/fmdb/FMDatabase.h
  2. 59 0
      src/fmdb/FMDatabase.m
  3. 41 4
      src/sample/main.m

+ 43 - 4
src/fmdb/FMDatabase.h

@@ -38,6 +38,10 @@
     #define instancetype id
 #endif
 
+
+typedef int(^ExecuteBulkSQLCallbackBlock)(NSDictionary *resultsDictionary);
+
+
 /** A SQLite ([http://sqlite.org/](http://sqlite.org/)) Objective-C wrapper.
  
  ### Usage
@@ -261,9 +265,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.
 
@@ -338,6 +344,41 @@
 // 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:userInfo: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:(ExecuteBulkSQLCallbackBlock)block;
 
 /** Last insert rowid
  
@@ -449,8 +490,6 @@
 // Documentation forthcoming.
 - (FMResultSet *)executeQuery:(NSString*)sql withVAList: (va_list)args;
 
-
-
 ///-------------------
 /// @name Transactions
 ///-------------------

+ 59 - 0
src/fmdb/FMDatabase.m

@@ -2,10 +2,15 @@
 #import "unistd.h"
 #import <objc/runtime.h>
 
+
+static ExecuteBulkSQLCallbackBlock 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
@@ -45,6 +50,7 @@ - (instancetype)initWithPath:(NSString*)aPath {
         _logsErrors                 = YES;
         _crashOnErrors              = NO;
         _maxBusyRetryTimeInterval   = 2;
+        execCallbackBlock           = nil;
     }
     
     return self;
@@ -1060,6 +1066,59 @@ - (BOOL)executeUpdateWithFormat:(NSString*)format, ... {
     return [self executeUpdate:sql withArgumentsInArray:arguments];
 }
 
+int executeBulkSQLCallback(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:(ExecuteBulkSQLCallbackBlock)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], executeBulkSQLCallback, 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);

+ 41 - 4
src/sample/main.m

@@ -231,13 +231,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, [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, [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, [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"];