aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/CustomQuery.h2
-rw-r--r--Source/CustomQuery.m206
-rw-r--r--Source/SPSQLParser.h27
-rw-r--r--Source/SPSQLParser.m380
-rw-r--r--Source/TableDump.m4
5 files changed, 399 insertions, 220 deletions
diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h
index 39b405f5..9c4e1a1e 100644
--- a/Source/CustomQuery.h
+++ b/Source/CustomQuery.h
@@ -78,6 +78,8 @@
CMMCPConnection *mySQLConnection;
NSString *usedQuery;
+ NSRange currentQueryRange;
+ NSArray *currentQueryRanges;
NSString *mySQLversion;
int queryStartPosition;
diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m
index bbcd6f2f..8e7d3325 100644
--- a/Source/CustomQuery.m
+++ b/Source/CustomQuery.m
@@ -59,7 +59,7 @@
// Retrieve the custom query string and split it into separate SQL queries
queryParser = [[SPSQLParser alloc] initWithString:[textView string]];
- queries = [queryParser parseQueries];
+ queries = [queryParser splitSqlStringByCharacter:';'];
[queryParser release];
NSRange curRange = [textView selectedRange];
@@ -97,8 +97,10 @@
// If the current selection is a single caret position, run the current query.
if (selectedRange.length == 0) {
- BOOL doLookBehind = YES;
- query = [self queryAtPosition:selectedRange.location lookBehind:&doLookBehind];
+ // BOOL doLookBehind = YES;
+ // query = [self queryAtPosition:selectedRange.location lookBehind:&doLookBehind];
+ if(currentQueryRange.length)
+ query = [[textView string] substringWithRange:currentQueryRange];
if (!query) {
NSBeep();
return;
@@ -108,7 +110,7 @@
// Otherwise, run the selected text.
} else {
queryParser = [[SPSQLParser alloc] initWithString:[[textView string] substringWithRange:selectedRange]];
- queries = [queryParser parseQueries];
+ queries = [queryParser splitSqlStringByCharacter:';'];
[queryParser release];
}
@@ -388,6 +390,8 @@
float executionTime = 0;
int firstErrorOccuredInQuery = -1;
BOOL suppressErrorSheet = NO;
+ // NSString *delimiterMatch = @"^\\s*delimiter\\s*$|^\\s*delimiter\\s+\\S+\\s*$";
+ NSCharacterSet *whitespaceAndNewlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
// Notify listeners that a query has started
[[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self];
@@ -403,11 +407,13 @@
[customQueryView removeTableColumn:[theColumns objectAtIndex:0]];
}
+ BOOL queriesSeparatedByDelimiter = NO;
+
// Perform the supplied queries in series
for ( i = 0 ; i < [queries count] ; i++ ) {
// Don't run blank queries, or queries which only contain whitespace.
- if ([[[queries objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0)
+ if ([[[queries objectAtIndex:i] stringByTrimmingCharactersInSet:whitespaceAndNewlineSet] length] == 0)
continue;
// Run the query, timing execution (note this also includes network and overhead)
@@ -467,7 +473,10 @@
if(usedQuery)
[usedQuery release];
- usedQuery = [[NSString stringWithString:[queries componentsJoinedByString:@";\n"]] retain];
+ if(!queriesSeparatedByDelimiter)
+ usedQuery = [[NSString stringWithString:[queries componentsJoinedByString:@";\n"]] retain];
+ else // TODO how to combine the query array if “delimiter command” was used?
+ usedQuery = @"";
//perform empty query if no query is given
if ( [queries count] == 0 ) {
@@ -489,15 +498,17 @@
}
//add query to history
- [queryHistoryButton insertItemWithTitle:[queries componentsJoinedByString:@"; "] atIndex:1];
- while ( [queryHistoryButton numberOfItems] > [[prefs objectForKey:@"CustomQueryMaxHistoryItems"] intValue] + 1 ) {
- [queryHistoryButton removeItemAtIndex:[queryHistoryButton numberOfItems]-1];
- }
- for ( i = 1 ; i < [queryHistoryButton numberOfItems] ; i++ )
- {
- [menuItems addObject:[queryHistoryButton itemTitleAtIndex:i]];
+ if(!queriesSeparatedByDelimiter) { // TODO only add to history if no “delimiter” command was used
+ [queryHistoryButton insertItemWithTitle:[queries componentsJoinedByString:@"; "] atIndex:1];
+ while ( [queryHistoryButton numberOfItems] > [[prefs objectForKey:@"CustomQueryMaxHistoryItems"] intValue] + 1 ) {
+ [queryHistoryButton removeItemAtIndex:[queryHistoryButton numberOfItems]-1];
+ }
+ for ( i = 1 ; i < [queryHistoryButton numberOfItems] ; i++ )
+ {
+ [menuItems addObject:[queryHistoryButton itemTitleAtIndex:i]];
+ }
+ [prefs setObject:menuItems forKey:@"queryHistory"];
}
- [prefs setObject:menuItems forKey:@"queryHistory"];
// Error checking
if ( [errors length] ) {
@@ -745,7 +756,7 @@
return NSMakeRange(NSNotFound, 0);
}
- NSString * theQuery = [queries objectAtIndex:anIndex];
+ NSString *theQuery = [queries objectAtIndex:anIndex];
// Calculate the text length before that query at index anIndex
long prevQueriesLength = 0;
@@ -772,38 +783,44 @@
- (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind
{
SPSQLParser *customQueryParser;
- NSArray *queries;
- NSString *query = nil;
- NSRange queryRange;
- int i, j, lastQueryStartPosition, queryPosition = 0;
+ NSArray *queries;
+ NSString *query = nil;
+ NSRange queryRange;
+
+ long i, j, lastQueryStartPosition, queryPosition = 0;
+ long queryCount;
+
+ NSCharacterSet *whitespaceAndNewlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
// If the supplied position is negative or beyond the end of the string, return nil.
if (position < 0 || position > [[textView string] length])
return nil;
- // Split the current text into queries
- customQueryParser = [[SPSQLParser alloc] initWithString:[textView string]];
- queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringByCharacter:';']];
-
- // ========= test case for SQL splitting into ranges == START
- // NSArray *qs; int qc;
- // qs = [[NSArray alloc] initWithArray:[customQueryParser splitStringIntoRangesOfSQLQueries]];
- // NSLog(@"---START---");
- // for(qc=0;qc<[qs count];qc++) {
- // NSLog(@"%d:", qc);
- // NSLog(@"%@", [[textView string] substringWithRange:NSRangeFromString([qs objectAtIndex:qc])]);
- // }
- // ========= test case for SQL splitting into ranges == END
- [customQueryParser release];
+ // Split the current text into queries and ranges
+ // only if the textView was really changed, otherwise use the cache
+ if([[textView textStorage] editedMask] != 0) {
+ customQueryParser = [[SPSQLParser alloc] initWithString:[textView string]];
+ queries = [[NSArray alloc] initWithArray:[customQueryParser splitSqlStringIntoRangesByCharacter:';']];
+ if(currentQueryRanges)
+ [currentQueryRanges release];
+ currentQueryRanges = [[NSArray arrayWithArray:queries] retain];
+ [customQueryParser release];
+ } else {
+ queries = [[NSArray alloc] initWithArray:currentQueryRanges];
+ }
- NSCharacterSet *newlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ queryCount = [queries count];
// Walk along the array of queries to identify the current query - taking into account
// the extra semicolon at the end of each query
- for (i = 0; i < [queries count]; i++ ) {
+ for (i = 0; i < queryCount; i++ ) {
+
lastQueryStartPosition = queryStartPosition;
- queryStartPosition = queryPosition;
- queryPosition += [[queries objectAtIndex:i] length];
+ queryRange = [[queries objectAtIndex:i] rangeValue];
+ queryStartPosition = queryRange.location;
+ queryPosition = NSMaxRange(queryRange);
+
if (queryPosition >= position) {
// If lookbehind is enabled, check whether the current position could be considered to
@@ -815,70 +832,83 @@
// If the caret is at the very start of the string, always associate
if (position == queryStartPosition) positionAssociatedWithPreviousQuery = YES;
-
+
+ // If the caret is in between a user-defined delimiter whose length is >1, always associate
+ if (!positionAssociatedWithPreviousQuery && i && NSMaxRange([[queries objectAtIndex:i-1] rangeValue]) < position && position < queryStartPosition) positionAssociatedWithPreviousQuery = YES;
+
// Otherwise associate if only whitespace since previous, and a newline before next.
if (!positionAssociatedWithPreviousQuery) {
+ @try{
NSString *stringToPrevious = [[textView string] substringWithRange:NSMakeRange(queryStartPosition, position - queryStartPosition)];
NSString *stringToEnd = [[textView string] substringWithRange:NSMakeRange(position, queryPosition - position)];
- NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
- if (![[stringToPrevious stringByTrimmingCharactersInSet:newlineSet] length]) {
+ if (![[stringToPrevious stringByTrimmingCharactersInSet:whitespaceAndNewlineSet] length]) {
for (j = 0; j < [stringToEnd length]; j++) {
if ([whitespaceSet characterIsMember:[stringToEnd characterAtIndex:j]]) continue;
- if ([newlineSet characterIsMember:[stringToEnd characterAtIndex:j]]) {
+ if ([whitespaceAndNewlineSet characterIsMember:[stringToEnd characterAtIndex:j]]) {
positionAssociatedWithPreviousQuery = YES;
}
break;
}
}
+ } @catch(id ae) {}
}
// If there is a previous query and the position should be associated with it, do so.
- if (i && positionAssociatedWithPreviousQuery && [[[queries objectAtIndex:i-1] stringByTrimmingCharactersInSet:newlineSet] length]) {
- query = [NSString stringWithString:[queries objectAtIndex:i-1]];
+ if (i && positionAssociatedWithPreviousQuery && [[[[textView string] substringWithRange:[[queries objectAtIndex:i-1] rangeValue]] stringByTrimmingCharactersInSet:whitespaceAndNewlineSet] length]) {
queryStartPosition = lastQueryStartPosition;
- queryRange=NSMakeRange(queryStartPosition, [[queries objectAtIndex:i-1] length]);
+ queryRange = [[queries objectAtIndex:i-1] rangeValue];
break;
}
// Lookbehind failed - set the pointer to NO so the parent knows.
*doLookBehind = NO;
}
-
- query = [NSString stringWithString:[queries objectAtIndex:i]];
- queryRange=NSMakeRange(queryStartPosition, [[queries objectAtIndex:i] length]);
break;
}
- queryPosition++;
}
// For lookbehinds catch position at the very end of a string ending in a semicolon
- if (*doLookBehind && position == [[textView string] length] && !query)
+ if (*doLookBehind && position == [[textView string] length])
{
- query = [queries lastObject];
- queryRange=NSMakeRange(queryStartPosition, [[queries lastObject] length]);
+ queryRange = [[queries lastObject] rangeValue];
}
[queries release];
+
// Remove all background color attributes
- [[textView textStorage] removeAttribute:NSBackgroundColorAttributeName range:NSMakeRange(0,[[textView string] length])];
+ NSRange textRange = NSMakeRange(0,[[textView string] length]);
+ [[textView textStorage] removeAttribute:NSBackgroundColorAttributeName range:textRange];
- // Ensure the string isn't empty.
- // (We could also strip comments for this check, but that prevents use of conditional comments)
- if ([[query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0)
+ queryRange = NSIntersectionRange(queryRange, textRange);
+ if (!queryRange.length) {
+ currentQueryRange = NSMakeRange(0,0);
return nil;
+ }
+
+ query = [[textView string] substringWithRange:queryRange];
// Highlight by setting a background color the current query
- // and delete leading/trailing white spaces
+ // and ignore leading/trailing white spaces
int biasStart = [query rangeOfRegex:@"^\\s*"].length;
- int biasEnd = [query rangeOfRegex:@"\\s*$"].length;
+ int biasEnd = [query rangeOfRegex:@"\\s*$"].length;
queryRange.location += biasStart;
- queryRange.length -= biasEnd+biasStart;
+ queryRange.length -= biasEnd+biasStart;
+
+ // Ensure the string isn't empty.
+ // (We could also strip comments for this check, but that prevents use of conditional comments)
+ if(queryRange.length < 1 || queryRange.length > [query length]) {
+ currentQueryRange = NSMakeRange(0,0);
+ return nil;
+ }
+
[[textView textStorage] addAttribute: NSBackgroundColorAttributeName
value: [NSColor colorWithDeviceRed:0.95 green:0.95 blue:0.95 alpha:1]
range: queryRange ];
- // Return the located string.
+ currentQueryRange = queryRange;
+
+ // Return the located string
return query;
}
@@ -934,6 +964,7 @@
mySQLConnection = theConnection;
prefs = [NSUserDefaults standardUserDefaults];
+ currentQueryRanges = nil;
if ( [prefs objectForKey:@"queryFavorites"] ) {
queryFavorites = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:@"queryFavorites"]];
@@ -1310,41 +1341,42 @@
// whether the caret is inside a valid query.
if ([textView selectedRange].length == 0) {
int selectionPosition = [textView selectedRange].location;
- int movedRangeStart, movedRangeLength;
- BOOL updateQueryButtons = FALSE;
- NSRange oldSelection;
- NSCharacterSet *whitespaceAndNewlineCharset = [NSCharacterSet whitespaceAndNewlineCharacterSet];
+ // int movedRangeStart, movedRangeLength;
+ BOOL updateQueryButtons = TRUE;
+ // NSRange oldSelection;
+ // NSCharacterSet *whitespaceAndNewlineCharset = [NSCharacterSet whitespaceAndNewlineCharacterSet];
// Retrieve the old selection position
- [[[aNotification userInfo] objectForKey:@"NSOldSelectedCharacterRange"] getValue:&oldSelection];
+ // [[[aNotification userInfo] objectForKey:@"NSOldSelectedCharacterRange"] getValue:&oldSelection];
// Only process the query text if the selection previously had length, or moved more than 100 characters,
// or the intervening space contained a semicolon, or typing has been performed with no current query.
// This adds more checks to every keypress, but ensures the majority of the actions don't incur a
// parsing overhead - which is cheap on small text strings but heavy of large queries.
- movedRangeStart = (selectionPosition < oldSelection.location)?selectionPosition:oldSelection.location;
- movedRangeLength = abs(selectionPosition - oldSelection.location);
- if (oldSelection.length > 0) updateQueryButtons = TRUE;
- if (!updateQueryButtons && movedRangeLength > 100) updateQueryButtons = TRUE;
- if (!updateQueryButtons && oldSelection.location > [[textView string] length]) updateQueryButtons = TRUE;
- if (!updateQueryButtons && [[textView string] rangeOfString:@";" options:0 range:NSMakeRange(movedRangeStart, movedRangeLength)].location != NSNotFound) updateQueryButtons = TRUE;
- if (!updateQueryButtons && ![runSelectionButton isEnabled] && selectionPosition > oldSelection.location
- && [[[[textView string] substringWithRange:NSMakeRange(movedRangeStart, movedRangeLength)] stringByTrimmingCharactersInSet:whitespaceAndNewlineCharset] length]) updateQueryButtons = TRUE;
- if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]) {
- int charPosition;
- unichar theChar;
- for (charPosition = selectionPosition; charPosition > 0; charPosition--) {
- theChar = [[textView string] characterAtIndex:charPosition-1];
- if (theChar == ';') {
- updateQueryButtons = TRUE;
- break;
- }
- if (![whitespaceAndNewlineCharset characterIsMember:theChar]) break;
- }
- }
- if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]) {
- updateQueryButtons = TRUE;
- }
+ // movedRangeStart = (selectionPosition < oldSelection.location)?selectionPosition:oldSelection.location;
+ // movedRangeLength = abs(selectionPosition - oldSelection.location);
+ // updateQueryButtons = TRUE;
+ // if (oldSelection.length > 0) updateQueryButtons = TRUE;
+ // if (!updateQueryButtons && movedRangeLength > 100) updateQueryButtons = TRUE;
+ // if (!updateQueryButtons && oldSelection.location > [[textView string] length]) updateQueryButtons = TRUE;
+ // if (!updateQueryButtons && [[textView string] rangeOfString:@";" options:0 range:NSMakeRange(movedRangeStart, movedRangeLength)].location != NSNotFound) updateQueryButtons = TRUE;
+ // if (!updateQueryButtons && ![runSelectionButton isEnabled] && selectionPosition > oldSelection.location
+ // && [[[[textView string] substringWithRange:NSMakeRange(movedRangeStart, movedRangeLength)] stringByTrimmingCharactersInSet:whitespaceAndNewlineCharset] length]) updateQueryButtons = TRUE;
+ // if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]) {
+ // int charPosition;
+ // unichar theChar;
+ // for (charPosition = selectionPosition; charPosition > 0; charPosition--) {
+ // theChar = [[textView string] characterAtIndex:charPosition-1];
+ // if (theChar == ';') {
+ // updateQueryButtons = TRUE;
+ // break;
+ // }
+ // if (![whitespaceAndNewlineCharset characterIsMember:theChar]) break;
+ // }
+ // }
+ // if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]) {
+ // updateQueryButtons = TRUE;
+ // }
if (updateQueryButtons) {
[runSelectionButton setTitle:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")];
diff --git a/Source/SPSQLParser.h b/Source/SPSQLParser.h
index af60645a..022e17bb 100644
--- a/Source/SPSQLParser.h
+++ b/Source/SPSQLParser.h
@@ -64,6 +64,10 @@
unichar *stringCharCache;
long charCacheStart;
long charCacheEnd;
+ NSString *delimiter;
+ int delimiterLength;
+ BOOL charIsDelimiter;
+ BOOL isDelimiterCommand;
}
@@ -74,6 +78,7 @@ typedef enum _SPCommentTypes {
} SPCommentType;
+
/*
* Removes comments within the current string, trimming "#", "--[/s]", and "/* * /" style strings.
*/
@@ -212,25 +217,16 @@ typedef enum _SPCommentTypes {
*/
- (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
-/*
- * As splitStringByCharacter: ..., but allows control over quoting
- * - it recognises CREATE ... BEGIN ... END statements
- * - it can detect a SINGLE SQL statement in between
- * delimiter foo ... foo delimiter ;
- * ['delimiter ;' MUST be given!]
- * - it returns an array of ranges (as NSString "{loc, length}").
- * FromPosition: is needed if a subrange is passed to sync the ranges
- * according to the CQ textView ones.
- */
-- (NSArray *) splitStringIntoRangesOfSQLQueries;
-- (NSArray *) splitStringIntoRangesOfSQLQueriesFromPosition:(long)position;
+- (NSArray *) splitSqlStringByCharacter:(unichar)character;
+- (NSArray *) splitSqlStringIntoRangesByCharacter:(unichar)character;
/*
* Methods used internally by this class to power the methods above:
*/
- (long) firstOccurrenceOfCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
-- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings ;
+- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
+- (long) firstOccurrenceInSqlOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings;
- (long) endIndexOfStringQuotedByCharacter:(unichar)quoteCharacter startingAtIndex:(long)index;
- (long) endIndexOfCommentOfType:(SPCommentType)commentType startingAtIndex:(long)index;
@@ -242,11 +238,6 @@ typedef enum _SPCommentTypes {
- (void) deleteCharactersInRange:(NSRange)aRange;
- (void) insertString:(NSString *)aString atIndex:(NSUInteger)anIndex;
-/*
- * return an array of queries
- */
-- (NSArray *) parseQueries;
-
/* Required and primitive methods to allow subclassing class cluster */
#pragma mark -
- (id) init;
diff --git a/Source/SPSQLParser.m b/Source/SPSQLParser.m
index 281c1b3d..ac59c2fc 100644
--- a/Source/SPSQLParser.m
+++ b/Source/SPSQLParser.m
@@ -44,54 +44,6 @@ TO_BUFFER_STATE to_scan_string (const char *);
/*
- * return an array of queries
- */
-- (NSArray *) parseQueries
-{
- [self deleteComments];
-
- /* this is a hack so I could test the funcs and procs viewing, needed a way to get those in.
- * all this does is look for 'delimiter' in the text to trigger this parser. basically
- * it runs through the query, line by line, and sets the delimiter accordingly to break
- * out the individual queries. this is not very rebust but works for testing purposes.
- * I believe Hans is currently working on a more robust parser. :mtv
- */
- if( [string rangeOfString:@"delimiter" options:NSCaseInsensitiveSearch].location != NSNotFound ) {
- NSString *delim = @";";
- NSString *thisLine = @"";
- NSMutableArray *nq = [[NSMutableArray alloc] init];
- NSArray *lines = [self splitStringByCharacter:'\n'];
- for( NSString *line in lines ) {
- line = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- if( [line hasPrefix:@"delimiter"] ) {
- delim = [line substringFromIndex:9];
- delim = [delim stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- NSLog( @"delimiter now [%@]", delim );
- continue;
- }
- if( [line hasSuffix:delim] ) {
- thisLine = [thisLine stringByAppendingString:line];
- [nq addObject:[thisLine substringWithRange:NSMakeRange(0,[thisLine length]-[delim length])]];
- NSLog( @"query: [%@]", [thisLine substringWithRange:NSMakeRange(0,[thisLine length]-[delim length])] );
- thisLine = @"";
- }
- else {
- thisLine = [thisLine stringByAppendingString:line];
- thisLine = [thisLine stringByAppendingString:@"\n"];
- }
- }
- if( thisLine != @"" ) {
- [nq addObject:thisLine];
- NSLog( @"query: [%@]", thisLine );
- }
- return nq;
- } else {
- // just split as normal
- return [self splitStringByCharacter:';'];
- }
-}
-
-/*
* Removes comments within the current string, trimming "#", "--[/s]", and "/* * /" style strings.
*/
- (void) deleteComments
@@ -409,7 +361,6 @@ TO_BUFFER_STATE to_scan_string (const char *);
return [self splitStringByCharacter:character skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
}
-
/*
* As splitStringByCharacter: ..., but allows control over both bracketing and quoting.
*/
@@ -438,73 +389,84 @@ TO_BUFFER_STATE to_scan_string (const char *);
}
/*
- * As splitStringByCharacter: ..., but allows control over quoting
- * - it recognises CREATE ... BEGIN ... END statements
- * - it can detect a SINGLE SQL statement in between
- * delimiter foo ... foo delimiter ;
- * ['delimiter ;' MUST be given!]
- * - it returns an array of ranges (as NSString "{loc, length}").
- * FromPosition: is needed if a subrange is passed to sync the ranges
- * according to the CQ textView ones.
+ * As splitStringByCharacter: ..., but allows control over both bracketing and quoting.
*/
-- (NSArray *) splitStringIntoRangesOfSQLQueries
+- (NSArray *) splitSqlStringByCharacter:(unichar)character
{
- return [self splitStringIntoRangesOfSQLQueriesFromPosition:0];
+ NSMutableArray *resultsArray = [NSMutableArray arrayWithCapacity:2000];
+
+ long stringIndex = -1, nextIndex = 0;
+ int queryLength;
+
+ // these delimiter variables will be set in firstOccurrenceOfCharacter:
+ delimiter = nil;
+ delimiterLength = 0; // is delimiter length minus 1
+ charIsDelimiter = YES; // flag if passed character is the current delimiter
+ isDelimiterCommand = NO;
+
+ IMP firstOccOfChar = [self methodForSelector:@selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:)];
+ IMP subString = [string methodForSelector:@selector(substringWithRange:)];
+
+ // Walk through the string finding the character to split by, and add all strings to the array.
+ while (1) {
+
+ nextIndex = (long)(*firstOccOfChar)(self, @selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, NO, YES);
+ if (nextIndex == NSNotFound)
+ break;
+
+ stringIndex += 1;
+ // Ignore a delimiter command and check range length
+ queryLength = nextIndex - stringIndex - delimiterLength;
+ if(!isDelimiterCommand && queryLength > 0)
+ [resultsArray addObject:(NSString *)(*subString)(string, @selector(substringWithRange:), NSMakeRange(stringIndex, queryLength))];
+ if(isDelimiterCommand) isDelimiterCommand = NO;
+ stringIndex = nextIndex;
+ }
+
+ // Add the end of the string after the previously matched character where appropriate.
+ if (stringIndex + 1 < [string length])
+ [resultsArray addObject:[string substringFromIndex:stringIndex + 1]];
+
+ return resultsArray;
}
-- (NSArray *) splitStringIntoRangesOfSQLQueriesFromPosition:(long)position
+
+/*
+ * As splitStringByCharacter: but it returns only the ranges of queries as NSValues
+ */
+- (NSArray *) splitSqlStringIntoRangesByCharacter:(unichar)character
{
- NSMutableArray *resultsArray = [NSMutableArray array];
+ NSMutableArray *resultsArray = [NSMutableArray arrayWithCapacity:2000];
+
+ long stringIndex = -1, nextIndex = 0;
+ int queryLength;
- //initialise flex
- yyuoffset = 0; yyuleng = 0;
- to_switch_to_buffer(to_scan_string([string UTF8String]));
+ // these delimiter variables will be set in firstOccurrenceOfCharacter:
+ delimiter = nil;
+ delimiterLength = 0; // is delimiter length minus 1
+ charIsDelimiter = YES; // flag if passed character is the current delimiter
+ isDelimiterCommand = NO;
- unsigned long token;
- unsigned long lastFoundToken = 0;
- unsigned long delimLength = 0;
- unsigned long commentStart = 0;
- unsigned long commentLength = 0;
+ IMP firstOccOfChar = [self methodForSelector:@selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:)];
- NSString *delimString;
+ // Walk through the string finding the character to split by, and add all strings to the array.
+ while (1) {
- //now loop through all queries
- while (token=tolex()){
- switch (token) {
- case SP_SQL_TOKEN_SEMICOLON:
- [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, yyuoffset-lastFoundToken]];
- break;
- case SP_SQL_TOKEN_SINGLE_LINE_COMMENT:
- commentStart = yyuoffset+position;
- commentLength = yyuleng;
- break;
- case SP_SQL_TOKEN_DELIM_END:
- [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, yyuoffset-lastFoundToken-delimLength]];
- delimLength = 0;
- delimString = nil;
- break;
- case SP_SQL_TOKEN_DELIM_VALUE:
- delimString = [string substringWithRange:NSMakeRange(yyuoffset,yyuleng)];
- // NSLog(@"del: %@", delimString);
- delimLength = yyuleng;
- break;
- case SP_SQL_TOKEN_COMPOUND_END:
- [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, yyuoffset+yyuleng-lastFoundToken]];
- break;
- default:
- continue;
- }
- if(token<SP_SQL_TOKEN_IGNORE)
- {
- lastFoundToken = yyuoffset+yyuleng;
- // ignore sinlge comment lines at the very beginning of a query
- if(commentStart == lastFoundToken)
- lastFoundToken += commentLength;
- }
- }
+ nextIndex = (long)(*firstOccOfChar)(self, @selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, NO, YES);
+ if (nextIndex == NSNotFound)
+ break;
- // add the last text chunk as query
- if(lastFoundToken+1<[self length])
- [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, [self length]-lastFoundToken-delimLength]];
+ stringIndex += 1;
+ // Ignore a delimiter command and check range length
+ queryLength = nextIndex - stringIndex - delimiterLength;
+ if(!isDelimiterCommand && queryLength > 0)
+ [resultsArray addObject:[NSValue valueWithRange:NSMakeRange(stringIndex, queryLength)]];
+ if(isDelimiterCommand) isDelimiterCommand = NO;
+ stringIndex = nextIndex;
+ }
+
+ // Add the end of the string after the previously matched character where appropriate.
+ if (stringIndex + 1 < [string length])
+ [resultsArray addObject:[NSValue valueWithRange:NSMakeRange(stringIndex + 1, [string length] - stringIndex - 1)]];
return resultsArray;
}
@@ -526,10 +488,7 @@ TO_BUFFER_STATE to_scan_string (const char *);
return [self firstOccurrenceOfCharacter:character afterIndex:startIndex skippingBrackets:NO ignoringQuotedStrings:ignoreQuotedStrings];
}
-
-/*
- * A method intended for use by the functions above.
- */
+
- (long) firstOccurrenceOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
{
long currentStringIndex, quotedStringEndIndex;
@@ -605,6 +564,130 @@ TO_BUFFER_STATE to_scan_string (const char *);
}
/*
+ * Look for the first occurence of a char and reset the split char on runtime
+ * via “delimiter” command for splitSqlStringIntoRangesByCharacter: and splitSqlStringByCharacter.
+ */
+- (long) firstOccurrenceInSqlOfCharacter:(unichar)character afterIndex:(long)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings
+{
+ long currentStringIndex, quotedStringEndIndex;
+ unichar currentCharacter;
+ long stringLength = [string length];
+ int bracketingLevel = 0;
+
+ // Cache frequently used selectors, avoiding dynamic binding overhead
+ IMP charAtIndex = [self methodForSelector:@selector(charAtIndex:)];
+ IMP endIndex = [self methodForSelector:@selector(endIndexOfStringQuotedByCharacter:startingAtIndex:)];
+
+ // Sanity check inputs
+ if (startIndex < -1) startIndex = -1;
+
+ // Walk along the string, processing characters
+ for (currentStringIndex = startIndex + 1; currentStringIndex < stringLength; currentStringIndex++) {
+ currentCharacter = (unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex);
+
+ // Check for the ending character, and if it has been found and quoting/brackets is valid, return.
+ // no “delimiter” is set by the user
+ if (charIsDelimiter)
+ {
+ if(currentCharacter == character)
+ if (!skipBrackets || bracketingLevel <= 0)
+ return currentStringIndex;
+ }
+ // a “delimiter” other than 'character' is set by the user
+ else
+ {
+ if([[self substringWithRange:NSMakeRange(currentStringIndex - delimiterLength, delimiterLength + 1)] isEqualToString:delimiter])
+ if (!skipBrackets || bracketingLevel <= 0)
+ return currentStringIndex;
+ }
+
+ // Process strings and comments as appropriate
+ switch (currentCharacter) {
+
+ // When quote characters are encountered and strings are not being ignored, walk to the end of the quoted string.
+ case '\'':
+ case '"':
+ case '`':
+ if (!ignoreQuotedStrings) break;
+ quotedStringEndIndex = (long)(*endIndex)(self, @selector(endIndexOfStringQuotedByCharacter:startingAtIndex:), currentCharacter, currentStringIndex+1);
+ if (quotedStringEndIndex == NSNotFound) {
+ return NSNotFound;
+ }
+ currentStringIndex = quotedStringEndIndex;
+ break;
+
+ // For opening brackets increment the bracket count
+ case '(':
+ bracketingLevel++;
+ break;
+
+ // For closing brackets decrement the bracket count
+ case ')':
+ bracketingLevel--;
+
+ // For comments starting "--[\s]", ensure the start syntax is valid before proceeding.
+ case '-':
+ if (stringLength < currentStringIndex + 2) break;
+ if ((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+1) != '-') break;
+ if (![[NSCharacterSet whitespaceCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+2)]) break;
+ currentStringIndex = [self endIndexOfCommentOfType:SPDoubleDashComment startingAtIndex:currentStringIndex];
+ break;
+
+ case '#':
+ currentStringIndex = [self endIndexOfCommentOfType:SPHashComment startingAtIndex:currentStringIndex];
+ break;
+
+ // For comments starting "/*", ensure the start syntax is valid before proceeding.
+ case '/':
+ if (stringLength < currentStringIndex + 1) break;
+ if ((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+1) != '*') break;
+ currentStringIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex];
+ break;
+ case 'd':
+ case 'D': // only parse to “deli” because there's no default command which begins with it; then check via regex
+ // Check for length of “elimiter x\s”
+ if (stringLength >= currentStringIndex + 11) {
+ // Check for “(^|\s)delimiter”
+ if(currentStringIndex == 0
+ || (currentStringIndex && [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex-1)])) {
+ NSArray *delimiterString;
+ switch((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+1)) {
+ case 'e':
+ case 'E':
+ switch((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+2)) {
+ case 'l':
+ case 'L':
+ switch((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+3)) {
+ case 'i':
+ case 'I':
+ if([self isMatchedByRegex:@"^(delimiter[ \\t]+(\\S+))(?=\\s)"
+ options:RKLCaseless
+ inRange:NSMakeRange(currentStringIndex, stringLength - currentStringIndex)
+ error:nil]) {
+ isDelimiterCommand = YES;
+ delimiterString = [[self arrayOfCaptureComponentsMatchedByRegex:@"(?i)^(delimiter[ \\t]+(\\S+))(?=\\s)"
+ range:NSMakeRange(currentStringIndex, stringLength - currentStringIndex)] objectAtIndex:0];
+ delimiter = [delimiterString objectAtIndex:2];
+ delimiterLength = [delimiter length] - 1;
+ charIsDelimiter = ([delimiter isEqualToString:[NSString stringWithFormat:@"%C", character]]);
+ return currentStringIndex + [[delimiterString objectAtIndex:1] length] - delimiterLength;
+ }
+ default: break;
+ }
+ default: break;
+ }
+ default: break;
+ }
+ }
+ }
+ }
+ }
+
+ // If no matches have been made in this string, return NSNotFound.
+ return NSNotFound;
+}
+
+/*
* A method intended for use by the functions above.
*/
- (long) endIndexOfStringQuotedByCharacter:(unichar)quoteCharacter startingAtIndex:(long)index
@@ -753,8 +836,6 @@ TO_BUFFER_STATE to_scan_string (const char *);
[self clearCharCache];
}
-
-
/* Required and primitive methods to allow subclassing class cluster */
#pragma mark -
- (id) init {
@@ -853,3 +934,76 @@ TO_BUFFER_STATE to_scan_string (const char *);
}
@end
+
+
+/*
+ * As splitStringByCharacter: ..., but allows control over quoting
+ * - it recognises CREATE ... BEGIN ... END statements
+ * - it can detect a SINGLE SQL statement in between
+ * delimiter foo ... foo delimiter ;
+ * ['delimiter ;' MUST be given!]
+ * - it returns an array of ranges (as NSString "{loc, length}").
+ * FromPosition: is needed if a subrange is passed to sync the ranges
+ * according to the CQ textView ones.
+ */
+// - (NSArray *) splitStringIntoRangesOfSQLQueries
+// {
+// return [self splitStringIntoRangesOfSQLQueriesFromPosition:0];
+// }
+// - (NSArray *) splitStringIntoRangesOfSQLQueriesFromPosition:(long)position
+// {
+// NSMutableArray *resultsArray = [NSMutableArray array];
+//
+// //initialise flex
+// yyuoffset = 0; yyuleng = 0;
+// to_switch_to_buffer(to_scan_string([string UTF8String]));
+//
+// unsigned long token;
+// unsigned long lastFoundToken = 0;
+// unsigned long delimLength = 0;
+// unsigned long commentStart = 0;
+// unsigned long commentLength = 0;
+//
+// NSString *delimString;
+//
+// //now loop through all queries
+// while (token=tolex()){
+// switch (token) {
+// case SP_SQL_TOKEN_SEMICOLON:
+// [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, yyuoffset-lastFoundToken]];
+// break;
+// case SP_SQL_TOKEN_SINGLE_LINE_COMMENT:
+// commentStart = yyuoffset+position;
+// commentLength = yyuleng;
+// break;
+// case SP_SQL_TOKEN_DELIM_END:
+// [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, yyuoffset-lastFoundToken-delimLength]];
+// delimLength = 0;
+// delimString = nil;
+// break;
+// case SP_SQL_TOKEN_DELIM_VALUE:
+// delimString = [string substringWithRange:NSMakeRange(yyuoffset,yyuleng)];
+// // NSLog(@"del: %@", delimString);
+// delimLength = yyuleng;
+// break;
+// case SP_SQL_TOKEN_COMPOUND_END:
+// [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, yyuoffset+yyuleng-lastFoundToken]];
+// break;
+// default:
+// continue;
+// }
+// if(token<SP_SQL_TOKEN_IGNORE)
+// {
+// lastFoundToken = yyuoffset+yyuleng;
+// // ignore sinlge comment lines at the very beginning of a query
+// if(commentStart == lastFoundToken)
+// lastFoundToken += commentLength;
+// }
+// }
+//
+// // add the last text chunk as query
+// if(lastFoundToken+1<[self length])
+// [resultsArray addObject:[NSString stringWithFormat:@"{%d,%d}", lastFoundToken+position, [self length]-lastFoundToken-delimLength]];
+//
+// return resultsArray;
+// }
diff --git a/Source/TableDump.m b/Source/TableDump.m
index 2d9b60ca..2fe99121 100644
--- a/Source/TableDump.m
+++ b/Source/TableDump.m
@@ -453,8 +453,8 @@
[singleProgressBar startAnimation:self];
//get array with an object for each mysql-query
- queries = [dumpFile splitStringByCharacter:';'];
-
+ queries = [dumpFile splitSqlStringByCharacter:';'];
+
[singleProgressBar stopAnimation:self];
[singleProgressBar setUsesThreadedAnimation:NO];
[singleProgressBar setIndeterminate:NO];