diff options
author | Bibiko <bibiko@eva.mpg.de> | 2009-06-04 14:11:55 +0000 |
---|---|---|
committer | Bibiko <bibiko@eva.mpg.de> | 2009-06-04 14:11:55 +0000 |
commit | fa3a82bd89eee142c575e13e47eb6a7bd5e1fc89 (patch) | |
tree | ee2536c1bafc17235d0d9d68ed5df3e100236319 | |
parent | f3e421d2e4ba9ec54878968c34044dce89dc6f09 (diff) | |
download | sequelpro-fa3a82bd89eee142c575e13e47eb6a7bd5e1fc89.tar.gz sequelpro-fa3a82bd89eee142c575e13e47eb6a7bd5e1fc89.tar.bz2 sequelpro-fa3a82bd89eee142c575e13e47eb6a7bd5e1fc89.zip |
• added support for the “delimiter” command
- added
(NSArray *) splitSqlStringByCharacter:(unichar)character;
(NSArray *) splitSqlStringIntoRangesByCharacter:(unichar)character;
(long) firstOccurrenceInSqlOfCharacter:
to the SQLParser which recognize a “delimiter” command
• queryAtPosition now works with ranges to speed it up
- the current query ranges resp. the just activated query range are cached in order to avoid parsing if the user only navigates through the textView buffer, or if the user calls Run Prev/Current Query only
• the "import dump" function makes usage of that new “delimiter” support
- i.e. dumps with procs/funcs declaration could be imported
IMPORTANT: Please check the new SQLParser exhaustively in order to prove that new approach
-rw-r--r-- | Source/CustomQuery.h | 2 | ||||
-rw-r--r-- | Source/CustomQuery.m | 206 | ||||
-rw-r--r-- | Source/SPSQLParser.h | 27 | ||||
-rw-r--r-- | Source/SPSQLParser.m | 380 | ||||
-rw-r--r-- | Source/TableDump.m | 4 |
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]; |