From 7b69d7481a828f4466335dd988da5ee3bc35af33 Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sun, 4 Sep 2011 13:30:26 +0000 Subject: Fix for issue 1168. Whenever the database is changed load all of the currently used relation names in a backgroud thread. In the event that the user opens the add new relation sheet during this process, the ability to enter a relation name and confirm the addition is disabled until the retrieval process is complete. There is also a new progress indicator on the add sheet to indicate this. --- Source/SPDatabaseDocument.h | 24 +++--- Source/SPDatabaseDocument.m | 16 ++-- Source/SPExtendedTableInfo.m | 1 - Source/SPTableRelations.h | 7 +- Source/SPTableRelations.m | 199 ++++++++++++++++++++++++++++--------------- 5 files changed, 160 insertions(+), 87 deletions(-) (limited to 'Source') diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index a0d02bb6..d79610e9 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -239,6 +239,19 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS #endif } +#ifdef SP_REFACTOR /* ivars */ +@property (assign) SPDatabaseData* databaseDataInstance; +@property (assign) SPTableData* tableDataInstance; +@property (assign) SPCustomQuery* customQueryInstance; +@property (assign) id databaseNameField; +@property (assign) id databaseEncodingButton; +@property (assign) id addDatabaseButton; + +@property (assign) id databaseRenameNameField; +@property (assign) id renameDatabaseButton; +@property (assign) id databaseRenameSheet; +#endif + #ifdef SP_REFACTOR /* ivars */ @property (assign) id delegate; @property (readonly) NSMutableArray* allDatabases; @@ -449,17 +462,6 @@ SPDatabaseData, SPTablesList, SPTableStructure, SPTableContent, SPTableData, SPS - (void)setTableSourceInstance:(SPTableStructure*)source; - (void)setTableContentInstance:(SPTableContent*)content; -@property (assign) SPDatabaseData* databaseDataInstance; -@property (assign) SPTableData* tableDataInstance; -@property (assign) SPCustomQuery* customQueryInstance; -@property (assign) id databaseNameField; -@property (assign) id databaseEncodingButton; -@property (assign) id addDatabaseButton; - -@property (assign) id databaseRenameNameField; -@property (assign) id renameDatabaseButton; -@property (assign) id databaseRenameSheet; - #endif @end diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 52858787..a6b905b2 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -73,6 +73,7 @@ enum { #import "SPDatabaseCopy.h" #import "SPTableCopy.h" #import "SPDatabaseRename.h" +#import "SPTableRelations.h" #endif #import "SPServerSupport.h" #ifndef SP_REFACTOR /* headers */ @@ -227,6 +228,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Set the connection controller's delegate [connectionController setDelegate:self]; + return connectionController; } @@ -5890,7 +5892,6 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif } - /** * Select the specified database and, optionally, table. */ @@ -5923,7 +5924,7 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // End the task first to ensure the database dropdown can be reselected [self endTask]; - if ( [mySQLConnection isConnected] ) { + if ([mySQLConnection isConnected]) { // Update the database list [[self onMainThread] setDatabases:self]; @@ -5970,10 +5971,14 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; // Set focus to table list filter field if visible // otherwise set focus to Table List view - if ( [[tablesListInstance tables] count] > 20 ) + if ([[tablesListInstance tables] count] > 20) { [[parentWindow onMainThread] makeFirstResponder:listFilterField]; - else + } + else { [[parentWindow onMainThread] makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]]; + } + + [tableRelationsInstance loadUsedRelationNames]; #endif } @@ -6031,8 +6036,9 @@ static NSString *SPCreateSyntx = @"SPCreateSyntax"; #endif #ifdef SP_REFACTOR /* glue */ - if ( delegate && [delegate respondsToSelector:@selector(databaseDidChange:)] ) + if (delegate && [delegate respondsToSelector:@selector(databaseDidChange:)]) { [delegate performSelectorOnMainThread:@selector(databaseDidChange:) withObject:self waitUntilDone:NO]; + } #endif [taskPool drain]; diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index 13e60f92..a1e59110 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -383,7 +383,6 @@ */ - (NSDictionary *)tableInformationForPrinting { - // Update possible pending comment changes by set the focus to create table syntax view [[NSApp keyWindow] makeFirstResponder:tableCreateSyntaxTextView]; diff --git a/Source/SPTableRelations.h b/Source/SPTableRelations.h index 74dd0a17..a600ebf0 100644 --- a/Source/SPTableRelations.h +++ b/Source/SPTableRelations.h @@ -50,12 +50,16 @@ IBOutlet NSPopUpButton *onUpdatePopUpButton; IBOutlet NSPopUpButton *onDeletePopUpButton; IBOutlet NSButton *confirmAddRelationButton; + IBOutlet NSProgressIndicator *dataProgressIndicator; + IBOutlet NSTextField *progressStatusTextField; MCPConnection *connection; - NSMutableArray *relationData; NSUserDefaults *prefs; + NSMutableArray *relationData; NSMutableArray *takenConstraintNames; + + BOOL isRetrievingRelationNames; } @property (readonly) NSMutableArray *relationData; @@ -77,6 +81,7 @@ - (void)endDocumentTaskForTab:(NSNotification *)aNotification; // Other +- (void)loadUsedRelationNames; - (NSArray *)relationDataForPrinting; - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; diff --git a/Source/SPTableRelations.m b/Source/SPTableRelations.m index 01fc4a6e..a26e9039 100644 --- a/Source/SPTableRelations.m +++ b/Source/SPTableRelations.m @@ -30,10 +30,14 @@ #import "SPTableView.h" #import "SPAlertSheets.h" +static NSString *SPRemoveRelation = @"SPRemoveRelation"; + @interface SPTableRelations () - (void)_refreshRelationDataForcingCacheRefresh:(BOOL)clearAllCaches; - (void)_updateAvailableTableColumns; +- (void)_loadUsedRelationNamesInBackground; +- (void)_relationNamesLoaded; @end @@ -51,6 +55,8 @@ relationData = [[NSMutableArray alloc] init]; prefs = [NSUserDefaults standardUserDefaults]; takenConstraintNames = [[NSMutableArray alloc] init]; + + isRetrievingRelationNames = NO; } return self; @@ -176,7 +182,6 @@ */ - (IBAction)addRelation:(id)sender { - // Check whether table editing is permitted (necessary as some actions - eg table double-click - bypass validation) if ([tableDocumentInstance isWorking] || [tablesListInstance tableType] != SPTableTypeTable) return; @@ -208,17 +213,6 @@ [refTablePopUpButton addItemWithTitle:[[result fetchRowAsArray] objectAtIndex:0]]; } - // Get a list of used constraint names for this db - [takenConstraintNames removeAllObjects]; - result = [connection queryString:[NSString stringWithFormat:@"SELECT DISTINCT constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY' AND constraint_schema = %@", [[tableDocumentInstance database] tickQuotedString]]]; - - [result dataSeek:0]; - - for (NSUInteger i = 0; i < [result numOfRows]; i++) - { - [takenConstraintNames addObject:[[[result fetchRowAsArray] objectAtIndex:0] lowercaseString]]; - } - // Reset other fields [constraintName setStringValue:@""]; [onDeletePopUpButton selectItemAtIndex:0]; @@ -258,7 +252,7 @@ [[buttons objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask]; [[buttons objectAtIndex:1] setKeyEquivalent:@"\r"]; - [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:@"removeRelation"]; + [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:SPRemoveRelation]; } } @@ -293,7 +287,8 @@ [addRelationButton setEnabled:enableInteraction]; [refreshRelationsButton setEnabled:enableInteraction]; [relationsTableView setEnabled:YES]; - } else { + } + else { [addRelationButton setEnabled:NO]; [refreshRelationsButton setEnabled:NO]; [relationsTableView setEnabled:NO]; @@ -307,34 +302,31 @@ #pragma mark - #pragma mark TextField delegate methods -- (void)controlTextDidChange:(NSNotification *)aNotification -{ - id field = [aNotification object]; - +- (void)controlTextDidChange:(NSNotification *)notification +{ // Make sure the user does not enter a taken name - if (field == constraintName) { + if ([notification object] == constraintName) { - NSString *userValue = [[constraintName stringValue] lowercaseString]; BOOL taken = NO; - for(NSString *takenName in takenConstraintNames) { - if([takenName isEqualToString:userValue]) { + NSString *userValue = [[constraintName stringValue] lowercaseString]; + + for (NSString *takenName in takenConstraintNames) + { + if ([takenName isEqualToString:userValue]) { taken = YES; break; } } - //make field red and disable add button - if(taken) { + // Make field red and disable add button + if (taken) { [constraintName setTextColor:[NSColor redColor]]; [confirmAddRelationButton setEnabled:NO]; } - //reset else { [constraintName setTextColor:[NSColor controlTextColor]]; [confirmAddRelationButton setEnabled:YES]; } - - return; } } @@ -366,7 +358,7 @@ * Double-click action on table cells - for the time being, return * NO to disable editing. */ -- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { if ([tableDocumentInstance isWorking]) return NO; @@ -376,7 +368,7 @@ /** * Disable row selection while the document is working. */ -- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex { return ![tableDocumentInstance isWorking]; } @@ -418,6 +410,26 @@ #pragma mark - #pragma mark Other +/** + * Loads the currently used relation names from information_schema.table_constraints. + */ +- (void)loadUsedRelationNames +{ + if (isRetrievingRelationNames) return; + + [dataProgressIndicator setHidden:NO]; + [dataProgressIndicator startAnimation:self]; + + [progressStatusTextField setHidden:NO]; + + [constraintName setEnabled:NO]; + [confirmAddRelationButton setEnabled:NO]; + + isRetrievingRelationNames = YES; + + [NSThread detachNewThreadSelector:@selector(_loadUsedRelationNamesInBackground) toTarget:self withObject:nil]; +} + /** * Returns an array of relation data to be used for printing purposes. The first element in the array is always * an array of the columns and each subsequent element is an array of relation data. @@ -462,7 +474,7 @@ */ - (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { - if ([contextInfo isEqualToString:@"removeRelation"]) { + if ([contextInfo isEqualToString:SPRemoveRelation]) { if (returnCode == NSAlertDefaultReturn) { @@ -536,22 +548,7 @@ } #pragma mark - - -/* - * Dealloc. - */ -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:SPUseMonospacedFonts]; - - [relationData release], relationData = nil; - - [takenConstraintNames release]; - takenConstraintNames = nil; - - [super dealloc]; -} +#pragma mark Private API /** * Refresh the displayed relations, optionally forcing a refresh of the underlying cache. @@ -561,22 +558,22 @@ [relationData removeAllObjects]; if ([tablesListInstance tableType] == SPTableTypeTable) { - + if (clearAllCaches) [tableDataInstance updateInformationForCurrentTable]; - + NSArray *constraints = [tableDataInstance getConstraints]; - + for (NSDictionary *constraint in constraints) { [relationData addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [constraint objectForKey:@"name"], @"name", - [[constraint objectForKey:@"columns"] objectAtIndex:0], @"columns", - [constraint objectForKey:@"ref_table"], @"fk_table", - [constraint objectForKey:@"ref_columns"], @"fk_columns", - ([constraint objectForKey:@"update"] ? [constraint objectForKey:@"update"] : @""), @"on_update", - ([constraint objectForKey:@"delete"] ? [constraint objectForKey:@"delete"] : @""), @"on_delete", - nil]]; - + [constraint objectForKey:@"name"], @"name", + [[constraint objectForKey:@"columns"] objectAtIndex:0], @"columns", + [constraint objectForKey:@"ref_table"], @"fk_table", + [constraint objectForKey:@"ref_columns"], @"fk_columns", + ([constraint objectForKey:@"update"] ? [constraint objectForKey:@"update"] : @""), @"on_update", + ([constraint objectForKey:@"delete"] ? [constraint objectForKey:@"delete"] : @""), @"on_delete", + nil]]; + } } @@ -591,24 +588,24 @@ { NSString *column = [columnPopUpButton titleOfSelectedItem]; NSString *table = [refTablePopUpButton titleOfSelectedItem]; - + [tableDataInstance resetAllData]; [tableDataInstance updateInformationForCurrentTable]; - + NSDictionary *columnInfo = [[tableDataInstance columnWithName:column] copy]; - + [refColumnPopUpButton setEnabled:NO]; [confirmAddRelationButton setEnabled:NO]; - + [refColumnPopUpButton removeAllItems]; - + [tableDataInstance resetAllData]; NSDictionary *tableInfo = [tableDataInstance informationForTable:table]; - + NSArray *columns = [tableInfo objectForKey:@"columns"]; - + NSMutableArray *validColumns = [NSMutableArray array]; - + // Only add columns of the same data type for (NSDictionary *aColumn in columns) { @@ -616,17 +613,81 @@ [validColumns addObject:[aColumn objectForKey:@"name"]]; } } - + // Add the valid columns if ([validColumns count] > 0) { NSArray *columnTitles = ([prefs boolForKey:SPAlphabeticalTableSorting])? [validColumns sortedArrayUsingSelector:@selector(compare:)] : validColumns; + [refColumnPopUpButton addItemsWithTitles:columnTitles]; - + [refColumnPopUpButton setEnabled:YES]; - [confirmAddRelationButton setEnabled:YES]; + + if (!isRetrievingRelationNames) { + [confirmAddRelationButton setEnabled:YES]; + } } - + [columnInfo release]; } +/** + * Loads all of the current foreign key relationship names for the current database. This method should be + * spawned on a separate thread. + */ +- (void)_loadUsedRelationNamesInBackground +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [takenConstraintNames removeAllObjects]; + + MCPResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT DISTINCT constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY' AND constraint_schema = %@", [[tableDocumentInstance database] tickQuotedString]]]; + + [result dataSeek:0]; + + for (NSUInteger i = 0; i < [result numOfRows]; i++) + { + [takenConstraintNames addObject:[[[result fetchRowAsArray] objectAtIndex:0] lowercaseString]]; + } + + // Update the UI on the main thread + [self performSelectorOnMainThread:@selector(_relationNamesLoaded) withObject:nil waitUntilDone:NO]; + + [pool release]; +} + +/** + * Called on the main thread, once all the current table relation names have been loaded and re-enables all + * the relevant UI controls. + */ +- (void)_relationNamesLoaded +{ + [dataProgressIndicator setHidden:YES]; + [dataProgressIndicator stopAnimation:self]; + + [progressStatusTextField setHidden:YES]; + + [constraintName setEnabled:YES]; + [confirmAddRelationButton setEnabled:YES]; + + [addRelationPanel makeFirstResponder:constraintName]; + + isRetrievingRelationNames = NO; +} + +#pragma mark - + +/* + * Dealloc. + */ +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:SPUseMonospacedFonts]; + + [relationData release], relationData = nil; + [takenConstraintNames release], takenConstraintNames = nil; + + [super dealloc]; +} + @end -- cgit v1.2.3