diff options
author | August "Gus" Mueller <gus@flyingmeat.com> | 2014-04-16 21:42:45 +0400 |
---|---|---|
committer | August "Gus" Mueller <gus@flyingmeat.com> | 2014-04-16 21:42:45 +0400 |
commit | 8941bf15a4c2e16e1850af59e735267bc154d943 (patch) | |
tree | e8e102c051a0554612a23bc9c943c00b1c5f2c90 | |
parent | 3691599e49f6f6ca35dd18d3bc92890e42fc8318 (diff) | |
parent | a29e7c46888ea51c713daf74b3ab037c33d2d0f3 (diff) |
Merge pull request #250 from OpenFibers/master
Some commits for FMSQLStatementSplitter
-rw-r--r-- | README.markdown | 18 | ||||
-rw-r--r-- | src/extra/FMStatementSplitter/FMSQLStatementSplitter.h | 19 | ||||
-rw-r--r-- | src/extra/FMStatementSplitter/FMStatementKeywordRecogniser.m | 4 | ||||
-rw-r--r-- | src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.h | 14 | ||||
-rw-r--r-- | src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.m | 63 | ||||
-rw-r--r-- | src/extra/FMStatementSplitter/FMStatementTokenRecogniser.h | 12 | ||||
-rw-r--r-- | src/sample/main.m | 35 |
7 files changed, 102 insertions, 63 deletions
diff --git a/README.markdown b/README.markdown index 6d5b85d..433d6d2 100644 --- a/README.markdown +++ b/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. +### 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 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: diff --git a/src/extra/FMStatementSplitter/FMSQLStatementSplitter.h b/src/extra/FMStatementSplitter/FMSQLStatementSplitter.h index d9beba1..e1042f2 100644 --- a/src/extra/FMStatementSplitter/FMSQLStatementSplitter.h +++ b/src/extra/FMStatementSplitter/FMSQLStatementSplitter.h @@ -8,14 +8,33 @@ #import <Foundation/Foundation.h> +/** + * The FMSplittedStatement class contains a separated statement. + */ @interface FMSplittedStatement : NSObject + +/** + * Separated statement string. + */ @property (nonatomic, retain) NSString *statementString;//statement string @end @interface FMSQLStatementSplitter : NSObject +/** + * Get singleton instance. + */ + (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; @end diff --git a/src/extra/FMStatementSplitter/FMStatementKeywordRecogniser.m b/src/extra/FMStatementSplitter/FMStatementKeywordRecogniser.m index 3ca3c27..b968708 100644 --- a/src/extra/FMStatementSplitter/FMStatementKeywordRecogniser.m +++ b/src/extra/FMStatementSplitter/FMStatementKeywordRecogniser.m @@ -98,11 +98,11 @@ NSUInteger remainingChars = [[scanner string] length] - *tokenPosition; 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 || 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); *tokenPosition = *tokenPosition + kwLength; diff --git a/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.h b/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.h index 567e251..5b5f400 100644 --- a/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.h +++ b/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.h @@ -91,40 +91,40 @@ * * @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. * * @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. */ -@property (readwrite,copy) NSString *escapeSequence; +@property (readwrite,nonatomic,copy) NSString *escapeSequence; /** * If `YES`, quoted string will contains `escapeSequence`. * If `NO`, quoted string will not contains `escapeSequence`. * 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. */ -@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. */ -@property (readwrite,assign) NSUInteger maximumLength; +@property (readwrite,nonatomic,assign) NSUInteger maximumLength; /** * Determines the name of the token produced. */ -@property (readwrite,copy) NSString *name; +@property (readwrite,nonatomic,copy) NSString *name; @end diff --git a/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.m b/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.m index 3db01d6..f27a08a 100644 --- a/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.m +++ b/src/extra/FMStatementSplitter/FMStatementQuotedRecogniser.m @@ -54,49 +54,6 @@ 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 { NSString *(^er)(NSString *tokenStream, NSUInteger *quotePosition) = [self escapeReplacer]; @@ -104,12 +61,12 @@ NSUInteger endQuoteLength = [self.endQuote length]; NSString *tokenString = [scanner string]; - long inputLength = [tokenString length]; + NSUInteger inputLength = [tokenString length]; NSUInteger rangeLength = [FMStatementQuotedRecogniser minWithLeftParam:inputLength - *tokenPosition rightParam:startQuoteLength + endQuoteLength + self.maximumLength]; - CFRange searchRange = CFRangeMake(*tokenPosition, rangeLength); + CFRange searchRange = CFRangeMake((CFIndex)*tokenPosition, (CFIndex)rangeLength); 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); @@ -120,22 +77,22 @@ CFRange endRange; 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. { NSUInteger resultRangeBegin = *tokenPosition; - *tokenPosition = endRange.location + endRange.length; + *tokenPosition = (NSUInteger)(endRange.location + endRange.length); NSUInteger resultRangeLength = *tokenPosition - resultRangeBegin; CFRelease(outputString); return NSMakeRange(resultRangeBegin, resultRangeLength); } 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, escapeRange.location + (self.shouldQuoteEscapeSequence ? escapeRange.length : 0) - searchRange.location); CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, (CFStringRef)tokenString, subStrRange); @@ -158,8 +115,8 @@ CFRelease(substr); 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) { diff --git a/src/extra/FMStatementSplitter/FMStatementTokenRecogniser.h b/src/extra/FMStatementSplitter/FMStatementTokenRecogniser.h index be86796..4ae25f9 100644 --- a/src/extra/FMStatementSplitter/FMStatementTokenRecogniser.h +++ b/src/extra/FMStatementSplitter/FMStatementTokenRecogniser.h @@ -8,9 +8,19 @@ #import <Foundation/Foundation.h> -@protocol FMStatementTokenRecogniser <NSObject, NSCoding> +/** + * The FMStatementTokenRecogniser protocol. + */ +@protocol FMStatementTokenRecogniser <NSObject> @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; @end diff --git a/src/sample/main.m b/src/sample/main.m index e44b9b2..0922023 100644 --- a/src/sample/main.m +++ b/src/sample/main.m @@ -5,6 +5,7 @@ #import <Foundation/Foundation.h> #import "FMDB.h" +#import "FMSQLStatementSplitter.h" #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]); |