diff options
author | rowanbeentje <rowan@beent.je> | 2009-02-23 01:21:43 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-02-23 01:21:43 +0000 |
commit | 40e5f768c40608f6d661d32e6dec9fefe64608e2 (patch) | |
tree | c95301a9b724d38f83bd4f34a8c0df7edf7a4a5d /Source/SPTableData.m | |
parent | bb8dfb0ad769b551ad2fd6944315c5292246ad7e (diff) | |
download | sequelpro-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.
Diffstat (limited to 'Source/SPTableData.m')
-rw-r--r-- | Source/SPTableData.m | 477 |
1 files changed, 319 insertions, 158 deletions
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]; |