Browse Source

Merge pull request #250 from OpenFibers/master

Some commits for FMSQLStatementSplitter
August "Gus" Mueller 11 years ago
parent
commit
8941bf15a4

+ 18 - 0
README.markdown

@@ -109,6 +109,24 @@ When you have finished executing queries and updates on the database, you should
 
 
 `FMDatabase` can begin and commit a transaction by invoking one of the appropriate methods or executing a begin/end transaction statement.
 `FMDatabase` can begin and commit a transaction by invoking one of the appropriate methods or executing a begin/end transaction statement.
 
 
+### Split Batch Statement
+
+`FMSQLStatementSplitter` can split batch sql statement into several separated statements, then `[FMDatabase executeUpdate:]` or other methods can be used to execute each separated statement:
+
+```
+NSString *batchStatement = @"insert into ftest values ('hello;');"
+                           @"insert into ftest values ('hi;');"
+                           @"insert into ftest values ('not h!\\\\');"
+                           @"insert into ftest values ('definitely not h!')";
+NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement];
+[queue inDatabase:^(FMDatabase *adb) {
+    for (FMSplittedStatement *sqlittedStatement in statements)
+    {
+        [adb executeUpdate:sqlittedStatement.statementString];
+    }
+}];
+```
+
 ### Data Sanitization
 ### Data Sanitization
 
 
 When providing a SQL statement to FMDB, you should not attempt to "sanitize" any values before insertion.  Instead, you should use the standard SQLite binding syntax:
 When providing a SQL statement to FMDB, you should not attempt to "sanitize" any values before insertion.  Instead, you should use the standard SQLite binding syntax:

+ 19 - 0
src/extra/FMStatementSplitter/FMSQLStatementSplitter.h

@@ -8,14 +8,33 @@
 
 
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
 
 
+/**
+ * The FMSplittedStatement class contains a separated statement.
+ */
 @interface FMSplittedStatement : NSObject
 @interface FMSplittedStatement : NSObject
+
+/**
+ * Separated statement string.
+ */
 @property (nonatomic, retain) NSString *statementString;//statement string
 @property (nonatomic, retain) NSString *statementString;//statement string
 @end
 @end
 
 
 @interface FMSQLStatementSplitter : NSObject
 @interface FMSQLStatementSplitter : NSObject
 
 
+/**
+ * Get singleton instance.
+ */
 + (instancetype)sharedInstance;
 + (instancetype)sharedInstance;
 
 
+/**
+ * Split batch sql statement into separated statements.
+ *
+ * @param batchStatement The batch statement string to split.
+ *
+ * @return Returns the array of splitted statements. Each member of return value is an `FMSplittedStatement`.
+ *
+ * @see FMSplittedStatement
+ */
 - (NSArray *)statementsFromBatchSqlStatement:(NSString *)batchStatement;
 - (NSArray *)statementsFromBatchSqlStatement:(NSString *)batchStatement;
 
 
 @end
 @end

+ 2 - 2
src/extra/FMStatementSplitter/FMStatementKeywordRecogniser.m

