aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPTableData.m
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 /Source/SPTableData.m
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.
Diffstat (limited to 'Source/SPTableData.m')
-rw-r--r--Source/SPTableData.m477
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];