From 9892a96b80073686b0dd1205d4f859b10d32336f Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Mon, 22 Mar 2010 23:07:03 +0000 Subject: - Simplify table source table setup and make thread safe. This should address http://log.sequelpro.com/view/43 , http://log.sequelpro.com/view/46 - Improve keepalive timer interaction - this should address http://log.sequelpro.com/view/74 and http://log.sequelpro.com/view/71 - Further thread safety improvements to Custom Query, Table Document, and the history controller --- Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m | 6 + Source/CustomQuery.m | 27 +- Source/SPHistoryController.m | 7 +- Source/TableDocument.m | 7 +- Source/TableSource.h | 1 + Source/TableSource.m | 299 ++++++++++----------- 6 files changed, 169 insertions(+), 178 deletions(-) diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m index 1a4786ec..7f5c4365 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m @@ -689,6 +689,12 @@ void pingConnectionTask(void *ptr) */ - (void)stopKeepAliveTimer { + // Stop keepalives on the main thread to avoid memory issues + if (![NSThread isMainThread]) { + [self performSelectorOnMainThread:@selector(stopKeepAliveTimer) withObject:nil waitUntilDone:NO]; + return; + } + if (keepAliveThread != NULL) pthread_cancel(keepAliveThread), keepAliveThread = NULL; if (!keepAliveTimer) return; [keepAliveTimer invalidate]; diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index 57d47b7b..f359f9d1 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -43,6 +43,7 @@ #import "SPEncodingPopupAccessory.h" #import "SPDataStorage.h" #import "SPAlertSheets.h" +#import "SPMainThreadTrampoline.h" @implementation CustomQuery @@ -506,14 +507,14 @@ // Reset the current table view as necessary to avoid redraw and reload issues. // Restore the view position to the top left to be within the results for all datasets. if(editedRow == -1) { - [customQueryView scrollRowToVisible:0]; - [customQueryView scrollColumnToVisible:0]; + [[customQueryView onMainThread] scrollRowToVisible:0]; + [[customQueryView onMainThread] scrollColumnToVisible:0]; } // Remove all the columns if not reloading the table if(!reloadingExistingResult) { if (cqColumnDefinition) [cqColumnDefinition release], cqColumnDefinition = nil; - [self performSelectorOnMainThread:@selector(updateTableView) withObject:nil waitUntilDone:YES]; + [[self onMainThread] updateTableView]; } // Disable automatic query retries on failure for the custom queries @@ -535,7 +536,7 @@ if (i > 0) { NSString *taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %ld of %lu...", @"Running multiple queries string"), (long)(i+1), (unsigned long)queryCount]; [tableDocumentInstance setTaskDescription:taskString]; - [errorText setStringValue:taskString]; + [[errorText onMainThread] setStringValue:taskString]; } NSString *query = [NSArrayObjectAtIndex(queries, i) stringByTrimmingCharactersInSet:whitespaceAndNewlineSet]; @@ -561,7 +562,7 @@ cqColumnDefinition = [[streamingResult fetchResultFieldsStructure] retain]; if(!reloadingExistingResult) { - [self performSelectorOnMainThread:@selector(updateTableView) withObject:nil waitUntilDone:YES]; + [[self onMainThread] updateTableView]; } [self processResultIntoDataStorage:streamingResult]; @@ -601,7 +602,7 @@ [errors appendString:[NSString stringWithFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), (long)(i+1), errorString]]; - [errorText setStringValue:errors]; + [[errorText onMainThread] setStringValue:errors]; // ask the user to continue after detecting an error if (![mySQLConnection queryCancelled]) { @@ -612,7 +613,7 @@ [alert setMessageText:NSLocalizedString(@"MySQL Error", @"mysql error message")]; [alert setInformativeText:[mySQLConnection getLastErrorMessage]]; [alert setAlertStyle:NSWarningAlertStyle]; - NSInteger choice = [alert runModal]; + NSInteger choice = [[alert onMainThread] runModal]; switch (choice){ case NSAlertFirstButtonReturn: suppressErrorSheet = YES; @@ -687,24 +688,24 @@ // Set up the status string if ( [mySQLConnection queryCancelled] ) { if (totalQueriesRun > 1) { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Cancelled in query %ld, after %@", @"text showing multiple queries were cancelled"), + [[affectedRowsText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Cancelled in query %ld, after %@", @"text showing multiple queries were cancelled"), (long)totalQueriesRun, [NSString stringForTimeInterval:executionTime] ]]; } else { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Cancelled after %@", @"text showing a query was cancelled"), + [[affectedRowsText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Cancelled after %@", @"text showing a query was cancelled"), [NSString stringForTimeInterval:executionTime] ]]; } } else if ( totalQueriesRun > 1 ) { if (totalAffectedRows==1) { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected in total, by %ld queries taking %@", @"text showing one row has been affected by multiple queries"), + [[affectedRowsText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected in total, by %ld queries taking %@", @"text showing one row has been affected by multiple queries"), (long)totalQueriesRun, [NSString stringForTimeInterval:executionTime] ]]; } else { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%ld rows affected in total, by %ld queries taking %@", @"text showing how many rows have been affected by multiple queries"), + [[affectedRowsText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%ld rows affected in total, by %ld queries taking %@", @"text showing how many rows have been affected by multiple queries"), (long)totalAffectedRows, (long)totalQueriesRun, [NSString stringForTimeInterval:executionTime] @@ -713,11 +714,11 @@ } } else { if (totalAffectedRows==1) { - [affectedRowsText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"1 row affected, taking %@", @"text showing one row has been affected by a single query"), + [[affectedRowsText onMainThread] 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(@"%ld rows affected, taking %@", @"text showing how many rows have been affected by a single query"), + [[affectedRowsText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"%ld rows affected, taking %@", @"text showing how many rows have been affected by a single query"), (long)totalAffectedRows, [NSString stringForTimeInterval:executionTime] ]]; diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m index 04d8a765..991c6e1e 100644 --- a/Source/SPHistoryController.m +++ b/Source/SPHistoryController.m @@ -27,6 +27,7 @@ #import "TablesList.h" #import "SPHistoryController.h" #import "SPStringAdditions.h" +#import "SPMainThreadTrampoline.h" @implementation SPHistoryController @@ -263,7 +264,7 @@ if ([history count] > 50) [history removeObjectAtIndex:0]; historyPosition = [history count] - 1; - [self updateToolbarItem]; + [[self onMainThread] updateToolbarItem]; } #pragma mark - @@ -318,7 +319,7 @@ { [tableContentInstance loadTable:[historyEntry objectForKey:@"table"]]; modifyingState = NO; - [self updateToolbarItem]; + [[self onMainThread] updateToolbarItem]; [theDocument endTask]; [loadPool drain]; return; @@ -380,7 +381,7 @@ } modifyingState = NO; - [self updateToolbarItem]; + [[self onMainThread] updateToolbarItem]; // End the task [theDocument endTask]; diff --git a/Source/TableDocument.m b/Source/TableDocument.m index 763a444b..4580e392 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -54,6 +54,7 @@ #import "SPProcessListController.h" #import "SPServerVariablesController.h" #import "SPAlertSheets.h" +#import "SPMainThreadTrampoline.h" @interface TableDocument (PrivateAPI) @@ -905,7 +906,7 @@ [tablesListInstance setConnection:mySQLConnection]; [tableDumpInstance setConnection:mySQLConnection]; - [tableWindow setTitle:[self displaySPName]]; + [[tableWindow onMainThread] setTitle:[self displaySPName]]; // Add a history entry if (!historyStateChanging) { @@ -916,9 +917,9 @@ // Set focus to table list filter field if visible // otherwise set focus to Table List view if ( [[tablesListInstance tables] count] > 20 ) - [tableWindow makeFirstResponder:listFilterField]; + [[tableWindow onMainThread] makeFirstResponder:listFilterField]; else - [tableWindow makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]]; + [[tableWindow onMainThread] makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]]; [self endTask]; [taskPool drain]; diff --git a/Source/TableSource.h b/Source/TableSource.h index 54a0c492..1d5cbcc0 100644 --- a/Source/TableSource.h +++ b/Source/TableSource.h @@ -72,6 +72,7 @@ // Table methods - (void)loadTable:(NSString *)aTable; - (IBAction)reloadTable:(id)sender; +- (void) setTableDetails:(NSDictionary *)tableDetails; // Edit methods - (IBAction)addField:(id)sender; diff --git a/Source/TableSource.m b/Source/TableSource.m index 5f5b0119..fa0e190d 100644 --- a/Source/TableSource.m +++ b/Source/TableSource.m @@ -33,6 +33,7 @@ #import "SPArrayAdditions.h" #import "SPConstants.h" #import "SPAlertSheets.h" +#import "SPMainThreadTrampoline.h" @interface TableSource (PrivateAPI) @@ -49,71 +50,36 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab */ - (void)loadTable:(NSString *)aTable { - NSEnumerator *enumerator; - id field; + NSArray *theTableFields, *theTableIndexes; + NSMutableDictionary *theTableEnumLists = [NSMutableDictionary dictionary]; NSArray *extrasArray; NSMutableDictionary *tempDefaultValues; - NSEnumerator *extrasEnumerator; - id extra; NSInteger i; SPSQLParser *fieldParser; - BOOL enableInteraction = ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableStructure] || ![tableDocumentInstance isWorking]; // Check whether a save of the current row is required. - if ( ![self saveRowOnDeselect] ) return; - - if (selectedTable) [selectedTable release]; - if (aTable == nil) { - selectedTable = nil; - } else { - selectedTable = [[NSString alloc] initWithString:aTable]; - } - [tableSourceView deselectAll:self]; - [indexView deselectAll:self]; - - if ( isEditingRow ) - return; - - // empty variables - [enumFields removeAllObjects]; - - if ( [aTable isEqualToString:@""] || !aTable ) { - [tableFields removeAllObjects]; - [indexes removeAllObjects]; - [tableSourceView reloadData]; - [indexView reloadData]; - [addFieldButton setEnabled:NO]; - [copyFieldButton setEnabled:NO]; - [removeFieldButton setEnabled:NO]; - [addIndexButton setEnabled:NO]; - [removeIndexButton setEnabled:NO]; - [editTableButton setEnabled:NO]; + if ( ![[self onMainThread] saveRowOnDeselect] ) return; + // If no table is selected, reset the interface and return + if (!aTable || ![aTable length]) { + [[self onMainThread] setTableDetails:nil]; return; } - // Enable edit table button - [editTableButton setEnabled:enableInteraction]; - - //query started + // Send the query started/working notification [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; - //perform queries and load results in array (each row as a dictionary) - tableSourceResult = [[mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [selectedTable backtickQuotedString]]] retain]; + // Retrieve the column information for this table. + // TODO: update this and indexes to use TableData at some point - tiny bit more parsing required... + tableSourceResult = [[mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [aTable backtickQuotedString]]] retain]; + + // If an error occurred, reset the interface and abort if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""]) { NSString *errorMessage = [NSString stringWithString:[mySQLConnection getLastErrorMessage]]; - [tableFields removeAllObjects]; - [indexes removeAllObjects]; - [tableSourceView reloadData]; - [tablesListInstance updateTables:self]; - [indexView reloadData]; - [addFieldButton setEnabled:NO]; - [copyFieldButton setEnabled:NO]; - [removeFieldButton setEnabled:NO]; - [addIndexButton setEnabled:NO]; - [removeIndexButton setEnabled:NO]; - [editTableButton setEnabled:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + [[self onMainThread] setTableDetails:nil]; + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), @@ -122,29 +88,20 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab return; } - [tableSourceResult setReturnDataAsStrings:YES]; - - // listFieldsFromTable is broken in the current version of the framework (no back-ticks for table name)! - // tableSourceResult = [[mySQLConnection listFieldsFromTable:selectedTable] retain]; - // [tableFields setArray:[[self fetchResultAsArray:tableSourceResult] retain]]; - [tableFields setArray:[self fetchResultAsArray:tableSourceResult]]; + // Process the field names into a local array of dictionaries + theTableFields = [self fetchResultAsArray:tableSourceResult]; [tableSourceResult release]; - indexResult = [[mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM %@", [selectedTable backtickQuotedString]]] retain]; + // Retrieve the indexes for the table + indexResult = [[mySQLConnection queryString:[NSString stringWithFormat:@"SHOW INDEX FROM %@", [aTable backtickQuotedString]]] retain]; + + // If an error occurred, reset the interface and abort if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""]) { NSString *errorMessage = [NSString stringWithString:[mySQLConnection getLastErrorMessage]]; - [tableFields removeAllObjects]; - [indexes removeAllObjects]; - [tableSourceView reloadData]; - [tablesListInstance updateTables:self]; - [indexView reloadData]; - [addFieldButton setEnabled:NO]; - [copyFieldButton setEnabled:NO]; - [removeFieldButton setEnabled:NO]; - [addIndexButton setEnabled:NO]; - [removeIndexButton setEnabled:NO]; - [editTableButton setEnabled:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; + [[self onMainThread] setTableDetails:nil]; + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [NSApp mainWindow], self, nil, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), @@ -152,33 +109,19 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab if (indexResult) [indexResult release]; return; } - [indexResult setReturnDataAsStrings:YES]; - // [indexes setArray:[[self fetchResultAsArray:indexResult] retain]]; - [indexes setArray:[self fetchResultAsArray:indexResult]]; + + // Process the indexes into a local array of dictionaries + theTableIndexes = [self fetchResultAsArray:indexResult]; [indexResult release]; - - //get table default values - if ( defaultValues ) { - [defaultValues release]; - defaultValues = nil; - } - - tempDefaultValues = [NSMutableDictionary dictionary]; - for ( i = 0 ; i < [tableFields count] ; i++ ) { - [tempDefaultValues setObject:[[tableFields objectAtIndex:i] objectForKey:@"Default"] forKey:[[tableFields objectAtIndex:i] objectForKey:@"Field"]]; - } - defaultValues = [[NSDictionary dictionaryWithDictionary:tempDefaultValues] retain]; - - //put field length and extras in separate key - enumerator = [tableFields objectEnumerator]; - while ( (field = [enumerator nextObject]) ) { + // Process all the fields to normalise keys and add additional information + for (id theField in theTableFields) { NSString *type; NSString *length; NSString *extras; // Set up the field parser with the type definition - fieldParser = [[SPSQLParser alloc] initWithString:[field objectForKey:@"Type"]]; + fieldParser = [[SPSQLParser alloc] initWithString:[theField objectForKey:@"Type"]]; // Pull out the field type; if no brackets are found, this returns nil - in which case simple values can be used. type = [fieldParser trimAndReturnStringToCharacter:'(' trimmingInclusively:YES returningInclusively:NO]; @@ -207,7 +150,7 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab [valueParser setString:[possibleValues objectAtIndex:i]]; [possibleValues replaceObjectAtIndex:i withObject:[valueParser unquotedString]]; } - [enumFields setObject:[NSArray arrayWithArray:possibleValues] forKey:[field objectForKey:@"Field"]]; + [theTableEnumLists setObject:[NSArray arrayWithArray:possibleValues] forKey:[theField objectForKey:@"Field"]]; [possibleValues release]; [valueParser release]; } @@ -215,72 +158,40 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab // For timestamps check to see whether "on update CURRENT_TIMESTAMP" - not returned // by SHOW COLUMNS - should be set from the table data store if ([type isEqualToString:@"timestamp"] - && [[[tableDataInstance columnWithName:[field objectForKey:@"Field"]] objectForKey:@"onupdatetimestamp"] integerValue]) + && [[[tableDataInstance columnWithName:[theField objectForKey:@"Field"]] objectForKey:@"onupdatetimestamp"] integerValue]) { - [field setObject:@"on update CURRENT_TIMESTAMP" forKey:@"Extra"]; + [theField setObject:@"on update CURRENT_TIMESTAMP" forKey:@"Extra"]; } - // scan extras for values like unsigned, zerofill, binary + // Scan extras for values like unsigned, zerofill, binary extrasArray = [extras componentsSeparatedByString:@" "]; - extrasEnumerator = [extrasArray objectEnumerator]; - - while ( (extra = [extrasEnumerator nextObject]) ) { - if ( [extra isEqualToString:@"unsigned"] ) { - [field setObject:@"1" forKey:@"unsigned"]; - } else if ( [extra isEqualToString:@"zerofill"] ) { - [field setObject:@"1" forKey:@"zerofill"]; - } else if ( [extra isEqualToString:@"binary"] ) { - [field setObject:@"1" forKey:@"binary"]; + for (id extra in extrasArray) { + if ([extra isEqualToString:@"unsigned"]) { + [theField setObject:@"1" forKey:@"unsigned"]; + } else if ([extra isEqualToString:@"zerofill"]) { + [theField setObject:@"1" forKey:@"zerofill"]; + } else if ([extra isEqualToString:@"binary"]) { + [theField setObject:@"1" forKey:@"binary"]; } else { - if ( ![extra isEqualToString:@""] ) + if (![extra isEqualToString:@""]) NSLog(@"ERROR: unknown option in field definition: %@", extra); } } - - [field setObject:type forKey:@"Type"]; - [field setObject:length forKey:@"Length"]; - } - - // If a view is selected, disable the buttons; otherwise enable. - BOOL editingEnabled = ([tablesListInstance tableType] == SP_TABLETYPE_TABLE) && enableInteraction; - [addFieldButton setEnabled:editingEnabled]; - [addIndexButton setEnabled:editingEnabled]; - - //the following three buttons will only be enabled if a row field/index is selected! - [copyFieldButton setEnabled:NO]; - [removeFieldButton setEnabled:NO]; - [removeIndexButton setEnabled:NO]; - - //add columns to indexedColumnsField - [indexedColumnsField removeAllItems]; - enumerator = [tableFields objectEnumerator]; - - while ( (field = [enumerator nextObject]) ) { - [indexedColumnsField addItemWithObjectValue:[field objectForKey:@"Field"]]; - } - - if ( [tableFields count] < 10 ) { - [indexedColumnsField setNumberOfVisibleItems:[tableFields count]]; - } else { - [indexedColumnsField setNumberOfVisibleItems:10]; + + [theField setObject:type forKey:@"Type"]; + [theField setObject:length forKey:@"Length"]; } - - [indexView reloadData]; - [tableSourceView reloadData]; - - // display and *then* tile to force scroll bars to be in the correct position - [[tableSourceView enclosingScrollView] display]; - [[tableSourceView enclosingScrollView] tile]; - - // Enable 'Duplicate field' if at least one field is specified - // if no field is selected 'Duplicate field' will copy the last field - // Enable 'Duplicate field' only for tables! - if ([tablesListInstance tableType] == SP_TABLETYPE_TABLE) - [copyFieldButton setEnabled:enableInteraction && ([tableSourceView numberOfRows] > 0)]; - else - [copyFieldButton setEnabled:NO]; - //query finished + // Set up the table details for the new table, and request an data/interface update + NSDictionary *tableDetails = [NSDictionary dictionaryWithObjectsAndKeys: + aTable, @"name", + theTableFields, @"tableFields", + theTableIndexes, @"tableIndexes", + theTableEnumLists, @"enumLists", + nil]; + [[self onMainThread] setTableDetails:tableDetails]; + + // Send the query finished/work complete notification [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; } @@ -294,6 +205,75 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab [self loadTable:selectedTable]; } +/** + * Update stored table details and update the interface to match the supplied + * table details. + * Should be called on the main thread. + */ +- (void) setTableDetails:(NSDictionary *)tableDetails +{ + NSString *newTableName = [tableDetails objectForKey:@"name"]; + NSMutableDictionary *newDefaultValues; + BOOL enableInteraction = ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableStructure] || ![tableDocumentInstance isWorking]; + + // Update the selected table name + if (selectedTable) [selectedTable release], selectedTable = nil; + if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName]; + + // Reset the table store and display + [enumFields removeAllObjects]; + [tableSourceView deselectAll:self]; + [indexView deselectAll:self]; + [tableFields removeAllObjects]; + [indexes removeAllObjects]; + [addFieldButton setEnabled:NO]; + [copyFieldButton setEnabled:NO]; + [removeFieldButton setEnabled:NO]; + [addIndexButton setEnabled:NO]; + [removeIndexButton setEnabled:NO]; + [editTableButton setEnabled:NO]; + + // If no table is selected, refresh the table display to blank and return + if (!selectedTable) { + [tableSourceView reloadData]; + [indexView reloadData]; + return; + } + + // Update the fields and indexes stores + [tableFields setArray:[tableDetails objectForKey:@"tableFields"]]; + [indexes setArray:[tableDetails objectForKey:@"tableIndexes"]]; + + // Update the default values array and the indexed column fields control + [indexedColumnsField removeAllItems]; + if (defaultValues) [defaultValues release], defaultValues = nil; + newDefaultValues = [NSMutableDictionary dictionaryWithCapacity:[tableFields count]]; + for (id theField in tableFields) { + [newDefaultValues setObject:[theField objectForKey:@"Default"] forKey:[theField objectForKey:@"Field"]]; + [indexedColumnsField addItemWithObjectValue:[theField objectForKey:@"Field"]]; + } + defaultValues = [[NSDictionary dictionaryWithDictionary:newDefaultValues] retain]; + + // Only show up to ten items in the indexed column fields control + if ([tableFields count] < 10) { + [indexedColumnsField setNumberOfVisibleItems:[tableFields count]]; + } else { + [indexedColumnsField setNumberOfVisibleItems:10]; + } + + // Enable the edit table button + [editTableButton setEnabled:enableInteraction]; + + // If a view is selected, disable the buttons; otherwise enable. + BOOL editingEnabled = ([tablesListInstance tableType] == SP_TABLETYPE_TABLE) && enableInteraction; + [addFieldButton setEnabled:editingEnabled]; + [addIndexButton setEnabled:editingEnabled]; + + // Reload the views + [indexView reloadData]; + [tableSourceView reloadData]; +} + #pragma mark - #pragma mark Edit methods @@ -615,36 +595,37 @@ closes the keySheet } } -/* -fetches the result as an array with a dictionary for each row in it -*/ +/** + * Converts the supplied result to an array containing a (mutable) dictionary for each row + */ - (NSArray *)fetchResultAsArray:(MCPResult *)theResult { NSUInteger numOfRows = [theResult numOfRows]; NSMutableArray *tempResult = [NSMutableArray arrayWithCapacity:numOfRows]; NSMutableDictionary *tempRow; NSArray *keys; - id key; NSInteger i; - Class nullClass = [NSNull class]; id prefsNullValue = [prefs objectForKey:SPNullValue]; + // Ensure table information is returned as strings to avoid problems with some server versions + [theResult setReturnDataAsStrings:YES]; + if (numOfRows) [theResult dataSeek:0]; for ( i = 0 ; i < numOfRows ; i++ ) { tempRow = [NSMutableDictionary dictionaryWithDictionary:[theResult fetchRowAsDictionary]]; - //use NULL string from preferences instead of the NSNull oject returned by the framework + // Replace NSNull instances with the NULL string from preferences keys = [tempRow allKeys]; - for (NSInteger j = 0; j < [keys count] ; j++) { - key = NSArrayObjectAtIndex(keys, j); - if ( [[tempRow objectForKey:key] isMemberOfClass:nullClass] ) - [tempRow setObject:prefsNullValue forKey:key]; + for (id theKey in keys) { + if ([[tempRow objectForKey:theKey] isNSNull]) + [tempRow setObject:prefsNullValue forKey:theKey]; } - // change some fields to be more human-readable or GUI compatible - if ( [[tempRow objectForKey:@"Extra"] isEqualToString:@""] ) { + + // Update some fields to be more human-readable or GUI compatible + if ([[tempRow objectForKey:@"Extra"] isEqualToString:@""]) { [tempRow setObject:@"None" forKey:@"Extra"]; } - if ( [[tempRow objectForKey:@"Null"] isEqualToString:@"YES"] ) { + if ([[tempRow objectForKey:@"Null"] isEqualToString:@"YES"]) { [tempRow setObject:@"1" forKey:@"Null"]; } else { [tempRow setObject:@"0" forKey:@"Null"]; @@ -1690,7 +1671,7 @@ would result in a position change. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Check whether a save of the current fields row is required. - if (![self saveRowOnDeselect]) return; + if (![[self onMainThread] saveRowOnDeselect]) return; if (![[indexedColumnsField stringValue] isEqualToString:@""]) { -- cgit v1.2.3