@@ -98,11 +98,11 @@ - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(
         NSUInteger remainingChars = [[scanner string] length] - *tokenPosition;
         NSUInteger remainingChars = [[scanner string] length] - *tokenPosition;
         if (remainingChars >= kwLength)
         if (remainingChars >= kwLength)
         {
         {
-            if (CFStringFindWithOptions((CFStringRef)[scanner string], (CFStringRef)keyword, CFRangeMake(*tokenPosition, kwLength), kCFCompareAnchored | kCFCompareCaseInsensitive, NULL))
+            if (CFStringFindWithOptions((CFStringRef)[scanner string], (CFStringRef)keyword, CFRangeMake((CFIndex)*tokenPosition, (CFIndex)kwLength), kCFCompareAnchored | kCFCompareCaseInsensitive, NULL))
             {
             {
                 if (remainingChars == kwLength ||
                 if (remainingChars == kwLength ||
                     nil == self.invalidFollowingCharacters ||
                     nil == self.invalidFollowingCharacters ||
-                    !CFStringFindCharacterFromSet((CFStringRef)[scanner string], (CFCharacterSetRef)self.invalidFollowingCharacters, CFRangeMake(*tokenPosition + kwLength, 1), kCFCompareAnchored, NULL))
+                    !CFStringFindCharacterFromSet((CFStringRef)[scanner string], (CFCharacterSetRef)self.invalidFollowingCharacters, CFRangeMake((CFIndex)(*tokenPosition + kwLength), 1), kCFCompareAnchored, NULL))
                 {
                 {
                     NSRange result = NSMakeRange(*tokenPosition, kwLength);
                     NSRange result = NSMakeRange(*tokenPosition, kwLength);
                     *tokenPosition = *tokenPosition + kwLength;
                     *tokenPosition = *tokenPosition + kwLength;

+ 7 - 7
src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.h

@@ -91,40 +91,40 @@
  *
  *
  * @see endQuote
  * @see endQuote
  */
  */
-@property (readwrite,copy) NSString *startQuote;
+@property (readwrite,nonatomic,copy) NSString *startQuote;
 
 
 /**
 /**
  * Determines the string used to indicate the end of the quoted literal.
  * Determines the string used to indicate the end of the quoted literal.
  *
  *
  * @see startQuote
  * @see startQuote
  */
  */
-@property (readwrite,copy) NSString *endQuote;
+@property (readwrite,nonatomic,copy) NSString *endQuote;
 
 
 /**
 /**
  * Determines the string used to indicate an escaped character in the quoted literal.
  * Determines the string used to indicate an escaped character in the quoted literal.
  */
  */
-@property (readwrite,copy) NSString *escapeSequence;
+@property (readwrite,nonatomic,copy) NSString *escapeSequence;
 
 
 /**
 /**
  * If `YES`, quoted string will contains `escapeSequence`.
  * If `YES`, quoted string will contains `escapeSequence`.
  * If `NO`, quoted string will not contains `escapeSequence`.
  * If `NO`, quoted string will not contains `escapeSequence`.
  * Default is `NO`.
  * Default is `NO`.
  */
  */
-@property (nonatomic, assign) BOOL shouldQuoteEscapeSequence;
+@property (nonatomic,assign) BOOL shouldQuoteEscapeSequence;
 
 
 /**
 /**
  * Determines how much of the input string to consume when an escaped literal is found, and what to replace it with.
  * Determines how much of the input string to consume when an escaped literal is found, and what to replace it with.
  */
  */
-@property (readwrite,copy) NSString *(^escapeReplacer)(NSString *tokenStream, NSUInteger *quotePosition);
+@property (readwrite,nonatomic,copy) NSString *(^escapeReplacer)(NSString *tokenStream, NSUInteger *quotePosition);
 
 
 /**
 /**
  * Determines the maximum length of the quoted literal not including quotes.  To indicate the literal can be any length specify NSNotFound.
  * Determines the maximum length of the quoted literal not including quotes.  To indicate the literal can be any length specify NSNotFound.
  */
  */
-@property (readwrite,assign) NSUInteger maximumLength;
+@property (readwrite,nonatomic,assign) NSUInteger maximumLength;
 
 
 /**
 /**
  * Determines the name of the token produced.
  * Determines the name of the token produced.
  */
  */
-@property (readwrite,copy) NSString *name;
+@property (readwrite,nonatomic,copy) NSString *name;
 
 
 @end
 @end

+ 10 - 53
src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.m

@@ -54,49 +54,6 @@ - (id)initWithStartQuote:(NSString *)initStartQuote endQuote:(NSString *)initEnd
     return self;
     return self;
 }
 }
 
 
-#define CPQuotedRecogniserStartQuoteKey     @"Q.s"
-#define CPQuotedRecogniserEndQuoteKey       @"Q.e"
-#define CPQuotedRecogniserEscapeSequenceKey @"Q.es"
-#define CPQuotedRecogniserMaximumLengthKey  @"Q.m"
-#define CPQuotedRecogniserNameKey           @"Q.n"
-
-- (id)initWithCoder:(NSCoder *)aDecoder
-{
-    self = [super init];
-    
-    if (nil != self)
-    {
-        [self setStartQuote:[aDecoder decodeObjectForKey:CPQuotedRecogniserStartQuoteKey]];
-        [self setEndQuote:[aDecoder decodeObjectForKey:CPQuotedRecogniserEndQuoteKey]];
-        [self setEscapeSequence:[aDecoder decodeObjectForKey:CPQuotedRecogniserEscapeSequenceKey]];
-        @try
-        {
-            [self setMaximumLength:[aDecoder decodeIntegerForKey:CPQuotedRecogniserMaximumLengthKey]];
-        }
-        @catch (NSException *exception)
-        {
-            NSLog(@"Warning, value for maximum length too long for this platform, allowing infinite lengths");
-            [self setMaximumLength:NSNotFound];
-        }
-        [self setName:[aDecoder decodeObjectForKey:CPQuotedRecogniserNameKey]];
-    }
-    
-    return self;
-}
-
-- (void)encodeWithCoder:(NSCoder *)aCoder
-{
-    if (nil != [self escapeReplacer])
-    {
-        NSLog(@"Warning: encoding CPQuoteRecogniser with an escapeReplacer set.  This will not be recreated when decoded.");
-    }
-    [aCoder encodeObject:[self startQuote]     forKey:CPQuotedRecogniserStartQuoteKey];
-    [aCoder encodeObject:[self endQuote]       forKey:CPQuotedRecogniserEndQuoteKey];
-    [aCoder encodeObject:[self escapeSequence] forKey:CPQuotedRecogniserEscapeSequenceKey];
-    [aCoder encodeInteger:[self maximumLength] forKey:CPQuotedRecogniserMaximumLengthKey];
-    [aCoder encodeObject:[self name]           forKey:CPQuotedRecogniserNameKey];
-}
-
 - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition
 - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition
 {
 {
     NSString *(^er)(NSString *tokenStream, NSUInteger *quotePosition) = [self escapeReplacer];
     NSString *(^er)(NSString *tokenStream, NSUInteger *quotePosition) = [self escapeReplacer];
@@ -104,12 +61,12 @@ - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(
     NSUInteger endQuoteLength = [self.endQuote length];
     NSUInteger endQuoteLength = [self.endQuote length];
     NSString *tokenString = [scanner string];
     NSString *tokenString = [scanner string];
 
 
-    long inputLength = [tokenString length];
+    NSUInteger inputLength = [tokenString length];
     NSUInteger rangeLength = [FMStatementQuotedRecogniser minWithLeftParam:inputLength - *tokenPosition
     NSUInteger rangeLength = [FMStatementQuotedRecogniser minWithLeftParam:inputLength - *tokenPosition
                                                                 rightParam:startQuoteLength + endQuoteLength + self.maximumLength];
                                                                 rightParam:startQuoteLength + endQuoteLength + self.maximumLength];
-    CFRange searchRange = CFRangeMake(*tokenPosition, rangeLength);
+    CFRange searchRange = CFRangeMake((CFIndex)*tokenPosition, (CFIndex)rangeLength);
     CFRange range;
     CFRange range;
-    BOOL matched = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.startQuote, searchRange, kCFCompareAnchored, &range);
+    Boolean matched = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.startQuote, searchRange, kCFCompareAnchored, &range);
     
     
     CFMutableStringRef outputString = CFStringCreateMutable(kCFAllocatorDefault, 0);
     CFMutableStringRef outputString = CFStringCreateMutable(kCFAllocatorDefault, 0);
     
     
@@ -120,22 +77,22 @@ - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(
         
         
         CFRange endRange;
         CFRange endRange;
         CFRange escapeRange;
         CFRange escapeRange;
-        BOOL matchedEndSequence = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.endQuote, searchRange, 0L, &endRange);
-        BOOL matchedEscapeSequence = nil == self.escapeSequence ? NO : CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.escapeSequence, searchRange, 0L, &escapeRange);
+        Boolean matchedEndSequence = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.endQuote, searchRange, 0L, &endRange);
+        Boolean matchedEscapeSequence = nil == self.escapeSequence ? NO : CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.escapeSequence, searchRange, 0L, &escapeRange);
         
         
-        while (matchedEndSequence && searchRange.location < inputLength)
+        while (matchedEndSequence && (NSUInteger)searchRange.location < inputLength)
         {
         {
             if (!matchedEscapeSequence || endRange.location < escapeRange.location)//End quote is not escaped by escape sequence.
             if (!matchedEscapeSequence || endRange.location < escapeRange.location)//End quote is not escaped by escape sequence.
             {
             {
                 NSUInteger resultRangeBegin = *tokenPosition;
                 NSUInteger resultRangeBegin = *tokenPosition;
-                *tokenPosition = endRange.location + endRange.length;
+                *tokenPosition = (NSUInteger)(endRange.location + endRange.length);
                 NSUInteger resultRangeLength = *tokenPosition - resultRangeBegin;
                 NSUInteger resultRangeLength = *tokenPosition - resultRangeBegin;
                 CFRelease(outputString);
                 CFRelease(outputString);
                 return NSMakeRange(resultRangeBegin, resultRangeLength);
                 return NSMakeRange(resultRangeBegin, resultRangeLength);
             }
             }
             else//End quote is escaped by escape sequence
             else//End quote is escaped by escape sequence
             {
             {
-                NSUInteger quotedPosition = escapeRange.location + escapeRange.length;
+                NSUInteger quotedPosition = (NSUInteger)(escapeRange.location + escapeRange.length);
                 CFRange subStrRange = CFRangeMake(searchRange.location,
                 CFRange subStrRange = CFRangeMake(searchRange.location,
                                                   escapeRange.location + (self.shouldQuoteEscapeSequence ? escapeRange.length : 0) - searchRange.location);
                                                   escapeRange.location + (self.shouldQuoteEscapeSequence ? escapeRange.length : 0) - searchRange.location);
                 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, (CFStringRef)tokenString, subStrRange);
                 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, (CFStringRef)tokenString, subStrRange);
@@ -158,8 +115,8 @@ - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(
                     CFRelease(substr);
                     CFRelease(substr);
                     quotedPosition += 1;
                     quotedPosition += 1;
                 }
                 }
-                searchRange.length   = searchRange.location + searchRange.length - quotedPosition;
-                searchRange.location = quotedPosition;
+                searchRange.length   = searchRange.location + searchRange.length - (CFIndex)quotedPosition;
+                searchRange.location = (CFIndex)quotedPosition;
                 
                 
                 if (endRange.location < searchRange.location)
                 if (endRange.location < searchRange.location)
                 {
                 {

+ 11 - 1
src/extra/FMStatementSplitter/FMStatementTokenRecogniser.h

@@ -8,9 +8,19 @@
 
 
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
 
 
-@protocol FMStatementTokenRecogniser <NSObject, NSCoding>
+/**
+ * The FMStatementTokenRecogniser protocol.
+ */
+@protocol FMStatementTokenRecogniser <NSObject>
 
 
 @required
 @required
+/**
+ * Recognise token with a scanner.
+ * @param scanner The recognising scanner.
+ * @param tokenPosition Begining token position to recognise of scanner.
+ *
+ * @return Returns the recognised token range of scanner. If not recognised, the location of return value is `NSNotFound`.
+ */
 - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition;
 - (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition;
 
 
 @end
 @end

+ 35 - 0
src/sample/main.m

@@ -5,6 +5,7 @@
 
 
 #import <Foundation/Foundation.h>
 #import <Foundation/Foundation.h>
 #import "FMDB.h"
 #import "FMDB.h"
+#import "FMSQLStatementSplitter.h"
 
 
 #define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) { NSLog(@"Failure on line %d", __LINE__); abort(); } }
 #define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) { NSLog(@"Failure on line %d", __LINE__); abort(); } }
 
 
@@ -1078,6 +1079,40 @@ int main (int argc, const char * argv[]) {
         
         
     }];
     }];
     
     
+    //Example for splitting batch statement
+    {
+        NSArray *batchStringArray = @[@"insert into ftest values ('hello;');",
+                                      @"insert into ftest values ('hi;');",
+                                      @"insert into ftest values ('not h!\\\\');",
+                                      @"insert into ftest values ('definitely not h!')"];
+        NSMutableString *batchStatement = [NSMutableString string];
+        for (NSString *str in batchStringArray)
+        {
+            [batchStatement appendString:str];
+        }
+        NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement];
+        NSLog(@"Number of sqlitted statements is %lu (should be %lu)", (unsigned long)statements.count, (unsigned long)batchStringArray.count);
+        
+        if (statements.count == batchStringArray.count)
+        {
+            for (NSUInteger splittedIndex = 0; splittedIndex < statements.count; splittedIndex++)
+            {
+                NSString *originalStatement = batchStringArray[splittedIndex];
+                NSString *splittedStatement = ((FMSplittedStatement *)statements[splittedIndex]).statementString;
+                if (![originalStatement isEqualToString:splittedStatement])
+                {
+                    NSLog(@"Splitter failed! Original batch string is %@", batchStatement);
+                }
+            }
+        }
+        
+        [queue inDatabase:^(FMDatabase *adb) {
+            for (FMSplittedStatement *sqlittedStatement in statements)
+            {
+                [adb executeUpdate:sqlittedStatement.statementString];
+            }
+        }];
+    }
     
     
     NSLog(@"That was version %@ of sqlite", [FMDatabase sqliteLibVersion]);
     NSLog(@"That was version %@ of sqlite", [FMDatabase sqliteLibVersion]);