diff options
author | stuconnolly <stuart02@gmail.com> | 2010-11-05 21:15:18 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2010-11-05 21:15:18 +0000 |
commit | a9be9780875d249019bd0cdf55eba960fb6f44be (patch) | |
tree | af7477b3e9c964dedf67ff40385f5724713ccc03 /Source/SPXMLExporter.m | |
parent | f683094590e4a95eb047893584493ed7b32e2a02 (diff) | |
download | sequelpro-a9be9780875d249019bd0cdf55eba960fb6f44be.tar.gz sequelpro-a9be9780875d249019bd0cdf55eba960fb6f44be.tar.bz2 sequelpro-a9be9780875d249019bd0cdf55eba960fb6f44be.zip |
Complete the implementation of supporting MySQL's XML schema format when exporting. Also, restore our old format and give the user the choice during export (defaults to MySQL schema). Completes the implementation of issue #840.
Diffstat (limited to 'Source/SPXMLExporter.m')
-rw-r--r-- | Source/SPXMLExporter.m | 313 |
1 files changed, 205 insertions, 108 deletions
diff --git a/Source/SPXMLExporter.m b/Source/SPXMLExporter.m index 1ff32018..962d5a53 100644 --- a/Source/SPXMLExporter.m +++ b/Source/SPXMLExporter.m @@ -35,6 +35,9 @@ @synthesize xmlDataArray; @synthesize xmlTableName; @synthesize xmlNULLString; +@synthesize xmlOutputIncludeStructure; +@synthesize xmlOutputIncludeContent; +@synthesize xmlFormat; /** * Initialise an instance of SPXMLExporter using the supplied delegate. @@ -62,13 +65,20 @@ { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + BOOL isTableExport = NO; + NSArray *xmlRow = nil; NSArray *fieldNames = nil; NSString *dataConversionString = nil; + + // Result sets + MCPResult *statusResult = nil; + MCPResult *structureResult = nil; MCPStreamingResult *streamingResult = nil; + NSMutableArray *xmlTags = [NSMutableArray array]; NSMutableString *xmlString = [NSMutableString string]; - NSMutableString *xmlItem = [NSMutableString string]; + NSMutableString *xmlItem = [NSMutableString string]; NSUInteger xmlRowCount = 0; NSUInteger i, totalRows, currentRowIndex, lastProgressValue, currentPoolDataLength; @@ -76,7 +86,8 @@ // Check to see if we have at least a table name or data array if ((![self xmlTableName]) && (![self xmlDataArray]) || ([[self xmlTableName] length] == 0) && ([[self xmlDataArray] count] == 0) || - (![self xmlNULLString])) + (([self xmlFormat] == SPXMLExportMySQLFormat) && ((![self xmlOutputIncludeStructure]) && (![self xmlOutputIncludeContent]))) || + (([self xmlFormat] == SPXMLExportPlainFormat) && (![self xmlNULLString]))) { [pool release]; return; @@ -92,67 +103,105 @@ // Make a streaming request for the data if the data array isn't set if ((![self xmlDataArray]) && [self xmlTableName]) { - totalRows = [[[[connection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [[self xmlTableName] backtickQuotedString]]] fetchRowAsArray] objectAtIndex:0] integerValue]; + + isTableExport = YES; + + totalRows = [[[[connection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [[self xmlTableName] backtickQuotedString]]] fetchRowAsArray] objectAtIndex:0] integerValue]; streamingResult = [connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [[self xmlTableName] backtickQuotedString]] useLowMemoryBlockingStreaming:[self exportUsingLowMemoryBlockingStreaming]]; - [xmlString appendFormat:@"\t<table_data name=\"%@\">\n\n", [self xmlTableName]]; + // Only include the structure if necessary + if (([self xmlFormat] == SPXMLExportMySQLFormat) && [self xmlOutputIncludeStructure]) { + + structureResult = [connection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [[self xmlTableName] backtickQuotedString]]]; + statusResult = [connection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE %@", [[self xmlTableName] tickQuotedString]]]; + + if ([structureResult numOfRows] && [statusResult numOfRows]) { + + [statusResult dataSeek:0]; + [structureResult dataSeek:0]; + + [xmlString appendFormat:@"\t<table_structure name=\"%@\">\n", [self xmlTableName]]; + + for (i = 0; i < [structureResult numOfRows]; i++) + { + NSDictionary *row = [structureResult fetchRowAsDictionary]; + + [xmlString appendFormat:@"\t\t<field field=\"%@\" type=\"%@\" null=\"%@\" key=\"%@\" default=\"%@\" extra=\"%@\" />\n", + [row objectForKey:@"Field"], + [row objectForKey:@"Type"], + [row objectForKey:@"Null"], + [row objectForKey:@"Key"], + [row objectForKey:@"Default"], + [row objectForKey:@"Extra"]]; + } + + NSDictionary *row = [statusResult fetchRowAsDictionary]; + + [xmlString appendFormat:@"\n\t\t<options name=\"%@\" engine=\"%@\" version=\"%@\" row_format=\"%@\" rows=\"%@\" avg_row_length=\"%@\" data_length=\"%@\" max_data_length=\"%@\" index_length=\"%@\" data_free=\"%@\" create_time=\"%@\" update_time=\"%@\" collation=\"%@\" create_options=\"%@\" comment=\"%@\" />\n", + [row objectForKey:@"Name"], + [row objectForKey:@"Engine"], + [row objectForKey:@"Version"], + [row objectForKey:@"Row_format"], + [row objectForKey:@"Rows"], + [row objectForKey:@"Avg_row_length"], + [row objectForKey:@"Data_length"], + [row objectForKey:@"Max_data_length"], + [row objectForKey:@"Index_length"], + [row objectForKey:@"Data_free"], + [row objectForKey:@"Create_time"], + [row objectForKey:@"Update_time"], + [row objectForKey:@"Collation"], + [row objectForKey:@"Create_options"], + [row objectForKey:@"Comment"]]; + + [xmlString appendFormat:@"\t</table_structure>\n\n"]; + } + } + + if (([self xmlFormat] == SPXMLExportMySQLFormat) && [self xmlOutputIncludeContent]) { + [xmlString appendFormat:@"\t<table_data name=\"%@\">\n\n", [self xmlTableName]]; + } + + [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; } else { totalRows = [[self xmlDataArray] count]; } - // Set up an array of encoded field names as opening and closing tags - fieldNames = ([self xmlDataArray]) ? [[self xmlDataArray] objectAtIndex:0] : [streamingResult fetchFieldNames]; + // Only proceed to export the content if this is not a table export or it is and include content is selected + if ((!isTableExport) || (isTableExport && [self xmlOutputIncludeContent])) { - [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; - - // Set up the starting row, which is 0 for streaming result sets and - // 1 for supplied arrays which include the column headers as the first row. - currentRowIndex = 0; - - if ([self xmlDataArray]) currentRowIndex++; - - // Drop into the processing loop - NSAutoreleasePool *xmlExportPool = [[NSAutoreleasePool alloc] init]; - - currentPoolDataLength = 0; - - // Inform the delegate that we are about to start writing the data to disk - [delegate performSelectorOnMainThread:@selector(xmlExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO]; - - while (1) - { - // Check for cancellation flag - if ([self isCancelled]) { - if (streamingResult) { - [connection cancelCurrentQuery]; - [streamingResult cancelResultLoad]; - } - - [xmlExportPool release]; - [pool release]; + // Set up an array of encoded field names as opening and closing tags + fieldNames = ([self xmlDataArray]) ? NSArrayObjectAtIndex([self xmlDataArray], 0) : [streamingResult fetchFieldNames]; + + for (i = 0; i < [fieldNames count]; i++) + { + [xmlTags addObject:[NSMutableArray array]]; - return; + [NSArrayObjectAtIndex(xmlTags, i) addObject:[NSString stringWithFormat:@"\t\t<%@>", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]]; + [NSArrayObjectAtIndex(xmlTags, i) addObject:[NSString stringWithFormat:@"</%@>\n", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]]; } - // Retrieve the next row from the supplied data, either directly from the array... - if ([self xmlDataArray]) { - xmlRow = NSArrayObjectAtIndex([self xmlDataArray], currentRowIndex); - } - // Or by reading an appropriate row from the streaming result - else { - xmlRow = [streamingResult fetchNextRowAsArray]; - - if (!xmlRow) break; + // If required, write an opening tag in the form of the table name + if ([self xmlFormat] == SPXMLExportPlainFormat) { + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t<%@>\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; } - // Get the cell count if we don't already have it stored - if (!xmlRowCount) xmlRowCount = [xmlRow count]; + // Set up the starting row, which is 0 for streaming result sets and + // 1 for supplied arrays which include the column headers as the first row. + currentRowIndex = 0; + + if ([self xmlDataArray]) currentRowIndex++; + + // Drop into the processing loop + NSAutoreleasePool *xmlExportPool = [[NSAutoreleasePool alloc] init]; + + currentPoolDataLength = 0; - // Construct the row - [xmlString setString:@"\t<row>\n"]; + // Inform the delegate that we are about to start writing the data to disk + [delegate performSelectorOnMainThread:@selector(xmlExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO]; - for (i = 0; i < xmlRowCount; i++) + while (1) { // Check for cancellation flag if ([self isCancelled]) { @@ -167,76 +216,126 @@ return; } - BOOL dataIsNULL = NO; - id data = NSArrayObjectAtIndex(xmlRow, i); + // Retrieve the next row from the supplied data, either directly from the array... + if ([self xmlDataArray]) { + xmlRow = NSArrayObjectAtIndex([self xmlDataArray], currentRowIndex); + } + // Or by reading an appropriate row from the streaming result + else { + xmlRow = [streamingResult fetchNextRowAsArray]; + + if (!xmlRow) break; + } - // Retrieve the contents of this tag - if ([data isKindOfClass:[NSData class]]) { - dataConversionString = [[NSString alloc] initWithData:data encoding:[self exportOutputEncoding]]; + // Get the cell count if we don't already have it stored + if (!xmlRowCount) xmlRowCount = [xmlRow count]; + + // Construct the row + [xmlString setString:@"\t<row>\n"]; + + for (i = 0; i < xmlRowCount; i++) + { + // Check for cancellation flag + if ([self isCancelled]) { + if (streamingResult) { + [connection cancelCurrentQuery]; + [streamingResult cancelResultLoad]; + } + + [xmlExportPool release]; + [pool release]; + + return; + } - if (dataConversionString == nil) { - dataConversionString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + BOOL dataIsNULL = NO; + id data = NSArrayObjectAtIndex(xmlRow, i); + + // Retrieve the contents of this tag + if ([data isKindOfClass:[NSData class]]) { + dataConversionString = [[NSString alloc] initWithData:data encoding:[self exportOutputEncoding]]; + + if (dataConversionString == nil) { + dataConversionString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + } + + [xmlItem setString:[NSString stringWithString:dataConversionString]]; + [dataConversionString release]; + } + else if ([data isKindOfClass:[NSNull class]]) { + dataIsNULL = YES; + + if ([self xmlFormat] == SPXMLExportPlainFormat) { + [xmlItem setString:[self xmlNULLString]]; + } + } + else if ([data isKindOfClass:[MCPGeometryData class]]) { + [xmlItem setString:[data wktString]]; + } + else { + [xmlItem setString:[data description]]; } - [xmlItem setString:[NSString stringWithString:dataConversionString]]; - [dataConversionString release]; - } - else if ([data isKindOfClass:[NSNull class]]) { - dataIsNULL = YES; - } - else if ([data isKindOfClass:[MCPGeometryData class]]) { - [xmlItem setString:[data wktString]]; - } - else { - [xmlItem setString:[data description]]; + if ([self xmlFormat] == SPXMLExportMySQLFormat) { + [xmlString appendFormat:@"\t\t<field name=\"%@\"", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]; + + if (dataIsNULL) { + [xmlString appendString:@" xsi:nil=\"true\" \\>\n"]; + } + else { + [xmlString appendFormat:@">%@</field>\n", [xmlItem HTMLEscapeString]]; + } + } + else if ([self xmlFormat] == SPXMLExportPlainFormat) { + // Add the opening and closing tag and the contents to the XML string + [xmlString appendString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(xmlTags, i), 0)]; + [xmlString appendString:[xmlItem HTMLEscapeString]]; + [xmlString appendString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(xmlTags, i), 1)]; + } } - - [xmlString appendFormat:@"\t\t<field name=\"%@\"", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]; - if (dataIsNULL) { - [xmlString appendString:@" xsi:nil=\"true\" \\>\n"]; - } - else { - [xmlString appendFormat:@">%@</field>\n", [xmlItem HTMLEscapeString]]; - } - } - - [xmlString appendString:@"\t</row>\n\n"]; + [xmlString appendString:@"\t</row>\n\n"]; + + // Record the total length for use with pool flushing + currentPoolDataLength += [xmlString length]; + + // Write the row to the filehandle + [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + + // Update the progress counter and progress bar + currentRowIndex++; + + // Update the progress + if (totalRows && (currentRowIndex * ([self exportMaxProgress] / totalRows)) > lastProgressValue) { - // Record the total length for use with pool flushing - currentPoolDataLength += [xmlString length]; - - // Write the row to the filehandle - [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; - - // Update the progress counter and progress bar - currentRowIndex++; - - // Update the progress - if (totalRows && (currentRowIndex * ([self exportMaxProgress] / totalRows)) > lastProgressValue) { + NSInteger progress = (currentRowIndex * ([self exportMaxProgress] / totalRows)); + + [self setExportProgressValue:progress]; + + lastProgressValue = progress; + } - NSInteger progress = (currentRowIndex * ([self exportMaxProgress] / totalRows)); + // Inform the delegate that the export's progress has been updated + [delegate performSelectorOnMainThread:@selector(xmlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; - [self setExportProgressValue:progress]; - - lastProgressValue = progress; + // Drain the autorelease pool as required to keep memory usage low + if (currentPoolDataLength > 250000) { + [xmlExportPool release]; + xmlExportPool = [[NSAutoreleasePool alloc] init]; + } + + // If an array was supplied and we've processed all rows, break + if ([self xmlDataArray] && totalRows == currentRowIndex) break; } - // Inform the delegate that the export's progress has been updated - [delegate performSelectorOnMainThread:@selector(xmlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; - - // Drain the autorelease pool as required to keep memory usage low - if (currentPoolDataLength > 250000) { - [xmlExportPool release]; - xmlExportPool = [[NSAutoreleasePool alloc] init]; + if (([self xmlFormat] == SPXMLExportMySQLFormat) && isTableExport) { + [[self exportOutputFile] writeData:[@"\t</table_data>\n\n" dataUsingEncoding:[self exportOutputEncoding]]]; + } + else if ([self xmlFormat] == SPXMLExportPlainFormat) { + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; } - // If an array was supplied and we've processed all rows, break - if ([self xmlDataArray] && totalRows == currentRowIndex) break; - } - - if ((![self xmlDataArray]) && [self xmlTableName]) { - [[self exportOutputFile] writeData:[@"\t</table_data>\n\n" dataUsingEncoding:[self exportOutputEncoding]]]; + [xmlExportPool release]; } // Write data to disk @@ -248,7 +347,6 @@ // Inform the delegate that the export process is complete [delegate performSelectorOnMainThread:@selector(xmlExportProcessComplete:) withObject:self waitUntilDone:NO]; - [xmlExportPool release]; [pool release]; } @@ -259,8 +357,7 @@ { if (xmlDataArray) [xmlDataArray release], xmlDataArray = nil; if (xmlTableName) [xmlTableName release], xmlTableName = nil; - - [xmlNULLString release], xmlNULLString = nil; + if (xmlNULLString) [xmlNULLString release], xmlNULLString = nil; [super dealloc]; } |