From f880dea7369cb6ce57c552cd359f871bf7e2e414 Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Fri, 5 Feb 2010 01:51:17 +0000 Subject: - Rework SPSQLParser, extending DELIMITER support into all the original functions (off by default). Remove the forked "...Sql..." functions, as they're now duplicates, and switch CustomQuery to using the original methods. - TableDump imports can now process DELIMITERs correctly as a result. - Alter the TableDump display of tables etc to use TablesList as the source of information, and used cached lists where appropriate for a small speedup. Also means we gain consistent sorting. - Display procedures and functions in the toggleable list when exporting as SQL - Tweak the procedure and function export to only export selected items, and also to respect the "export drop syntax" and "export create syntax" checkboxes - Fix a crash when removing items from the TablesList resulted in an errorneous selection by deselecting all rows before deleting (and preemptively applying the same fix to TableContent) --- Source/CustomQuery.m | 12 +- Source/SPSQLParser.h | 38 +++- Source/SPSQLParser.m | 484 ++++++++++++++++++-------------------------------- Source/SPTableData.m | 4 +- Source/TableContent.m | 1 + Source/TableDump.h | 8 + Source/TableDump.m | 134 +++++++++----- Source/TablesList.m | 1 + 8 files changed, 313 insertions(+), 369 deletions(-) (limited to 'Source') diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index a891f35e..2d1dae4d 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -67,7 +67,8 @@ // Retrieve the custom query string and split it into separate SQL queries queryParser = [[SPSQLParser alloc] initWithString:[textView string]]; - queries = [queryParser splitSqlStringByCharacter:';']; + [queryParser setDelimiterSupport:YES]; + queries = [queryParser splitStringByCharacter:';']; [queryParser release]; oldThreadedQueryRange = [textView selectedRange]; @@ -135,7 +136,8 @@ // Otherwise, run the selected text. } else { queryParser = [[SPSQLParser alloc] initWithString:[[textView string] substringWithRange:selectedRange]]; - queries = [queryParser splitSqlStringByCharacter:';']; + [queryParser setDelimiterSupport:YES]; + queries = [queryParser splitStringByCharacter:';']; [queryParser release]; // Remember query start position for error highlighting @@ -965,7 +967,8 @@ // 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:';']]; + [customQueryParser setDelimiterSupport:YES]; + queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringIntoRangesByCharacter:';']]; numberOfQueries = [queries count]; if(currentQueryRanges) [currentQueryRanges release]; @@ -1078,7 +1081,8 @@ // Split the current text into ranges of queries customQueryParser = [[SPSQLParser alloc] initWithString:[[textView string] substringWithRange:NSMakeRange(position, [[textView string] length]-position)]]; - queries = [[NSArray alloc] initWithArray:[customQueryParser splitSqlStringIntoRangesByCharacter:';']]; + [customQueryParser setDelimiterSupport:YES]; + queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringIntoRangesByCharacter:';']]; [customQueryParser release]; // Check for a valid index diff --git a/Source/SPSQLParser.h b/Source/SPSQLParser.h index a3ae4224..1331d60f 100644 --- a/Source/SPSQLParser.h +++ b/Source/SPSQLParser.h @@ -64,11 +64,12 @@ NSInteger parsedToPosition; NSInteger charCacheStart; NSInteger charCacheEnd; - NSString *delimiter; - NSInteger delimiterLength; - BOOL charIsDelimiter; - BOOL isDelimiterCommand; BOOL ignoreCommentStrings; + + BOOL supportDelimiters; + NSString *delimiter; + NSUInteger delimiterLengthMinusOne; + BOOL lastMatchIsDelimiter; } typedef enum _SPCommentTypes { @@ -77,7 +78,23 @@ typedef enum _SPCommentTypes { SPCStyleComment = 2 } SPCommentType; -- (void) setIgnoringCommentStrings:(BOOL)ignoringCommentStrings; +/* + * Set whether comment strings should be ignored during parsing. + * Normally, comment strings are treated as dead space and ignored; + * for certain parsing operations, characters within comments need + * to be inspected, and setIgnoreCommentStrings can be set to YES to + * achieve this. + */ +- (void) setIgnoreCommentStrings:(BOOL)ignoringCommentStrings; + +/* + * Set whether DELIMITER support should be enabled while parsing. + * This is off by default; when switched on, delimiters commands will + * be parsed out and not returned to the calling class, and any active + * delimiter statements will be used to override the supplied character + * for many commands. + */ +- (void) setDelimiterSupport:(BOOL)shouldSupportDelimiters; /* * Removes comments within the current string, trimming "#", "--[/s]", and "⁄* *⁄" style strings. @@ -217,8 +234,13 @@ typedef enum _SPCommentTypes { */ - (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings; -- (NSArray *) splitSqlStringByCharacter:(unichar)character; -- (NSArray *) splitSqlStringIntoRangesByCharacter:(unichar)character; +/* + * As splitStringByCharacter:, but returning only the ranges of queries, stored as NSValues. + * Quoted strings are automatically ignored when looking for the characters. + * SQL comments are automatically ignored when looking for the characters. + * Returns an array with one range covering the entire string if the supplied character is not found. + */ +- (NSArray *) splitStringIntoRangesByCharacter:(unichar)character; /* * Methods used internally by this class to power the methods above: @@ -226,7 +248,6 @@ typedef enum _SPCommentTypes { - (NSUInteger) firstOccurrenceOfCharacter:(unichar)character ignoringQuotedStrings:(BOOL)ignoreQuotedStrings; - (NSUInteger) firstOccurrenceOfCharacter:(unichar)character afterIndex:(NSInteger)startIndex ignoringQuotedStrings:(BOOL)ignoreQuotedStrings; - (NSUInteger) firstOccurrenceOfCharacter:(unichar)character afterIndex:(NSInteger)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings; -- (NSUInteger) firstOccurrenceInSqlOfCharacter:(unichar)character afterIndex:(NSInteger)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings; - (NSUInteger) endIndexOfStringQuotedByCharacter:(unichar)quoteCharacter startingAtIndex:(NSInteger)index; - (NSUInteger) endIndexOfCommentOfType:(SPCommentType)commentType startingAtIndex:(NSInteger)index; @@ -250,6 +271,7 @@ typedef enum _SPCommentTypes { - (id) initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding; - (id) initWithFormat:(NSString *)format, ...; - (id) initWithFormat:(NSString *)format arguments:(va_list)argList; +- (void) initSQLExtensions; - (NSUInteger) length; - (unichar) characterAtIndex:(NSUInteger)index; - (id) description; diff --git a/Source/SPSQLParser.m b/Source/SPSQLParser.m index c93f3198..fab0ab7f 100644 --- a/Source/SPSQLParser.m +++ b/Source/SPSQLParser.m @@ -42,11 +42,24 @@ TO_BUFFER_STATE to_scan_string (const char *); */ @implementation SPSQLParser : NSMutableString -- (void)setIgnoringCommentStrings:(BOOL)ignoringCommentStrings +/* + * Control whether comment strings should be skipped during parsing. + */ +- (void)setIgnoreCommentStrings:(BOOL)ignoringCommentStrings { ignoreCommentStrings = ignoringCommentStrings; } +/* + * Control whether DELIMITER commands are recognised and used to override + * supported characters. + */ +- (void) setDelimiterSupport:(BOOL)shouldSupportDelimiters +{ + supportDelimiters = shouldSupportDelimiters; +} + + /* * Removes comments within the current string, trimming "#", "--[/s]", and "⁄* *⁄" style strings. */ @@ -167,10 +180,12 @@ TO_BUFFER_STATE to_scan_string (const char *); */ - (BOOL) trimToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings { - NSUInteger stringIndex; + NSUInteger stringIndex = -1; // Get the first occurrence of the specified character, returning NO if not found - stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings]; + do { + stringIndex = [self firstOccurrenceOfCharacter:character afterIndex:stringIndex ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && stringIndex != NSNotFound); if (stringIndex == NSNotFound) return NO; // If it has been found, trim the string appropriately and return YES @@ -193,14 +208,18 @@ TO_BUFFER_STATE to_scan_string (const char *); * are ignored. */ - (NSString *) stringToCharacter:(unichar)character inclusively:(BOOL)inclusive ignoringQuotedStrings:(BOOL)ignoreQuotedStrings { - NSUInteger stringIndex; + NSUInteger stringIndex = -1; + NSUInteger returnFromPosition; // Get the first occurrence of the specified character, returning nil if not found - stringIndex = [self firstOccurrenceOfCharacter:character ignoringQuotedStrings:ignoreQuotedStrings]; + do { + returnFromPosition = stringIndex + 1; + stringIndex = [self firstOccurrenceOfCharacter:character afterIndex:stringIndex ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && stringIndex != NSNotFound); if (stringIndex == NSNotFound) return nil; // If it has been found, return the appropriate string range - return [string substringWithRange:NSMakeRange(0, stringIndex + (inclusive?1:0))]; + return [string substringWithRange:NSMakeRange(returnFromPosition, stringIndex + (inclusive?1:0) - returnFromPosition)]; } @@ -220,7 +239,8 @@ TO_BUFFER_STATE to_scan_string (const char *); */ - (NSString *) trimAndReturnStringToCharacter:(unichar)character trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn ignoringQuotedStrings:(BOOL)ignoreQuotedStrings { - NSUInteger stringIndex; + NSUInteger returnFromPosition; + NSUInteger stringIndex = 0; NSString *resultString; if (character != parsedToChar) { @@ -229,11 +249,14 @@ TO_BUFFER_STATE to_scan_string (const char *); } // Get the first occurrence of the specified character, returning nil if it could not be found - stringIndex = [self firstOccurrenceOfCharacter:character afterIndex:parsedToPosition ignoringQuotedStrings:ignoreQuotedStrings]; + do { + returnFromPosition = stringIndex; + stringIndex = [self firstOccurrenceOfCharacter:character afterIndex:parsedToPosition ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && stringIndex != NSNotFound); if (stringIndex == NSNotFound) return nil; // Select the appropriate string range, truncate the current string, and return the selected string - resultString = [NSString stringWithString:[string substringWithRange:NSMakeRange(0, stringIndex + (inclusiveReturn?1:0))]]; + resultString = [NSString stringWithString:[string substringWithRange:NSMakeRange(returnFromPosition, stringIndex + (inclusiveReturn?1:0) - returnFromPosition)]]; [self deleteCharactersInRange:NSMakeRange(0, stringIndex + (inclusiveTrim?1:0))]; return resultString; } @@ -275,14 +298,19 @@ TO_BUFFER_STATE to_scan_string (const char *); */ - (NSString *) stringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter inclusively:(BOOL)inclusive skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings { - NSUInteger fromCharacterIndex, toCharacterIndex; + NSUInteger toCharacterIndex, fromCharacterIndex = -1; // Look for the first occurrence of the from: character - fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:-1 skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + do { + fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && fromCharacterIndex != NSNotFound); if (fromCharacterIndex == NSNotFound) return nil; // Look for the first/balancing occurrence of the to: character - toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + toCharacterIndex = fromCharacterIndex; + do { + toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:toCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && toCharacterIndex != NSNotFound); if (toCharacterIndex == NSNotFound) return nil; // Return the correct part of the string. @@ -326,15 +354,20 @@ TO_BUFFER_STATE to_scan_string (const char *); */ - (NSString *) trimAndReturnStringFromCharacter:(unichar)fromCharacter toCharacter:(unichar)toCharacter trimmingInclusively:(BOOL)inclusiveTrim returningInclusively:(BOOL)inclusiveReturn skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings { - NSUInteger fromCharacterIndex, toCharacterIndex; + NSUInteger fromCharacterIndex = -1, toCharacterIndex; NSString *resultString; // Look for the first occurrence of the from: character - fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:-1 skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + do { + fromCharacterIndex = [self firstOccurrenceOfCharacter:fromCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && fromCharacterIndex != NSNotFound); if (fromCharacterIndex == NSNotFound) return nil; // Look for the first/balancing occurrence of the to: character - toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:fromCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + toCharacterIndex = fromCharacterIndex; + do { + toCharacterIndex = [self firstOccurrenceOfCharacter:toCharacter afterIndex:toCharacterIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; + } while (lastMatchIsDelimiter && toCharacterIndex != NSNotFound); if (toCharacterIndex == NSNotFound) return nil; // Select the correct part of the string, truncate the current string, and return the selected string. @@ -376,118 +409,90 @@ TO_BUFFER_STATE to_scan_string (const char *); - (NSArray *) splitStringByCharacter:(unichar)character skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings { NSMutableArray *resultsArray = [NSMutableArray array]; - NSInteger stringIndex = -1; - NSUInteger nextIndex = 0; - - // Walk through the string finding the character to split by, and add all strings to the array. - while (1) { - nextIndex = [self firstOccurrenceOfCharacter:character afterIndex:stringIndex skippingBrackets:skipBrackets ignoringQuotedStrings:ignoreQuotedStrings]; - if (nextIndex == NSNotFound) { - break; - } - - [resultsArray addObject:[string substringWithRange:NSMakeRange(stringIndex+1, nextIndex - stringIndex - 1)]]; - stringIndex = nextIndex; - } - - // Add the end of the string after the previously matched character where appropriate. - if (stringIndex + 1 < [string length]) { - [resultsArray addObject:[string substringWithRange:NSMakeRange(stringIndex+1, [string length] - stringIndex - 1)]]; - } - - return resultsArray; -} - -/* - * As splitStringByCharacter: ..., but allows control over both bracketing and quoting. - */ -- (NSArray *) splitSqlStringByCharacter:(unichar)character -{ - NSMutableArray *resultsArray = [NSMutableArray arrayWithCapacity:2000]; - NSInteger stringIndex = -1; NSUInteger nextIndex = 0; NSInteger 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 firstOccOfChar = [self methodForSelector:@selector(firstOccurrenceOfCharacter: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 = (NSUInteger)(*firstOccOfChar)(self, @selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, NO, YES); + nextIndex = (NSUInteger)(*firstOccOfChar)(self, @selector(firstOccurrenceOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, skipBrackets, ignoreQuotedStrings); + while (lastMatchIsDelimiter && nextIndex != NSNotFound) { + stringIndex = nextIndex; + nextIndex = (NSUInteger)(*firstOccOfChar)(self, @selector(firstOccurrenceOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, skipBrackets, ignoreQuotedStrings); + } 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; + // Add queries to the result array if they have a length + stringIndex++; + queryLength = nextIndex - stringIndex - delimiterLengthMinusOne; + if (queryLength > 0) + CFArrayAppendValue((CFMutableArrayRef)resultsArray, (NSString *)(*subString)(string, @selector(substringWithRange:), NSMakeRange(stringIndex, queryLength))); + stringIndex = nextIndex; } - // Add the end of the string after the previously matched character where appropriate - // if it does not contain only white space characters and if the last query is not a - // "delimiter" statement to avoid unnecessary error messages. + // Add the end of the string after the previously matched character where appropriate. if (stringIndex + 1 < [string length]) { - NSString *lastQuery = [[string substringFromIndex:stringIndex + 1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if([lastQuery length] && ![lastQuery isMatchedByRegex:@"(?i)^\\s*delimiter\\s+\\S+"]) - [resultsArray addObject:lastQuery]; + NSString *finalQuery = [[string substringFromIndex:stringIndex + 1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (supportDelimiters && [finalQuery isMatchedByRegex:@"(?i)^\\s*delimiter\\s+\\S+"]) + finalQuery = nil; + if ([finalQuery length]) + [resultsArray addObject:finalQuery]; } - + return resultsArray; } + /* - * As splitStringByCharacter: but it returns only the ranges of queries as NSValues + * As splitStringByCharacter:, but returning only the ranges of queries, stored as NSValues. */ -- (NSArray *) splitSqlStringIntoRangesByCharacter:(unichar)character +- (NSArray *) splitStringIntoRangesByCharacter:(unichar)character { - NSMutableArray *resultsArray = [NSMutableArray arrayWithCapacity:2000]; - + NSMutableArray *resultsArray = [NSMutableArray array]; NSInteger stringIndex = -1; NSUInteger nextIndex = 0; NSInteger 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(firstOccurrenceOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:)]; - IMP firstOccOfChar = [self methodForSelector:@selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:)]; - - // Walk through the string finding the character to split by, and add all strings to the array. + // Walk through the string finding the character to split by, and add all ranges to the array. while (1) { - - nextIndex = (NSUInteger)(*firstOccOfChar)(self, @selector(firstOccurrenceInSqlOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, NO, YES); + nextIndex = (NSUInteger)(*firstOccOfChar)(self, @selector(firstOccurrenceOfCharacter:afterIndex:skippingBrackets:ignoringQuotedStrings:), character, stringIndex, NO, YES); + while (lastMatchIsDelimiter && nextIndex != NSNotFound) { + stringIndex = nextIndex; + nextIndex = (NSUInteger)(*firstOccOfChar)(self, @selector(firstOccurrenceOfCharacter: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:[NSValue valueWithRange:NSMakeRange(stringIndex, queryLength)]]; - if(isDelimiterCommand) isDelimiterCommand = NO; + // Add ranges to the result array if they have a length + stringIndex++; + queryLength = nextIndex - stringIndex - delimiterLengthMinusOne; + if (queryLength > 0) + CFArrayAppendValue((CFMutableArrayRef)resultsArray, [NSValue valueWithRange:NSMakeRange(stringIndex, queryLength)]); + 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)]]; + stringIndex++; + if (stringIndex < [string length]) { + NSString *finalQuery = [[string substringFromIndex:stringIndex] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (supportDelimiters && [finalQuery isMatchedByRegex:@"(?i)^\\s*delimiter\\s+\\S+"]) + finalQuery = nil; + if ([finalQuery length]) + [resultsArray addObject:[NSValue valueWithRange:NSMakeRange(stringIndex, [string length] - stringIndex - delimiterLengthMinusOne)]]; + } return resultsArray; } + /* * A method intended for use by the functions above. */ @@ -512,10 +517,12 @@ TO_BUFFER_STATE to_scan_string (const char *); unichar currentCharacter; NSUInteger stringLength = [string length]; NSInteger bracketingLevel = 0; + lastMatchIsDelimiter = NO; // Cache frequently used selectors, avoiding dynamic binding overhead IMP charAtIndex = [self methodForSelector:@selector(charAtIndex:)]; IMP endIndex = [self methodForSelector:@selector(endIndexOfStringQuotedByCharacter:startingAtIndex:)]; + IMP substringWithRange = [self methodForSelector:@selector(substringWithRange:)]; // Sanity check inputs if (startIndex < -1) startIndex = -1; @@ -525,7 +532,15 @@ TO_BUFFER_STATE to_scan_string (const char *); 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. - if (currentCharacter == character) { + // If delimiter support is active and a delimiter is set, check for the delimiter + if (supportDelimiters && delimiter) { + if (currentStringIndex >= delimiterLengthMinusOne && [delimiter isEqualToString:(NSString *)(*substringWithRange)(self, @selector(substringWithRange:), NSMakeRange(currentStringIndex - delimiterLengthMinusOne, delimiterLengthMinusOne + 1))]) { + if (!skipBrackets || bracketingLevel <= 0) { + parsedToPosition = currentStringIndex; + return currentStringIndex; + } + } + } else if (currentCharacter == character) { if (!skipBrackets || bracketingLevel <= 0) { parsedToPosition = currentStringIndex; return currentStringIndex; @@ -577,140 +592,66 @@ TO_BUFFER_STATE to_scan_string (const char *); if ((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+1) != '*') break; currentStringIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex]; break; - } - } - - // If no matches have been made in this string, return NSNotFound. - parsedToPosition = stringLength - 1; - return NSNotFound; -} - -/* - * Look for the first occurence of a char and reset the split char on runtime - * via “delimiter” command for splitSqlStringIntoRangesByCharacter: and splitSqlStringByCharacter. - */ -- (NSUInteger) firstOccurrenceInSqlOfCharacter:(unichar)character afterIndex:(NSInteger)startIndex skippingBrackets:(BOOL)skipBrackets ignoringQuotedStrings:(BOOL)ignoreQuotedStrings -{ - NSUInteger currentStringIndex, quotedStringEndIndex; - unichar currentCharacter; - NSUInteger stringLength = [string length]; - NSInteger 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 = (NSUInteger)(*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 '#': - if(ignoreCommentStrings) break; - currentStringIndex = [self endIndexOfCommentOfType:SPHashComment startingAtIndex:currentStringIndex]; - break; - // For comments starting "/*", ensure the start syntax is valid before proceeding. - case '/': - if(ignoreCommentStrings) break; - if (stringLength < currentStringIndex + 1) break; - if ((unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex+1) != '*') break; - currentStringIndex = [self endIndexOfCommentOfType:SPCStyleComment startingAtIndex:currentStringIndex]; - break; + // Check for delimiter strings, by first checking letter-by-letter to "deli" for speed (as there's no default + // commands which start with it), and then switching to regex for simplicty. 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; + case 'D': + + // Only proceed if delimiter support is enabled and the remaining string is long enough, + // and that the "d" is the start of a word + if (supportDelimiters && stringLength >= currentStringIndex + 11 + && (currentStringIndex == 0 + || [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:(unichar)(long)(*charAtIndex)(self, @selector(charAtIndex:), currentStringIndex-1)])) + { + 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]) + { + + // Delimiter command found. Extract the delimiter string itself + NSArray *delimiterCommandParts = [[self arrayOfCaptureComponentsMatchedByRegex:@"(?i)^(delimiter[ \\t]+(\\S+))(?=\\s)" + range:NSMakeRange(currentStringIndex, stringLength - currentStringIndex)] objectAtIndex:0]; + delimiter = [delimiterCommandParts objectAtIndex:2]; + delimiterLengthMinusOne = [delimiter length] - 1; + parsedToPosition = currentStringIndex + [[delimiterCommandParts objectAtIndex:1] length]; + + // Drop back to standard non-delimiter mode if the delimiter has ended + if ([delimiter isEqualToString:[NSString stringWithFormat:@"%C", character]]) { + delimiter = nil; + delimiterLengthMinusOne = 0; } - default: break; - } + + // With the internal state updated, return the match, clearly marked as a delimiter + lastMatchIsDelimiter = YES; + return parsedToPosition; + } default: break; } default: break; } - } + default: break; } + } } } // If no matches have been made in this string, return NSNotFound. + parsedToPosition = stringLength - 1; return NSNotFound; } + /* * A method intended for use by the functions above. */ @@ -870,71 +811,52 @@ TO_BUFFER_STATE to_scan_string (const char *); if (self = [super init]) { string = [[NSMutableString string] retain]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; - ignoreCommentStrings = NO; - + [self initSQLExtensions]; return self; } - (id) initWithBytes:(const void *)bytes length:(NSUInteger)length encoding:(NSStringEncoding)encoding { if (self = [super init]) { string = [[NSMutableString alloc] initWithBytes:bytes length:length encoding:encoding]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return self; } - (id) initWithBytesNoCopy:(void *)bytes length:(NSUInteger)length encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)flag { if (self = [super init]) { string = [[NSMutableString alloc] initWithBytesNoCopy:bytes length:length encoding:encoding freeWhenDone:flag]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return self; } - (id) initWithCapacity:(NSUInteger)capacity { if (self = [super init]) { string = [[NSMutableString stringWithCapacity:capacity] retain]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return self; } - (id) initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)flag { if (self = [super init]) { string = [[NSMutableString alloc] initWithCharactersNoCopy:characters length:length freeWhenDone:flag]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return self; } - (id) initWithContentsOfFile:(id)path { - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; return [self initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; } - (id) initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)encoding error:(NSError **)error { if (self = [super init]) { string = [[NSMutableString alloc] initWithContentsOfFile:path encoding:encoding error:error]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return self; } - (id) initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding { if (self = [super init]) { string = [[NSMutableString alloc] initWithCString:nullTerminatedCString encoding:encoding]; } - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return self; } - (id) initWithFormat:(NSString *)format, ... { @@ -942,19 +864,26 @@ TO_BUFFER_STATE to_scan_string (const char *); va_start(argList, format); id str = [self initWithFormat:format arguments:argList]; va_end(argList); - parsedToChar = '\0'; - parsedToPosition = -1; - charCacheEnd = -1; + [self initSQLExtensions]; return str; } - (id) initWithFormat:(NSString *)format arguments:(va_list)argList { if (self = [super init]) { string = [[NSMutableString alloc] initWithFormat:format arguments:argList]; } + [self initSQLExtensions]; + return self; +} +- (void) initSQLExtensions { parsedToChar = '\0'; parsedToPosition = -1; charCacheEnd = -1; - return self; + ignoreCommentStrings = NO; + supportDelimiters = NO; + delimiter = nil; + delimiterLengthMinusOne = 0; + lastMatchIsDelimiter = NO; + } - (NSUInteger) length { return [string length]; @@ -971,6 +900,9 @@ TO_BUFFER_STATE to_scan_string (const char *); } - (void) setString:(NSString *)aString { [string setString:aString]; + delimiter = nil; + delimiterLengthMinusOne = 0; + lastMatchIsDelimiter = NO; [self clearCharCache]; } - (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)aString { @@ -983,76 +915,4 @@ TO_BUFFER_STATE to_scan_string (const char *); [super dealloc]; } -@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 #import +typedef enum _SPExportModes { + SPExportingSQL = 0, + SPExportingCSV = 1, + SPExportingXML = 2, + SPExportingDOT = 3 +} SPExportMode; + @interface TableDump : NSObject { IBOutlet id tableDocumentInstance; @@ -103,6 +110,7 @@ NSMutableArray *fieldMappingArray; NSMutableArray *fieldMappingButtonOptions; NSInteger fieldMappingCurrentRow; + NSUInteger exportMode; NSUserDefaults *prefs; BOOL progressCancelled; diff --git a/Source/TableDump.m b/Source/TableDump.m index b6f71c2c..c835b526 100644 --- a/Source/TableDump.m +++ b/Source/TableDump.m @@ -45,38 +45,42 @@ #pragma mark IBAction methods /** - * Get the tables in db + * Update the table lists with the list of tables, retrieved from the + * tablesList. If the user has pressed the reload button, trigger a reload + * from the server; otherwise used the cached lists. + * Retrieve only tables for all modes except SQL. */ - (IBAction)reloadTables:(id)sender { - MCPResult *queryResult; - NSInteger i; - - //get tables + + // Trigger a reload if necessary + if (sender != self) [tablesListInstance updateTables:self]; + + // Clear all existing tables [tables removeAllObjects]; - queryResult = (MCPResult *)[mySQLConnection listTables]; - - if ([queryResult numOfRows]) [queryResult dataSeek:0]; - NSMutableArray *unsortedTables = [NSMutableArray array]; - for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) { - [unsortedTables addObject:[[queryResult fetchRowAsArray] objectAtIndex:0]]; + + // For all modes, retrieve table and view names + NSArray *tablesAndViews = [tablesListInstance allTableAndViewNames]; + for (id itemName in tablesAndViews) { + [tables addObject:[NSMutableArray arrayWithObjects:[NSNumber numberWithBool:YES], itemName, [NSNumber numberWithInt:SP_TABLETYPE_TABLE], nil]]; } - - NSSortDescriptor *desc = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES selector:@selector(localizedCompare:)]; - NSArray *sortedTables = [unsortedTables sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]]; - [desc release]; - - for ( i = 0 ; i < [sortedTables count]; i++ ) { - [tables addObject: - [NSMutableArray arrayWithObjects: - [NSNumber numberWithBool:YES], - [sortedTables objectAtIndex:i], - nil]]; + + // For SQL only, add procedures and functions + if (exportMode == SPExportingSQL) { + NSArray *procedures = [tablesListInstance allProcedureNames]; + for (id procName in procedures) { + [tables addObject:[NSMutableArray arrayWithObjects:[NSNumber numberWithBool:YES], procName, [NSNumber numberWithInt:SP_TABLETYPE_PROC], nil]]; + } + NSArray *functions = [tablesListInstance allFunctionNames]; + for (id funcName in functions) { + [tables addObject:[NSMutableArray arrayWithObjects:[NSNumber numberWithBool:YES], funcName, [NSNumber numberWithInt:SP_TABLETYPE_FUNC], nil]]; + } } - - [exportDumpTableView reloadData]; - [exportMultipleCSVTableView reloadData]; - [exportMultipleXMLTableView reloadData]; + + // Update interface + if (exportMode == SPExportingSQL) [exportDumpTableView reloadData]; + else if (exportMode == SPExportingCSV) [exportMultipleCSVTableView reloadData]; + else if (exportMode == SPExportingXML) [exportMultipleXMLTableView reloadData]; } /** @@ -160,6 +164,7 @@ switch ( tag ) { case 5: // export dump + exportMode = SPExportingSQL; [self reloadTables:self]; file = [NSString stringWithFormat:@"%@_%@.sql", [tableDocumentInstance database], currentDate]; [savePanel setRequiredFileType:@"sql"]; @@ -169,6 +174,7 @@ // Export the full resultset for the currently selected table to a file in CSV format case 6: + exportMode = SPExportingCSV; file = [NSString stringWithFormat:@"%@.csv", [tableDocumentInstance table]]; [savePanel setAccessoryView:exportCSVView]; [csvFullStreamingSwitch setEnabled:YES]; @@ -177,12 +183,14 @@ // Export the full resultset for the currently selected table to a file in XML format case 7: + exportMode = SPExportingXML; file = [NSString stringWithFormat:@"%@.xml", [tableDocumentInstance table]]; contextInfo = @"exportTableContentAsXML"; break; // Export the current "browse" view to a file in CSV format case 8: + exportMode = SPExportingCSV; file = [NSString stringWithFormat:@"%@ view.csv", [tableDocumentInstance table]]; [savePanel setAccessoryView:exportCSVView]; [csvFullStreamingSwitch setEnabled:NO]; @@ -191,12 +199,14 @@ // Export the current "browse" view to a file in XML format case 9: + exportMode = SPExportingXML; file = [NSString stringWithFormat:@"%@ view.xml", [tableDocumentInstance table]]; contextInfo = @"exportBrowseViewAsXML"; break; // Export the current custom query result set to a file in CSV format case 10: + exportMode = SPExportingCSV; file = @"customresult.csv"; [savePanel setAccessoryView:exportCSVView]; [csvFullStreamingSwitch setEnabled:NO]; @@ -205,12 +215,14 @@ // Export the current custom query result set to a file in XML format case 11: + exportMode = SPExportingXML; file = @"customresult.xml"; contextInfo = @"exportCustomResultAsXML"; break; // Export multiple tables to a file in CSV format case 12: + exportMode = SPExportingCSV; [self reloadTables:self]; file = [NSString stringWithFormat:@"%@.csv", [tableDocumentInstance database]]; [savePanel setAccessoryView:exportMultipleCSVView]; @@ -219,6 +231,7 @@ // Export multiple tables to a file in XML format case 13: + exportMode = SPExportingXML; [self reloadTables:self]; file = [NSString stringWithFormat:@"%@.xml", [tableDocumentInstance database]]; [savePanel setAccessoryView:exportMultipleXMLView]; @@ -227,6 +240,7 @@ // graphviz dot file case 14: + exportMode = SPExportingDOT; [self reloadTables:self]; file = [NSString stringWithString:[tableDocumentInstance database]]; [savePanel setRequiredFileType:@"dot"]; @@ -606,6 +620,7 @@ // Read in the file in a loop sqlParser = [[SPSQLParser alloc] init]; + [sqlParser setDelimiterSupport:YES]; sqlDataBuffer = [[NSMutableData alloc] init]; importPool = [[NSAutoreleasePool alloc] init]; while (1) { @@ -1341,6 +1356,8 @@ NSArray *fieldNames; NSArray *theRow; NSMutableArray *selectedTables = [NSMutableArray array]; + NSMutableArray *selectedProcs = [NSMutableArray array]; + NSMutableArray *selectedFuncs = [NSMutableArray array]; NSMutableDictionary *viewSyntaxes = [NSMutableDictionary dictionary]; NSMutableString *metaString = [NSMutableString string]; NSMutableString *cellValue = [NSMutableString string]; @@ -1373,10 +1390,22 @@ [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; - // Copy over the selected table names into a table in preparation for iteration + // Copy over the selected item names into tables in preparation for iteration + NSMutableArray *targetArray; for ( i = 0 ; i < [tables count] ; i++ ) { if ( [NSArrayObjectAtIndex(NSArrayObjectAtIndex(tables, i), 0) boolValue] ) { - [selectedTables addObject:[NSString stringWithString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(tables, i), 1)]]; + switch ([NSArrayObjectAtIndex(NSArrayObjectAtIndex(tables, i), 2) intValue]) { + case SP_TABLETYPE_PROC: + targetArray = selectedProcs; + break; + case SP_TABLETYPE_FUNC: + targetArray = selectedFuncs; + break; + default: + targetArray = selectedTables; + break; + } + [targetArray addObject:[NSString stringWithString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(tables, i), 1)]]; } } @@ -1675,8 +1704,26 @@ // Add an additional separator between tables [fileHandle writeData:[[NSString stringWithString:@"\n\n"] dataUsingEncoding:NSUTF8StringEncoding]]; } - - for (NSString *procedureType in [NSArray arrayWithObjects:@"PROCEDURE", @"FUNCTION", nil]) { + + // Process any deferred views, adding commands to delete the placeholder tables and add the actual views + viewSyntaxEnumerator = [viewSyntaxes keyEnumerator]; + while (tableName = [viewSyntaxEnumerator nextObject]) { + [metaString setString:@"\n\n"]; + [metaString appendFormat:@"DROP TABLE %@;\n", [tableName backtickQuotedString]]; + [metaString appendFormat:@"%@;\n", [viewSyntaxes objectForKey:tableName]]; + [fileHandle writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + } + + // Export procedures and functions + for (NSString *procedureType in [NSArray arrayWithObjects:@"PROCEDURE", @"FUNCTION", nil]) { + + // Retrieve the array of selected procedures or functions, and skip export if not selected + NSMutableArray *selectedItems; + if ([procedureType isEqualToString:@"PROCEDURE"]) selectedItems = selectedProcs; + else selectedItems = selectedFuncs; + if (![selectedItems count]) continue; + + // Retrieve the definitions queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"/*!50003 SHOW %@ STATUS WHERE `Db` = %@ */;", procedureType, [[tableDocumentInstance database] tickQuotedString]]]; @@ -1689,14 +1736,24 @@ [[tableDocumentInstance database] tickQuotedString]]]; [metaString appendString:@"--\n"]; [metaString appendString:@"DELIMITER ;;\n"]; - + + // Loop through the definitions, exporting if enabled for (int t=0; t<[queryResult numOfRows]; t++) { NSDictionary *proceduresList = [[NSDictionary alloc] initWithDictionary:[queryResult fetchRowAsDictionary]]; NSString *procedureName = [NSString stringWithFormat:@"%@", [proceduresList objectForKey:@"Name"]]; + + // Only proceed if the item was selected for export + if (![selectedItems containsObject:procedureName]) continue; + + // Add the "drop" command if specified in the export dialog + if ([addDropTableSwitch state] == NSOnState) { + [metaString appendString:[NSString stringWithFormat:@"/*!50003 DROP %@ IF EXISTS %@ */;;\n", + procedureType, + [procedureName backtickQuotedString]]]; + } - [metaString appendString:[NSString stringWithFormat:@"/*!50003 DROP %@ IF EXISTS %@ */;;\n", - procedureType, - [procedureName backtickQuotedString]]]; + // Only continue if the "create syntax" is specified in the export dialog + if ([addCreateTableSwitch state] == NSOffState) continue; //Definer is user@host but we need to escape it to `user`@`host` NSArray *procedureDefiner = [[proceduresList objectForKey:@"Definer"] componentsSeparatedByString:@"@"]; @@ -1748,15 +1805,6 @@ } - // Process any deferred views, adding commands to delete the placeholder tables and add the actual views - viewSyntaxEnumerator = [viewSyntaxes keyEnumerator]; - while (tableName = [viewSyntaxEnumerator nextObject]) { - [metaString setString:@"\n\n"]; - [metaString appendFormat:@"DROP TABLE %@;\n", [tableName backtickQuotedString]]; - [metaString appendFormat:@"%@;\n", [viewSyntaxes objectForKey:tableName]]; - [fileHandle writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; - } - // Restore unique checks, foreign key checks, and other settings saved at the start [metaString setString:@"\n\n\n"]; [metaString appendString:@"/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"]; diff --git a/Source/TablesList.m b/Source/TablesList.m index cff345fc..a81c6d75 100644 --- a/Source/TablesList.m +++ b/Source/TablesList.m @@ -1750,6 +1750,7 @@ - (void)removeTable { NSIndexSet *indexes = [tablesListView selectedRowIndexes]; + [tablesListView selectRowIndexes:[NSIndexSet indexSet] byExtendingSelection:NO]; // get last index NSUInteger currentIndex = [indexes lastIndex]; -- cgit v1.2.3