From 40e5f768c40608f6d661d32e6dec9fefe64608e2 Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Mon, 23 Feb 2009 01:21:43 +0000 Subject: A patch to improve View support, addressing Issue #130. This patch undoes the damage caused by r333, making views viewable again, and extends support by preventing errors in the info pane, by drawing views with a different icon to easily differentiate them, by making all source/index information non-editable to match GUI to behaviour, and to tweak the interface slightly to match. --- Source/SPTableData.h | 7 +- Source/SPTableData.m | 477 ++++++++++++++++++++++++++++++++++----------------- Source/SPTableInfo.m | 11 +- Source/TableDump.m | 4 +- Source/TableSource.h | 1 + Source/TableSource.m | 23 ++- Source/TablesList.h | 9 + Source/TablesList.m | 79 +++++++-- 8 files changed, 428 insertions(+), 183 deletions(-) (limited to 'Source') diff --git a/Source/SPTableData.h b/Source/SPTableData.h index fd069a6b..0a3a8883 100644 --- a/Source/SPTableData.h +++ b/Source/SPTableData.h @@ -47,8 +47,11 @@ - (void) resetAllData; - (void) resetStatusData; - (void) resetColumnData; -- (BOOL) updateInformationFromCreateTableForCurrentTable; -- (NSDictionary *) informationFromCreateTableSyntaxForTable:(NSString *)tableName; +- (BOOL) updateInformationForCurrentTable; +- (NSDictionary *) informationForTable:(NSString *)tableName; +- (BOOL) updateInformationForCurrentView; +- (NSDictionary *) informationForView:(NSString *)viewName; - (BOOL) updateStatusInformationForCurrentTable; +- (NSDictionary *) parseFieldDefinitionStringParts:(NSArray *)definitionParts; @end diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 144c3280..aff7b456 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -63,7 +63,11 @@ - (NSString *) tableEncoding { if (tableEncoding == nil) { - [self updateInformationFromCreateTableForCurrentTable]; + if ([tableListInstance tableType] == SP_TABLETYPE_VIEW) { + [self updateInformationForCurrentView]; + } else { + [self updateInformationForCurrentTable]; + } } return [NSString stringWithString:tableEncoding]; } @@ -75,7 +79,11 @@ - (NSArray *) columns { if ([columns count] == 0) { - [self updateInformationFromCreateTableForCurrentTable]; + if ([tableListInstance tableType] == SP_TABLETYPE_VIEW) { + [self updateInformationForCurrentView]; + } else { + [self updateInformationForCurrentTable]; + } } return columns; } @@ -97,7 +105,11 @@ - (NSArray *) columnNames { if ([columnNames count] == 0) { - [self updateInformationFromCreateTableForCurrentTable]; + if ([tableListInstance tableType] == SP_TABLETYPE_VIEW) { + [self updateInformationForCurrentView]; + } else { + [self updateInformationForCurrentTable]; + } } return columnNames; } @@ -109,7 +121,11 @@ - (NSDictionary *) columnAtIndex:(int)index { if ([columns count] == 0) { - [self updateInformationFromCreateTableForCurrentTable]; + if ([tableListInstance tableType] == SP_TABLETYPE_VIEW) { + [self updateInformationForCurrentView]; + } else { + [self updateInformationForCurrentTable]; + } } return [columns objectAtIndex:index]; } @@ -177,9 +193,9 @@ * Retrieves the information for the current table and stores it in cache. * Returns a boolean indicating success. */ -- (BOOL) updateInformationFromCreateTableForCurrentTable +- (BOOL) updateInformationForCurrentTable { - NSDictionary *tableData = [self informationFromCreateTableSyntaxForTable:[tableListInstance tableName]]; + NSDictionary *tableData = [self informationForTable:[tableListInstance tableName]]; NSDictionary *columnData; NSEnumerator *enumerator; @@ -213,13 +229,13 @@ * This function is rather long due to the painful parsing required, but is fast. * Returns a boolean indicating success. */ -- (NSDictionary *) informationFromCreateTableSyntaxForTable:(NSString *)tableName +- (NSDictionary *) informationForTable:(NSString *)tableName { - SPSQLParser *createTableParser, *fieldsParser, *fieldParser, *detailParser; - NSMutableArray *tableColumns, *fieldStrings, *definitionParts, *detailParts; + SPSQLParser *createTableParser, *fieldsParser, *fieldParser; + NSMutableArray *tableColumns, *fieldStrings, *definitionParts; NSMutableDictionary *tableColumn, *tableData; - NSString *detailString, *encodingString; - unsigned i, j, stringStart, partsArrayLength; + NSString *encodingString; + unsigned i, stringStart; // Catch unselected tables and return nil if ([tableName isEqualToString:@""] || !tableName) return nil; @@ -227,9 +243,11 @@ // Retrieve the CREATE TABLE syntax for the table CMMCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE `%@`", tableName]]; - // Check for any errors + // Check for any errors, but only display them if a connection still exists if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""]) { - NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving table information:\n\n%@", [mySQLConnection getLastErrorMessage]], @"OK", nil, nil); + if (![mySQLConnection isConnected]) { + NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving table information:\n\n%@", [mySQLConnection getLastErrorMessage]], @"OK", nil, nil); + } return nil; } @@ -270,151 +288,8 @@ // Capture the area between the two backticks as the name [tableColumn setObject:[fieldsParser trimAndReturnStringFromCharacter:'`' toCharacter:'`' trimmingInclusively:YES returningInclusively:NO ignoringQuotedStrings:NO] forKey:@"name"]; - // Split the remaining field definition string by spaces ready for processing - [definitionParts addObjectsFromArray:[fieldsParser splitStringByCharacter:' ' skippingBrackets:YES]]; - - // The first item is always the data type. - [fieldParser setString:[definitionParts objectAtIndex:0]]; - - // If no field length definition is present, store only the type - if ([fieldParser firstOccurrenceOfCharacter:'(' ignoringQuotedStrings:YES] == NSNotFound) { - [tableColumn setObject:[fieldParser uppercaseString] forKey:@"type"]; - - // Otherwise separate out the length definition for processing - } else { - detailParser = [[SPSQLParser alloc] initWithString:[[fieldParser stringToCharacter:'(' inclusively:NO] uppercaseString]]; - [tableColumn setObject:[NSString stringWithString:detailParser] forKey:@"type"]; - - // For ENUMs and SETs, capture the field value options into an array for storage - if ([detailParser isEqualToString:@"ENUM"] || [detailParser isEqualToString:@"SET"]) { - [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; - detailParts = [[NSMutableArray alloc] initWithArray:[detailParser splitStringByCharacter:',']]; - for (j = 0; j < [detailParts count]; j++) { - [detailParser setString:[detailParts objectAtIndex:j]]; - [detailParts replaceObjectAtIndex:j withObject:[detailParser unquotedString]]; - } - [tableColumn setObject:[NSArray arrayWithArray:detailParts] forKey:@"values"]; - [detailParts release]; - - // For types with required or optional decimals, store as appropriate - } else if ([detailParser isEqualToString:@"REAL"] || [detailParser isEqualToString:@"DOUBLE"] || [detailParser isEqualToString:@"FLOAT"] || [detailParser isEqualToString:@"DECIMAL"] || [detailParser isEqualToString:@"NUMERIC"]) { - [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; - detailParts = [[NSMutableArray alloc] initWithArray:[detailParser splitStringByCharacter:',']]; - [detailParser setString:[detailParts objectAtIndex:0]]; - [tableColumn setObject:[detailParser unquotedString] forKey:@"length"]; - if ([detailParts count] > 1) { - [detailParser setString:[detailParts objectAtIndex:1]]; - [tableColumn setObject:[detailParser unquotedString] forKey:@"decimals"]; - } - [detailParts release]; - - // Otherwise capture the length only. - } else { - [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; - [tableColumn setObject:[detailParser unquotedString] forKey:@"length"]; - } - [detailParser release]; - } - - // Also capture a general column type "group" to allow behavioural switches - detailString = [[NSString alloc] initWithString:[tableColumn objectForKey:@"type"]]; - if ([detailString isEqualToString:@"BIT"]) { - [tableColumn setObject:@"bit" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"TINYINT"] || [detailString isEqualToString:@"SMALLINT"] || [detailString isEqualToString:@"MEDIUMINT"] - || [detailString isEqualToString:@"INT"] || [detailString isEqualToString:@"INTEGER"] || [detailString isEqualToString:@"BIGINT"]) { - [tableColumn setObject:@"integer" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"REAL"] || [detailString isEqualToString:@"DOUBLE"] || [detailString isEqualToString:@"FLOAT"] - || [detailString isEqualToString:@"DECIMAL"] || [detailString isEqualToString:@"NUMERIC"]) { - [tableColumn setObject:@"float" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"DATE"] || [detailString isEqualToString:@"TIME"] || [detailString isEqualToString:@"TIMESTAMP"] - || [detailString isEqualToString:@"DATETIME"] || [detailString isEqualToString:@"YEAR"]) { - [tableColumn setObject:@"date" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"CHAR"] || [detailString isEqualToString:@"VARCHAR"]) { - [tableColumn setObject:@"string" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"BINARY"] || [detailString isEqualToString:@"VARBINARY"]) { - [tableColumn setObject:@"binary" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"ENUM"] || [detailString isEqualToString:@"SET"]) { - [tableColumn setObject:@"enum" forKey:@"typegrouping"]; - } else if ([detailString isEqualToString:@"TINYTEXT"] || [detailString isEqualToString:@"TEXT"] - || [detailString isEqualToString:@"MEDIUMTEXT"] || [detailString isEqualToString:@"LONGTEXT"]) { - [tableColumn setObject:@"textdata" forKey:@"typegrouping"]; - - // Default to "blobdata". This means that future and currently unsupported types - including spatial extensions - - // will be preserved unmangled. - } else { - [tableColumn setObject:@"blobdata" forKey:@"typegrouping"]; - } - - // Set up some column defaults for all columns - [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"null"]; - [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"unsigned"]; - [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"binary"]; - [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"zerofill"]; - [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"autoincrement"]; - - // Walk through the remaining column definition parts storing recognised details - partsArrayLength = [definitionParts count]; - for (j = 1; j < partsArrayLength; j++) { - detailString = [[NSString alloc] initWithString:[[definitionParts objectAtIndex:j] uppercaseString]]; - - // Whether numeric fields are unsigned - if ([detailString isEqualToString:@"UNSIGNED"]) { - [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"unsigned"]; - - // Whether numeric fields are zerofill - } else if ([detailString isEqualToString:@"ZEROFILL"]) { - [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"zerofill"]; - - // Whether text types are binary - } else if ([detailString isEqualToString:@"BINARY"]) { - [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"binary"]; - - // Whether text types have a different encoding to the table - } else if ([detailString isEqualToString:@"CHARSET"] && (j + 1 < partsArrayLength)) { - if (![[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"DEFAULT"]) { - [tableColumn setValue:[definitionParts objectAtIndex:j+1] forKey:@"encoding"]; - } - j++; - } else if ([detailString isEqualToString:@"CHARACTER"] && (j + 2 < partsArrayLength) - && [[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"SET"]) { - if (![[[definitionParts objectAtIndex:j+2] uppercaseString] isEqualToString:@"DEFAULT"]) {; - [tableColumn setValue:[definitionParts objectAtIndex:j+2] forKey:@"encoding"]; - } - j = j + 2; - - // Whether text types have a different collation to the table - } else if ([detailString isEqualToString:@"COLLATE"] && (j + 1 < partsArrayLength)) { - if (![[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"DEFAULT"]) { - [tableColumn setValue:[definitionParts objectAtIndex:j+1] forKey:@"collation"]; - } - j++; - - // Whether fields are NOT NULL - } else if ([detailString isEqualToString:@"NOT"] && (j + 1 < partsArrayLength) - && [[[definitionParts objectAtIndex:j+1] uppercaseString] isEqualToString:@"NULL"]) { - [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"null"]; - j++; - - // Whether fields are NULL - } else if ([detailString isEqualToString:@"NULL"]) { - [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"null"]; - - // Whether fields should auto-increment - } else if ([detailString isEqualToString:@"AUTO_INCREMENT"]) { - [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"autoincrement"]; - - // Field defaults - } else if ([detailString isEqualToString:@"DEFAULT"] && (j + 1 < partsArrayLength)) { - detailParser = [[SPSQLParser alloc] initWithString:[definitionParts objectAtIndex:j+1]]; - [tableColumn setValue:[detailParser unquotedString] forKey:@"default"]; - [detailParser release]; - j++; - } - - // TODO: Currently unhandled: [UNIQUE | PRIMARY] KEY | COMMENT 'foo' | COLUMN_FORMAT bar | STORAGE q | REFERENCES... - - [detailString release]; - } + // Split the remaining field definition string by spaces and process + [tableColumn addEntriesFromDictionary:[self parseFieldDefinitionStringParts:[fieldsParser splitStringByCharacter:' ' skippingBrackets:YES]]]; // Store the column. [tableColumns addObject:[NSDictionary dictionaryWithDictionary:tableColumn]]; @@ -466,6 +341,121 @@ } +/* + * Retrieves the information for the current view and stores it in cache. + * Returns a boolean indicating success. + */ +- (BOOL) updateInformationForCurrentView +{ + NSDictionary *viewData = [self informationForView:[tableListInstance tableName]]; + NSDictionary *columnData; + NSEnumerator *enumerator; + + if (viewData == nil) { + [columns removeAllObjects]; + [columnNames removeAllObjects]; + return FALSE; + } + + [columns addObjectsFromArray:[viewData objectForKey:@"columns"]]; + + enumerator = [columns objectEnumerator]; + while (columnData = [enumerator nextObject]) { + [columnNames addObject:[NSString stringWithString:[columnData objectForKey:@"name"]]]; + } + + if (tableEncoding != nil) { + [tableEncoding release]; + } + tableEncoding = [[NSString alloc] initWithString:[viewData objectForKey:@"encoding"]]; + + return TRUE; +} + + +/* + * Retrieve information which can be used to display views. Unlike tables, all the information + * for views cannot be extracted from the CREATE ALGORITHM syntax without selecting all the info + * from the referenced tables. For the time being we therefore use the column information for + * SHOW COLUMNS (subsequently parsed), and derive the encoding from the database as no other source + * is available. + * Returns a boolean indicating success. + */ +- (NSDictionary *) informationForView:(NSString *)viewName +{ + SPSQLParser *fieldParser; + NSMutableArray *tableColumns; + NSDictionary *resultRow; + NSMutableDictionary *tableColumn, *viewData; + unsigned i; + + // Catch unselected views and return nil + if ([viewName isEqualToString:@""] || !viewName) return nil; + + // Retrieve the SHOW COLUMNS syntax for the table + CMMCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM `%@`", viewName]]; + + // Check for any errors, but only display them if a connection still exists + if (![[mySQLConnection getLastErrorMessage] isEqualToString:@""]) { + if (![mySQLConnection isConnected]) { + NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving view information:\n\n%@", [mySQLConnection getLastErrorMessage]], @"OK", nil, nil); + } + return nil; + } + + // Loop through the fields and capture details + if ([theResult numOfRows]) [theResult dataSeek:0]; + tableColumns = [[NSMutableArray alloc] init]; + tableColumn = [[NSMutableDictionary alloc] init]; + fieldParser = [[SPSQLParser alloc] init]; + for ( i = 0; i < [theResult numOfRows] ; i++ ) { + [tableColumn removeAllObjects]; + resultRow = [theResult fetchRowAsDictionary]; + + // Add the column name + [tableColumn setObject:[NSString stringWithString:[resultRow objectForKey:@"Field"]] forKey:@"name"]; + + // Populate type, length, and other available details from the Type columns + [fieldParser setString:[resultRow objectForKey:@"Type"]]; + [tableColumn addEntriesFromDictionary:[self parseFieldDefinitionStringParts:[fieldParser splitStringByCharacter:' ' skippingBrackets:YES]]]; + + // If there's a null column, use the details from it + if ([resultRow objectForKey:@"Null"]) { + if ([[[resultRow objectForKey:@"Null"] uppercaseString] isEqualToString:@"NO"]) { + [tableColumn setValue:[NSNumber numberWithBool:NO] forKey:@"null"]; + } else { + [tableColumn setValue:[NSNumber numberWithBool:YES] forKey:@"null"]; + } + } + + // Select the column default if available + if ([resultRow objectForKey:@"Default"]) { + if ([[resultRow objectForKey:@"Default"] isNSNull]) { + [tableColumn setValue:[NSString stringWithString:[[NSUserDefaults standardUserDefaults] objectForKey:@"nullValue"]] forKey:@"default"]; + } else { + [tableColumn setValue:[NSString stringWithString:[resultRow objectForKey:@"Default"]] forKey:@"default"]; + } + } + + // Add the column to the list + [tableColumns addObject:[NSDictionary dictionaryWithDictionary:tableColumn]]; + } + [fieldParser release]; + [tableColumn release]; + + // The character set has to be guessed at via the database encoding. + // Add the details to the data object. + viewData = [NSMutableDictionary dictionary]; + [viewData setObject:[NSString stringWithString:[tableDocumentInstance databaseEncoding]] forKey:@"encoding"]; + [viewData setObject:[NSArray arrayWithArray:tableColumns] forKey:@"columns"]; + + [tableColumns release]; + + return viewData; +} + + + /* * Retrieve the status of a table as a dictionary and add it to the local cache for reuse. */ @@ -475,6 +465,14 @@ // Catch unselected tables and return nil if ([[tableListInstance tableName] isEqualToString:@""] || ![tableListInstance tableName]) return nil; + // When views are selected, populate the table with a default dictionary - all values, including comment, return no + // meaningful information for views so we may as well skip the query. + if ([tableListInstance tableType] == SP_TABLETYPE_VIEW) { + + [status setDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"View", @"Engine", @"No status information is available for views.", @"Comment", [tableListInstance tableName], @"Name", nil]]; + return TRUE; + } + // Run the status query and retrieve as a dictionary. CMMCPResult *tableStatusResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE '%@'", [tableListInstance tableName]]]; @@ -496,6 +494,169 @@ } +/* + * Parse an array of field definition parts - not including name but including type and optionally unsigned/zerofill/null + * and so forth - into a dictionary of parsed details. Intended for use both with CREATE TABLE syntax - with fuller + * details - and with the "type" column from SHOW COLUMNS. + * Returns a dictionary of details with lowercase keys. + */ +- (NSDictionary *) parseFieldDefinitionStringParts:(NSArray *)definitionParts +{ + SPSQLParser *detailParser; + SPSQLParser *fieldParser = [[SPSQLParser alloc] init]; + NSMutableDictionary *fieldDetails = [[NSMutableDictionary alloc] init]; + NSMutableArray *detailParts; + NSString *detailString; + int i, partsArrayLength; + + if (![definitionParts count]) return [NSDictionary dictionary]; + + // The first item is always the data type. + [fieldParser setString:[definitionParts objectAtIndex:0]]; + + // If no field length definition is present, store only the type + if ([fieldParser firstOccurrenceOfCharacter:'(' ignoringQuotedStrings:YES] == NSNotFound) { + [fieldDetails setObject:[fieldParser uppercaseString] forKey:@"type"]; + + // Otherwise separate out the length definition for processing + } else { + detailParser = [[SPSQLParser alloc] initWithString:[[fieldParser stringToCharacter:'(' inclusively:NO] uppercaseString]]; + [fieldDetails setObject:[NSString stringWithString:detailParser] forKey:@"type"]; + + // For ENUMs and SETs, capture the field value options into an array for storage + if ([detailParser isEqualToString:@"ENUM"] || [detailParser isEqualToString:@"SET"]) { + [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; + detailParts = [[NSMutableArray alloc] initWithArray:[detailParser splitStringByCharacter:',']]; + for (i = 0; i < [detailParts count]; i++) { + [detailParser setString:[detailParts objectAtIndex:i]]; + [detailParts replaceObjectAtIndex:i withObject:[detailParser unquotedString]]; + } + [fieldDetails setObject:[NSArray arrayWithArray:detailParts] forKey:@"values"]; + [detailParts release]; + + // For types with required or optional decimals, store as appropriate + } else if ([detailParser isEqualToString:@"REAL"] || [detailParser isEqualToString:@"DOUBLE"] || [detailParser isEqualToString:@"FLOAT"] || [detailParser isEqualToString:@"DECIMAL"] || [detailParser isEqualToString:@"NUMERIC"]) { + [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; + detailParts = [[NSMutableArray alloc] initWithArray:[detailParser splitStringByCharacter:',']]; + [detailParser setString:[detailParts objectAtIndex:0]]; + [fieldDetails setObject:[detailParser unquotedString] forKey:@"length"]; + if ([detailParts count] > 1) { + [detailParser setString:[detailParts objectAtIndex:1]]; + [fieldDetails setObject:[detailParser unquotedString] forKey:@"decimals"]; + } + [detailParts release]; + + // Otherwise capture the length only. + } else { + [detailParser setString:[fieldParser stringFromCharacter:'(' toCharacter:')' inclusively:NO]]; + [fieldDetails setObject:[detailParser unquotedString] forKey:@"length"]; + } + [detailParser release]; + } + + // Also capture a general column type "group" to allow behavioural switches + detailString = [[NSString alloc] initWithString:[fieldDetails objectForKey:@"type"]]; + if ([detailString isEqualToString:@"BIT"]) { + [fieldDetails setObject:@"bit" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"TINYINT"] || [detailString isEqualToString:@"SMALLINT"] || [detailString isEqualToString:@"MEDIUMINT"] + || [detailString isEqualToString:@"INT"] || [detailString isEqualToString:@"INTEGER"] || [detailString isEqualToString:@"BIGINT"]) { + [fieldDetails setObject:@"integer" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"REAL"] || [detailString isEqualToString:@"DOUBLE"] || [detailString isEqualToString:@"FLOAT"] + || [detailString isEqualToString:@"DECIMAL"] || [detailString isEqualToString:@"NUMERIC"]) { + [fieldDetails setObject:@"float" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"DATE"] || [detailString isEqualToString:@"TIME"] || [detailString isEqualToString:@"TIMESTAMP"] + || [detailString isEqualToString:@"DATETIME"] || [detailString isEqualToString:@"YEAR"]) { + [fieldDetails setObject:@"date" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"CHAR"] || [detailString isEqualToString:@"VARCHAR"]) { + [fieldDetails setObject:@"string" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"BINARY"] || [detailString isEqualToString:@"VARBINARY"]) { + [fieldDetails setObject:@"binary" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"ENUM"] || [detailString isEqualToString:@"SET"]) { + [fieldDetails setObject:@"enum" forKey:@"typegrouping"]; + } else if ([detailString isEqualToString:@"TINYTEXT"] || [detailString isEqualToString:@"TEXT"] + || [detailString isEqualToString:@"MEDIUMTEXT"] || [detailString isEqualToString:@"LONGTEXT"]) { + [fieldDetails setObject:@"textdata" forKey:@"typegrouping"]; + + // Default to "blobdata". This means that future and currently unsupported types - including spatial extensions - + // will be preserved unmangled. + } else { + [fieldDetails setObject:@"blobdata" forKey:@"typegrouping"]; + } + + // Set up some column defaults for all columns + [fieldDetails setValue:[NSNumber numberWithBool:YES] forKey:@"null"]; + [fieldDetails setValue:[NSNumber numberWithBool:NO] forKey:@"unsigned"]; + [fieldDetails setValue:[NSNumber numberWithBool:NO] forKey:@"binary"]; + [fieldDetails setValue:[NSNumber numberWithBool:NO] forKey:@"zerofill"]; + [fieldDetails setValue:[NSNumber numberWithBool:NO] forKey:@"autoincrement"]; + + // Walk through the remaining column definition parts storing recognised details + partsArrayLength = [definitionParts count]; + for (i = 1; i < partsArrayLength; i++) { + detailString = [[NSString alloc] initWithString:[[definitionParts objectAtIndex:i] uppercaseString]]; + + // Whether numeric fields are unsigned + if ([detailString isEqualToString:@"UNSIGNED"]) { + [fieldDetails setValue:[NSNumber numberWithBool:YES] forKey:@"unsigned"]; + + // Whether numeric fields are zerofill + } else if ([detailString isEqualToString:@"ZEROFILL"]) { + [fieldDetails setValue:[NSNumber numberWithBool:YES] forKey:@"zerofill"]; + + // Whether text types are binary + } else if ([detailString isEqualToString:@"BINARY"]) { + [fieldDetails setValue:[NSNumber numberWithBool:YES] forKey:@"binary"]; + + // Whether text types have a different encoding to the table + } else if ([detailString isEqualToString:@"CHARSET"] && (i + 1 < partsArrayLength)) { + if (![[[definitionParts objectAtIndex:i+1] uppercaseString] isEqualToString:@"DEFAULT"]) { + [fieldDetails setValue:[definitionParts objectAtIndex:i+1] forKey:@"encoding"]; + } + i++; + } else if ([detailString isEqualToString:@"CHARACTER"] && (i + 2 < partsArrayLength) + && [[[definitionParts objectAtIndex:i+1] uppercaseString] isEqualToString:@"SET"]) { + if (![[[definitionParts objectAtIndex:i+2] uppercaseString] isEqualToString:@"DEFAULT"]) {; + [fieldDetails setValue:[definitionParts objectAtIndex:i+2] forKey:@"encoding"]; + } + i = i + 2; + + // Whether text types have a different collation to the table + } else if ([detailString isEqualToString:@"COLLATE"] && (i + 1 < partsArrayLength)) { + if (![[[definitionParts objectAtIndex:i+1] uppercaseString] isEqualToString:@"DEFAULT"]) { + [fieldDetails setValue:[definitionParts objectAtIndex:i+1] forKey:@"collation"]; + } + i++; + + // Whether fields are NOT NULL + } else if ([detailString isEqualToString:@"NOT"] && (i + 1 < partsArrayLength) + && [[[definitionParts objectAtIndex:i+1] uppercaseString] isEqualToString:@"NULL"]) { + [fieldDetails setValue:[NSNumber numberWithBool:NO] forKey:@"null"]; + i++; + + // Whether fields are NULL + } else if ([detailString isEqualToString:@"NULL"]) { + [fieldDetails setValue:[NSNumber numberWithBool:YES] forKey:@"null"]; + + // Whether fields should auto-increment + } else if ([detailString isEqualToString:@"AUTO_INCREMENT"]) { + [fieldDetails setValue:[NSNumber numberWithBool:YES] forKey:@"autoincrement"]; + + // Field defaults + } else if ([detailString isEqualToString:@"DEFAULT"] && (i + 1 < partsArrayLength)) { + detailParser = [[SPSQLParser alloc] initWithString:[definitionParts objectAtIndex:i+1]]; + [fieldDetails setValue:[detailParser unquotedString] forKey:@"default"]; + [detailParser release]; + i++; + } + + // TODO: Currently unhandled: [UNIQUE | PRIMARY] KEY | COMMENT 'foo' | COLUMN_FORMAT bar | STORAGE q | REFERENCES... + + [detailString release]; + } + + return [fieldDetails autorelease]; +} + - (void) dealloc { [columns release]; diff --git a/Source/SPTableInfo.m b/Source/SPTableInfo.m index 80760278..3857f858 100644 --- a/Source/SPTableInfo.m +++ b/Source/SPTableInfo.m @@ -104,8 +104,17 @@ objectValueForTableColumn:(NSTableColumn *)aTableColumn - (void)tableChanged:(NSNotification *)notification { NSDictionary *tableStatus; - + [info removeAllObjects]; + + // For views, no information can be displayed. + if ([tableListInstance tableType] == SP_TABLETYPE_VIEW) { + [info addObject:@"VIEW INFORMATION"]; + [info addObject:@"no information available"]; + [infoTable reloadData]; + return; + } + [info addObject:@"TABLE INFORMATION"]; if ([tableListInstance tableName]) diff --git a/Source/TableDump.m b/Source/TableDump.m index 9c27e325..c5b28b68 100644 --- a/Source/TableDump.m +++ b/Source/TableDump.m @@ -817,7 +817,7 @@ rowCount = [queryResult numOfRows]; // Retrieve the table details via the data class, and use it to build an array containing column numeric status - tableDetails = [NSDictionary dictionaryWithDictionary:[tableDataInstance informationFromCreateTableSyntaxForTable:tableName]]; + tableDetails = [NSDictionary dictionaryWithDictionary:[tableDataInstance informationForTable:tableName]]; colCount = [[tableDetails objectForKey:@"columns"] count]; tableColumnNumericStatus = [NSMutableArray arrayWithCapacity:colCount]; for ( j = 0; j < colCount ; j++ ) { @@ -1544,7 +1544,7 @@ } NSDate *startDate = [NSDate date]; // Retrieve the table details via the data class, and use it to build an array containing column numeric status - tableDetails = [NSDictionary dictionaryWithDictionary:[tableDataInstance informationFromCreateTableSyntaxForTable:tableName]]; + tableDetails = [NSDictionary dictionaryWithDictionary:[tableDataInstance informationForTable:tableName]]; tableColumnNumericStatus = [NSMutableArray array]; for ( j = 0; j < [[tableDetails objectForKey:@"columns"] count] ; j++ ) { tableColumnTypeGrouping = [[[tableDetails objectForKey:@"columns"] objectAtIndex:j] objectForKey:@"typegrouping"]; diff --git a/Source/TableSource.h b/Source/TableSource.h index 29cbfbaa..9cbedba1 100644 --- a/Source/TableSource.h +++ b/Source/TableSource.h @@ -114,6 +114,7 @@ //tableView delegate methods - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView; - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command; +- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex; //slitView delegate methods - (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview; diff --git a/Source/TableSource.m b/Source/TableSource.m index d9c7a595..33d3777b 100644 --- a/Source/TableSource.m +++ b/Source/TableSource.m @@ -165,7 +165,7 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab } // Determine the table type - if ( ![tableType isKindOfClass:[NSNull class]] ) { + if ( ![tableType isKindOfClass:[NSNull class]] && [tablesListInstance tableType] != SP_TABLETYPE_VIEW) { [tableTypeButton selectItemWithTitle:tableType]; [tableTypeButton setEnabled:YES]; } else { @@ -173,12 +173,13 @@ loads aTable, put it in an array, update the tableViewColumns and reload the tab [tableTypeButton setEnabled:NO]; } - //enable buttons - [addFieldButton setEnabled:YES]; - [copyFieldButton setEnabled:YES]; - [removeFieldButton setEnabled:YES]; - [addIndexButton setEnabled:YES]; - [removeIndexButton setEnabled:YES]; + // If a view is selected, disable the buttons; otherwise enable. + BOOL editingEnabled = ([tablesListInstance tableType] == SP_TABLETYPE_TABLE); + [addFieldButton setEnabled:editingEnabled]; + [copyFieldButton setEnabled:editingEnabled]; + [removeFieldButton setEnabled:editingEnabled]; + [addIndexButton setEnabled:editingEnabled]; + [removeIndexButton setEnabled:editingEnabled]; //add columns to indexedColumnsField [indexedColumnsField removeAllItems]; @@ -1036,6 +1037,14 @@ traps enter and esc and make/cancel editing without entering next row } +/* + * Modify cell display by disabling table cells when a view is selected, meaning structure/index + * is uneditable. + */ +- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { + [aCell setEnabled:([tablesListInstance tableType] == SP_TABLETYPE_TABLE)]; +} + #pragma mark SplitView delegate methods - (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview diff --git a/Source/TablesList.h b/Source/TablesList.h index d2cb20da..b4875dd9 100644 --- a/Source/TablesList.h +++ b/Source/TablesList.h @@ -25,6 +25,12 @@ #import #import +enum sp_table_types +{ + SP_TABLETYPE_TABLE = 0, + SP_TABLETYPE_VIEW = 1 +}; + @class CMMCResult; @class CMMCPConnection; @@ -47,6 +53,7 @@ CMMCPConnection *mySQLConnection; NSMutableArray *tables; + NSMutableArray *tableTypes; // NSUserDefaults *prefs; BOOL structureLoaded, contentLoaded, statusLoaded, alertSheetOpened; } @@ -70,7 +77,9 @@ //getter methods - (NSString *)tableName; +- (int)tableType; - (NSArray *)tables; +- (NSArray *)tableTypes; - (BOOL)structureLoaded; - (BOOL)contentLoaded; - (BOOL)statusLoaded; diff --git a/Source/TablesList.m b/Source/TablesList.m index e92c7b13..0b5b76fd 100644 --- a/Source/TablesList.m +++ b/Source/TablesList.m @@ -43,25 +43,53 @@ loads all table names in array tables and reload the tableView - (IBAction)updateTables:(id)sender { CMMCPResult *theResult; + NSArray *resultRow; int i; - - //query started - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self]; + BOOL containsViews = NO; [tablesListView deselectAll:self]; [tables removeAllObjects]; - [tables addObject:NSLocalizedString(@"TABLES",@"header for table list")]; + [tableTypes removeAllObjects]; + [tableTypes addObject:[NSNumber numberWithInt:-1]]; + + if ([tableDocumentInstance database]) { + + // Notify listeners that a query has started + [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryWillBePerformed" object:self]; + + // Select the table list for the current database. On MySQL versions after 5 this will include + // views; on MySQL versions >= 5.0.02 select the "full" list to also select the table type column. + theResult = [mySQLConnection queryString:@"SHOW /*!50002 FULL*/ TABLES"]; + if ([theResult numOfRows]) [theResult dataSeek:0]; + if ([theResult numOfFields] == 1) { + for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { + [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:0]]; + [tableTypes addObject:[NSNumber numberWithInt:SP_TABLETYPE_TABLE]]; + } + } else { + for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { + resultRow = [theResult fetchRowAsArray]; + [tables addObject:[resultRow objectAtIndex:0]]; + if ([[resultRow objectAtIndex:1] isEqualToString:@"VIEW"]) { + [tableTypes addObject:[NSNumber numberWithInt:SP_TABLETYPE_VIEW]]; + containsViews = YES; + } else { + [tableTypes addObject:[NSNumber numberWithInt:SP_TABLETYPE_TABLE]]; + } + } + } + } - theResult = (CMMCPResult *)[mySQLConnection listTables]; - if ([theResult numOfRows]) [theResult dataSeek:0]; - for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { - [tables addObject:[[theResult fetchRowAsArray] objectAtIndex:0]]; + if (containsViews) { + [tables insertObject:NSLocalizedString(@"TABLES & VIEWS",@"header for table & views list") atIndex:0]; + } else { + [tables insertObject:NSLocalizedString(@"TABLES",@"header for table list") atIndex:0]; } - - [tablesListView reloadData]; - - //query finished + + // Notify listeners that the query has finished [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:self]; + + [tablesListView reloadData]; } /* @@ -307,11 +335,30 @@ returns the currently selected table or nil if no table or mulitple tables are s } } +/* + * Returns the currently selected table type, or -1 if no table or multiple tables are selected + */ +- (int) tableType +{ + if ( [tablesListView numberOfSelectedRows] == 1 ) { + return [[tableTypes objectAtIndex:[tablesListView selectedRow]] intValue]; + } else if ([tablesListView numberOfSelectedRows] > 1) { + return -1; + } else { + return -1; + } +} + - (NSArray *)tables { return tables; } +- (NSArray *)tableTypes +{ + return tableTypes; +} + /* returns YES if table source has already been loaded */ @@ -616,7 +663,11 @@ traps enter and esc and edit/cancel without entering next row row:(int)rowIndex { if (rowIndex > 0 && [[aTableColumn identifier] isEqualToString:@"tables"]) { - [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"table-small"]]; + if ([[tableTypes objectAtIndex:rowIndex] intValue] == SP_TABLETYPE_VIEW) { + [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"table-view-small"]]; + } else { + [(ImageAndTextCell*)aCell setImage:[NSImage imageNamed:@"table-small"]]; + } [(ImageAndTextCell*)aCell setIndentationLevel:1]; if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"useMonospacedFonts"] ) { [(ImageAndTextCell*)aCell setFont:[NSFont fontWithName:@"Monaco" size:[NSFont smallSystemFontSize]]]; @@ -680,6 +731,7 @@ loads structure or source if tab selected the first time self = [super init]; tables = [[NSMutableArray alloc] init]; + tableTypes = [[NSMutableArray alloc] init]; structureLoaded = NO; contentLoaded = NO; statusLoaded = NO; @@ -692,6 +744,7 @@ loads structure or source if tab selected the first time // NSLog(@"TableList dealloc"); [tables release]; + [tableTypes release]; [super dealloc]; } -- cgit v1.2.3