aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-02-23 01:21:43 +0000
committerrowanbeentje <rowan@beent.je>2009-02-23 01:21:43 +0000
commit40e5f768c40608f6d661d32e6dec9fefe64608e2 (patch)
treec95301a9b724d38f83bd4f34a8c0df7edf7a4a5d
parentbb8dfb0ad769b551ad2fd6944315c5292246ad7e (diff)
downloadsequelpro-40e5f768c40608f6d661d32e6dec9fefe64608e2.tar.gz
sequelpro-40e5f768c40608f6d661d32e6dec9fefe64608e2.tar.bz2
sequelpro-40e5f768c40608f6d661d32e6dec9fefe64608e2.zip
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.
-rw-r--r--Resources/Images/table-view-small.tifbin0 -> 24768 bytes
-rw-r--r--Source/SPTableData.h7
-rw-r--r--Source/SPTableData.m477
-rw-r--r--Source/SPTableInfo.m11
-rw-r--r--Source/TableDump.m4
-rw-r--r--Source/TableSource.h1
-rw-r--r--Source/TableSource.m23
-rw-r--r--Source/TablesList.h9
-rw-r--r--Source/TablesList.m79
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj4
10 files changed, 432 insertions, 183 deletions
diff --git a/Resources/Images/table-view-small.tif b/Resources/Images/table-view-small.tif
new file mode 100644
index 00000000..4e78e81c
--- /dev/null
+++ b/Resources/Images/table-view-small.tif
Binary files differ
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]];
@@ -467,6 +342,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.
*/
- (BOOL) updateStatusInformationForCurrentTable
@@ -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 <Cocoa/Cocoa.h>
#import <MCPKit_bundled/MCPKit_bundled.h>
+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];
}
diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj
index 52a315eb..b218578b 100644
--- a/sequel-pro.xcodeproj/project.pbxproj
+++ b/sequel-pro.xcodeproj/project.pbxproj
@@ -94,6 +94,7 @@
4DECC4900EC2B436008D359E /* MCPKit_bundled.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4DECC3330EC2A170008D359E /* MCPKit_bundled.framework */; };
4DECC4910EC2B436008D359E /* Growl.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4DECC3340EC2A170008D359E /* Growl.framework */; };
58186D210F4CB38900851FE9 /* ConnectionErrorDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = 58186D1F0F4CB38900851FE9 /* ConnectionErrorDialog.xib */; };
+ 584F5F8F0F50ACD800036517 /* table-view-small.tif in Resources */ = {isa = PBXBuildFile; fileRef = 584F5F8E0F50ACD800036517 /* table-view-small.tif */; };
58C56EF50F438E120035701E /* SPDataCellFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C56EF40F438E120035701E /* SPDataCellFormatter.m */; };
58FEF16D0F23D66600518E8E /* SPSQLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF16C0F23D66600518E8E /* SPSQLParser.m */; };
58FEF57E0F3B4E9700518E8E /* SPTableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF57D0F3B4E9700518E8E /* SPTableData.m */; };
@@ -250,6 +251,7 @@
4DECC3330EC2A170008D359E /* MCPKit_bundled.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MCPKit_bundled.framework; path = Frameworks/MCPKit_bundled.framework; sourceTree = "<group>"; };
4DECC3340EC2A170008D359E /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = "<group>"; };
58186D200F4CB38900851FE9 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/ConnectionErrorDialog.xib; sourceTree = "<group>"; };
+ 584F5F8E0F50ACD800036517 /* table-view-small.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "table-view-small.tif"; sourceTree = "<group>"; };
58C56EF30F438E120035701E /* SPDataCellFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDataCellFormatter.h; sourceTree = "<group>"; };
58C56EF40F438E120035701E /* SPDataCellFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataCellFormatter.m; sourceTree = "<group>"; };
58FEF16B0F23D66600518E8E /* SPSQLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLParser.h; sourceTree = "<group>"; };
@@ -522,6 +524,7 @@
17E641B20EF02036001BC333 /* showvariables.tiff */,
17E641B30EF02036001BC333 /* spyglass.tiff */,
17E641B40EF02036001BC333 /* table-small.tif */,
+ 584F5F8E0F50ACD800036517 /* table-view-small.tif */,
17E641B50EF02036001BC333 /* tableadd.tiff */,
17E641B60EF02036001BC333 /* tablecopy.tiff */,
17E641B70EF02036001BC333 /* tabledelete.tiff */,
@@ -692,6 +695,7 @@
1761FD480EF03A6F00331368 /* MainMenu.xib in Resources */,
B5E2C5FA0F2353B5007446E0 /* TablePropertyIcon.png in Resources */,
58186D210F4CB38900851FE9 /* ConnectionErrorDialog.xib in Resources */,
+ 584F5F8F0F50ACD800036517 /* table-view-small.tif in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};