diff options
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CustomQuery.h | 4 | ||||
-rw-r--r-- | Source/CustomQuery.m | 237 |
2 files changed, 209 insertions, 32 deletions
diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h index 770a4257..42803215 100644 --- a/Source/CustomQuery.h +++ b/Source/CustomQuery.h @@ -64,6 +64,8 @@ CMMCPConnection *mySQLConnection; NSString *usedQuery; + + int queryStartPosition; } // IBAction methods @@ -83,6 +85,8 @@ // Query actions - (void)performQueries:(NSArray *)queries; - (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind; +- (NSRange)queryTextRangeAtPosition:(long)position lookBehind:(BOOL *)doLookBehind; +- (NSRange)queryTextRangeForQuery:(int)anIndex startPosition:(long)position; // Accessors - (NSArray *)currentResult; diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index 63f6986b..6929c99e 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -57,6 +57,9 @@ NSRange curRange = [textView selectedRange]; // Unselect a selection if given to avoid interferring with error highlighting [textView setSelectedRange:NSMakeRange(curRange.location, 0)]; + // Reset queryStartPosition + queryStartPosition = 0; + [self performQueries:queries]; // If no error was selected reconstruct a given selection if([textView selectedRange].length == 0) @@ -361,6 +364,8 @@ sets the tableView columns corresponding to the mysql-result NSMutableString *errors = [NSMutableString string]; int i, totalQueriesRun = 0, totalAffectedRows = 0; float executionTime = 0; + int firstErrorOccuredInQuery = -1; + BOOL suppressErrorSheet = NO; // Notify listeners that a query has started [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self]; @@ -394,12 +399,44 @@ sets the tableView columns corresponding to the mysql-result // Store any error messages if ( ![[mySQLConnection getLastErrorMessage] isEqualToString:@""] ) { - + // If the query errored, append error to the error log for display at the end if ( [queries count] > 1 ) { - [errors appendString:[NSString stringWithFormat:NSLocalizedString(@"[ERROR in query %d] %@\n", @"error text when multiple custom query failed"), + if(firstErrorOccuredInQuery == -1) + firstErrorOccuredInQuery = i+1; + + if(!suppressErrorSheet) + { + // Update error text for the user + [errors appendString:[NSString stringWithFormat:NSLocalizedString(@"[ERROR in query %d] %@\n", @"error text when multiple custom query failed"), i+1, [mySQLConnection getLastErrorMessage]]]; + [errorText setStringValue:errors]; + // ask the user to continue after detecting an error + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle:NSLocalizedString(@"Run All", @"run all button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Continue", @"continue button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Stop", @"stop button")]; + [alert setMessageText:NSLocalizedString(@"MySQL Error", @"mysql error message")]; + [alert setInformativeText:[mySQLConnection getLastErrorMessage]]; + [alert setAlertStyle:NSWarningAlertStyle]; + int choice = [alert runModal]; + switch (choice){ + case NSAlertFirstButtonReturn: + suppressErrorSheet = YES; + case NSAlertSecondButtonReturn: + break; + default: + if(i < [queries count]-1) // output that message only if it was not the last one + [errors appendString:NSLocalizedString(@"Execution stopped!\n", @"execution stopped message")]; + i = [queries count]; // break for loop; for safety reasons stop the execution of the following queries + } + + } else { + [errors appendString:[NSString stringWithFormat:NSLocalizedString(@"[ERROR in query %d] %@\n", @"error text when multiple custom query failed"), + i+1, + [mySQLConnection getLastErrorMessage]]]; + } } else { [errors setString:[mySQLConnection getLastErrorMessage]]; } @@ -416,7 +453,7 @@ sets the tableView columns corresponding to the mysql-result [errors setString:[mySQLConnection getLastErrorMessage]]; } -//put result in array + //put result in array [queryResult release]; queryResult = nil; if ( nil != theResult ) @@ -429,7 +466,7 @@ sets the tableView columns corresponding to the mysql-result queryResult = [[NSArray arrayWithArray:tempResult] retain]; } -//add query to history + //add query to history [queryHistoryButton insertItemWithTitle:[queries componentsJoinedByString:@"; "] atIndex:1]; while ( [queryHistoryButton numberOfItems] > [[prefs objectForKey:@"CustomQueryMaxHistoryItems"] intValue] + 1 ) { [queryHistoryButton removeItemAtIndex:[queryHistoryButton numberOfItems]-1]; @@ -446,7 +483,7 @@ sets the tableView columns corresponding to the mysql-result [errorText setStringValue:errors]; // select the line x of the first error if error message contains "at line x" NSError *err1 = NULL; - NSRange errorLineNumberRange = [errors rangeOfRegex:@"at line ([0-9]+)" options:RKLNoOptions inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1]; + NSRange errorLineNumberRange = [errors rangeOfRegex:@"([0-9]+)$" options:RKLNoOptions inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1]; if(errorLineNumberRange.length) // if a line number was found { // Get the line number @@ -454,54 +491,67 @@ sets the tableView columns corresponding to the mysql-result [textView selectLineNumber:errorAtLine ignoreLeadingNewLines:YES]; // Check for near message - NSRange errorNearMessageRange = [errors rangeOfRegex:@"use near '(.*?)'" options:(RKLMultiline|RKLDotAll) inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1]; + NSRange errorNearMessageRange = [errors rangeOfRegex:@" '(.*?)' " options:(RKLMultiline|RKLDotAll) inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1]; if(errorNearMessageRange.length) // if a "near message" was found { - // Get the line of the first error via the current selected line - NSRange lineRange = [[textView string] lineRangeForRange:NSMakeRange([textView selectedRange].location, 0)]; - // Build the range to search for nearMessage (beginning from the error line to try to avoid mismatching) - NSRange theRange = NSMakeRange(lineRange.location, [[textView string] length]-lineRange.location); + // Build the range to search for nearMessage (beginning from queryStartPosition to try to avoid mismatching) + NSRange theRange = NSMakeRange(queryStartPosition, [[textView string] length]-queryStartPosition); // Get the range in textView of the near message NSRange textNearMessageRange = [[[textView string] substringWithRange:theRange] rangeOfString:[errors substringWithRange:errorNearMessageRange] options:NSLiteralSearch]; - // Correct the near message range - textNearMessageRange = NSMakeRange(textNearMessageRange.location+lineRange.location, textNearMessageRange.length); + // Correct the near message range relative to queryStartPosition + textNearMessageRange = NSMakeRange(textNearMessageRange.location+queryStartPosition, textNearMessageRange.length); // Select the near message and scroll to it [textView setSelectedRange:textNearMessageRange]; [textView scrollRangeToVisible:textNearMessageRange]; } + } else { // Select first erroneous query entirely + + NSRange queryRange; + if(firstErrorOccuredInQuery == -1) // for current or previous query + { + BOOL isLookBehind = YES; + queryRange = [self queryTextRangeAtPosition:[textView selectedRange].location lookBehind:&isLookBehind]; + [textView setSelectedRange:queryRange]; + } else { + // select the query for which the first error was detected + queryRange = [self queryTextRangeForQuery:firstErrorOccuredInQuery startPosition:queryStartPosition]; + [textView setSelectedRange:queryRange]; + } + } + } else { [errorText setStringValue:NSLocalizedString(@"There were no errors.", @"text shown when query was successfull")]; } // Set up the status string if ( totalQueriesRun > 1 ) { - if (totalAffectedRows==1) { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected in total, by %i queries taking %@", @"text showing one row has been affected by multiple queries"), + if (totalAffectedRows==1) { + [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected in total, by %i queries taking %@", @"text showing one row has been affected by multiple queries"), totalQueriesRun, [NSString stringForTimeInterval:executionTime] ]]; - - } else { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%i rows affected in total, by %i queries taking %@", @"text showing how many rows have been affected by multiple queries"), + + } else { + [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%i rows affected in total, by %i queries taking %@", @"text showing how many rows have been affected by multiple queries"), totalAffectedRows, totalQueriesRun, [NSString stringForTimeInterval:executionTime] ]]; - - } + + } } else { - if (totalAffectedRows==1) { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected, taking %@", @"text showing one row has been affected by a single query"), + if (totalAffectedRows==1) { + [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected, taking %@", @"text showing one row has been affected by a single query"), [NSString stringForTimeInterval:executionTime] ]]; - } else { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%i rows affected, taking %@", @"text showing how many rows have been affected by a single query"), + } else { + [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%i rows affected, taking %@", @"text showing how many rows have been affected by a single query"), totalAffectedRows, [NSString stringForTimeInterval:executionTime] ]]; - - } + + } } @@ -513,10 +563,10 @@ sets the tableView columns corresponding to the mysql-result [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; // Perform the Growl notification for query completion - [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished" + [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Query Finished" description:[NSString stringWithFormat:NSLocalizedString(@"%@",@"description for query finished growl notification"), [errorText stringValue]] notificationName:@"Query Finished"]; - + return; } @@ -560,6 +610,123 @@ sets the tableView columns corresponding to the mysql-result } /* + * Retrieve the range of the query at a position specified + * within the custom query text view. + */ +- (NSRange)queryTextRangeAtPosition:(long)position lookBehind:(BOOL *)doLookBehind +{ + SPSQLParser *customQueryParser; + NSArray *queries; + NSString *query = nil; + int i, lastQueryStartPosition, queryPosition = 0; + + // If the supplied position is negative or beyond the end of the string, return nil. + if (position < 0 || position > [[textView string] length]) + return NSMakeRange(NSNotFound,0); + + // Split the current text into queries + customQueryParser = [[SPSQLParser alloc] initWithString:[textView string]]; + queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringByCharacter:';']]; + [customQueryParser release]; + + // 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++ ) { + lastQueryStartPosition = queryStartPosition; + queryStartPosition = queryPosition; + queryPosition += [[queries objectAtIndex:i] length]; + if (queryPosition >= position) { + + // If lookbehind is enabled, determine whether it's valid and check + // if after the caret position are only white spaces or newlines + if (*doLookBehind) { + NSCharacterSet *trimSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + NSString *string = [textView string]; + unsigned int curCaretPosition = [textView selectedRange].location; + NSRange curlineRange = [string lineRangeForRange:NSMakeRange(curCaretPosition, 0)]; + NSString *lineTailFromCaret = [[string substringWithRange: + NSMakeRange([textView selectedRange].location, + curlineRange.length + curlineRange.location - curCaretPosition)] stringByTrimmingCharactersInSet:trimSet]; + + if (i + && + ![[[string substringWithRange: + NSMakeRange(queryStartPosition, position - queryStartPosition)] stringByTrimmingCharactersInSet:trimSet] length] + && + ![lineTailFromCaret length] + ) + { + query = [NSString stringWithString:[queries objectAtIndex:i-1]]; + queryStartPosition = lastQueryStartPosition; + break; + } + *doLookBehind = NO; + } + + query = [NSString stringWithString:[queries objectAtIndex:i]]; + break; + } + queryPosition++; + } + if (doLookBehind && position == [[textView string] length] && !query) + { + query = [queries lastObject]; + } + + if(queryStartPosition < 0) queryStartPosition = 0; + + [queries release]; + + // Remove all leading white spaces + NSError *err; + int offset = [query rangeOfRegex:@"^(\\s*)" options:RKLNoOptions inRange:NSMakeRange(0, [query length]) capture:1 error:&err].length; + + return NSMakeRange(queryStartPosition+offset, [query length]-offset); +} + +/* + * Retrieve the range of the query for the passed index seen from a start position + * specified within the custom query text view. + */ +- (NSRange)queryTextRangeForQuery:(int)anIndex startPosition:(long)position +{ + SPSQLParser *customQueryParser; + NSArray *queries; + int i; + + // If the supplied position is negative or beyond the end of the string, return nil. + if (position < 0 || position > [[textView string] length]) + return NSMakeRange(NSNotFound,0); + + // Split the current text into queries + customQueryParser = [[SPSQLParser alloc] initWithString:[[textView string] substringWithRange:NSMakeRange(position, [[textView string] length]-position)]]; + queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringByCharacter:';']]; + [customQueryParser release]; + anIndex--; + if(anIndex < 0 || anIndex >= [queries count]) + { + [queries release]; + return NSMakeRange(NSNotFound, 0); + } + + NSString * theQuery = [queries objectAtIndex:anIndex]; + + // Calculate the text length before that query at index anIndex + long prevQueriesLength = 0; + for (i = 0; i < anIndex; i++ ) { + prevQueriesLength += [[queries objectAtIndex:i] length] + 1; + } + + [queries release]; + + // Remove all leading white spaces + NSError *err; + int offset = [theQuery rangeOfRegex:@"^(\\s*)" options:RKLNoOptions inRange:NSMakeRange(0, [theQuery length]) capture:1 error:&err].length; + + return NSMakeRange(position+offset+prevQueriesLength, [theQuery length] - offset); +} + +/* * Retrieve the query at a position specified within the custom query * text view. This will return nil if the position specified is beyond * the available string or if an empty query would be returned. @@ -572,7 +739,7 @@ sets the tableView columns corresponding to the mysql-result SPSQLParser *customQueryParser; NSArray *queries; NSString *query = nil; - int i, queryPosition = 0, queryStartPosition; + int i, lastQueryStartPosition, queryPosition = 0; // If the supplied position is negative or beyond the end of the string, return nil. if (position < 0 || position > [[textView string] length]) @@ -586,6 +753,7 @@ sets the tableView columns corresponding to the mysql-result // 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++ ) { + lastQueryStartPosition = queryStartPosition; queryStartPosition = queryPosition; queryPosition += [[queries objectAtIndex:i] length]; if (queryPosition >= position) { @@ -598,7 +766,7 @@ sets the tableView columns corresponding to the mysql-result unsigned int curCaretPosition = [textView selectedRange].location; NSRange curlineRange = [string lineRangeForRange:NSMakeRange(curCaretPosition, 0)]; NSString *lineTailFromCaret = [[string substringWithRange: - NSMakeRange([textView selectedRange].location, + NSMakeRange([textView selectedRange].location, curlineRange.length + curlineRange.location - curCaretPosition)] stringByTrimmingCharactersInSet:trimSet]; if (i @@ -610,6 +778,7 @@ sets the tableView columns corresponding to the mysql-result ) { query = [NSString stringWithString:[queries objectAtIndex:i-1]]; + queryStartPosition = lastQueryStartPosition; break; } *doLookBehind = NO; @@ -620,8 +789,13 @@ sets the tableView columns corresponding to the mysql-result } queryPosition++; } - if (doLookBehind && position == [[textView string] length] && !query) query = [queries lastObject]; - + if (doLookBehind && position == [[textView string] length] && !query) + { + query = [queries lastObject]; + } + + if(queryStartPosition < 0) queryStartPosition = 0; + [queries release]; // Ensure the string isn't empty. @@ -1178,7 +1352,6 @@ traps enter key and #pragma mark - - // Last but not least - (id)init; { |