diff options
21 files changed, 351 insertions, 206 deletions
diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h index adcecb05..7e60ccb2 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h @@ -51,11 +51,6 @@ /** * */ -- (BOOL)connectionEncodingViaLatin1:(id)connection; - -/** - * - */ - (NSString *)keychainPasswordForConnection:(id)connection; /** @@ -66,11 +61,6 @@ /** * */ -- (NSString *)onReconnectShouldUseEncoding:(id)connection; - -/** - * - */ - (void)noConnectionAvailable:(id)connection; /** @@ -91,11 +81,6 @@ /** * */ -- (NSString *)connectionEncoding; - -/** - * - */ - (BOOL)navigatorSchemaPathExistsForDatabase:(NSString*)dbname; /** @@ -153,7 +138,11 @@ NSString *connectionSocket; NSInteger maxAllowedPacketSize; unsigned long connectionThreadId; - + + NSString *encoding, *previousEncoding; + NSStringEncoding *stringEncoding; + BOOL encodingUsesLatin1Transport, previousEncodingUsesLatin1Transport; + NSInteger currentProxyState; double lastQueryExecutionTime; @@ -318,8 +307,13 @@ void performThreadedKeepAlive(void *ptr); - (NSString *)findSocketPath; // Encoding -- (void)setEncoding:(NSStringEncoding)theEncoding; -- (NSStringEncoding)encoding; +- (BOOL)setEncoding:(NSString *)theEncoding; +- (NSString *)encoding; +- (NSStringEncoding)stringEncoding; +- (BOOL)setEncodingUsesLatin1Transport:(BOOL)useLatin1; +- (BOOL)encodingUsesLatin1Transport; +- (void)storeEncodingForRestoration; +- (void)restoreStoredEncoding; // Time zone - (void)setTimeZone:(NSTimeZone *)iTimeZone; diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m index 8ef0fb37..b619db67 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m @@ -94,7 +94,11 @@ static BOOL sTruncateLongFieldInLogs = YES; return nil; } - mEncoding = NSISOLatin1StringEncoding; + encoding = [[NSString alloc] initWithString:@"latin1"]; + previousEncoding = nil; + stringEncoding = NSISOLatin1StringEncoding; + encodingUsesLatin1Transport = NO; + previousEncodingUsesLatin1Transport = NO; mConnectionFlags = kMCPConnectionDefaultOption; // Anything that performs a mysql_net_read is not thread-safe: mysql queries, pings @@ -401,7 +405,11 @@ static BOOL sTruncateLongFieldInLogs = YES; lastKeepAliveTime = 0; automaticReconnectAttempts = 0; pingFailureCount = 0; - mEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(mConnection)]; + const char *mysqlStringEncoding = mysql_character_set_name(mConnection); + [encoding release]; + encoding = [[NSString alloc] initWithUTF8String:mysqlStringEncoding]; + stringEncoding = [MCPConnection encodingForMySQLEncoding:mysqlStringEncoding]; + encodingUsesLatin1Transport = NO; [self setLastErrorMessage:nil]; connectionThreadId = mConnection->thread_id; [self timeZone]; // Getting the timezone used by the server. @@ -469,23 +477,15 @@ static BOOL sTruncateLongFieldInLogs = YES; - (BOOL)reconnect { NSAutoreleasePool *reconnectionPool = [[NSAutoreleasePool alloc] init]; - NSString *currentEncoding = nil; - BOOL currentEncodingUsesLatin1Transport = NO; + NSString *currentEncoding = [NSString stringWithString:encoding]; + BOOL currentEncodingUsesLatin1Transport = encodingUsesLatin1Transport; NSString *currentDatabase = nil; - // Store the currently selected database and encoding so they can be re-set if reconnection was successful + // Store the currently selected database so it can be re-set if reconnection was successful if (delegate && [delegate respondsToSelector:@selector(onReconnectShouldSelectDatabase:)] && [delegate onReconnectShouldSelectDatabase:self]) { currentDatabase = [NSString stringWithString:[delegate onReconnectShouldSelectDatabase:self]]; } - if (delegate && [delegate respondsToSelector:@selector(onReconnectShouldUseEncoding:)]) { - currentEncoding = [NSString stringWithString:[delegate onReconnectShouldUseEncoding:self]]; - } - - if (delegate && [delegate respondsToSelector:@selector(connectionEncodingViaLatin1:)]) { - currentEncodingUsesLatin1Transport = [delegate connectionEncodingViaLatin1:self]; - } - // Close the connection if it exists. if (mConnected) { mysql_close(mConnection); @@ -579,11 +579,8 @@ static BOOL sTruncateLongFieldInLogs = YES; } if (currentEncoding) { - [self queryString:[NSString stringWithFormat:@"/*!40101 SET NAMES '%@' */", currentEncoding]]; - [self setEncoding:[MCPConnection encodingForMySQLEncoding:[currentEncoding UTF8String]]]; - if (currentEncodingUsesLatin1Transport) { - [self queryString:@"/*!40101 SET CHARACTER_SET_RESULTS=latin1 */"]; - } + [self setEncoding:currentEncoding]; + [self setEncodingUsesLatin1Transport:currentEncodingUsesLatin1Transport]; } } else { @@ -653,7 +650,6 @@ static BOOL sTruncateLongFieldInLogs = YES; // Note that a return of "NO" here has already asked the user, so if reconnect fails, // return failure. if ([self reconnect]) { - [self restoreConnectionDetails]; return YES; } return NO; @@ -851,12 +847,8 @@ void performThreadedKeepAlive(void *ptr) connectionStartTime = mach_absolute_time(); [self fetchMaxAllowedPacket]; - if (delegate && [delegate respondsToSelector:@selector(onReconnectShouldUseEncoding:)]) { - [self queryString:[NSString stringWithFormat:@"/*!40101 SET NAMES '%@' */", [NSString stringWithString:[delegate onReconnectShouldUseEncoding:self]]]]; - if (delegate && [delegate respondsToSelector:@selector(connectionEncodingViaLatin1:)]) { - if ([delegate connectionEncodingViaLatin1:self]) [self queryString:@"/*!40101 SET CHARACTER_SET_RESULTS=latin1 */"]; - } - } + [self setEncoding:encoding]; + [self setEncodingUsesLatin1Transport:encodingUsesLatin1Transport]; } /** @@ -1241,7 +1233,11 @@ void performThreadedKeepAlive(void *ptr) } mConnected = YES; - mEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(mConnection)]; + const char *mysqlStringEncoding = mysql_character_set_name(mConnection); + [encoding release]; + encoding = [[NSString alloc] initWithUTF8String:mysqlStringEncoding]; + stringEncoding = [MCPConnection encodingForMySQLEncoding:mysqlStringEncoding]; + encodingUsesLatin1Transport = NO; // Getting the timezone used by the server. [self timeZone]; @@ -1267,6 +1263,14 @@ void performThreadedKeepAlive(void *ptr) if (dbName == nil) return NO; if (mConnected) { + + // Ensure the change is made in UTF8 to avoid encoding problems + BOOL changeEncoding = ![[self encoding] isEqualToString:@"utf8"]; + if (changeEncoding) { + [self storeEncodingForRestoration]; + [self setEncoding:@"utf8"]; + } + const char *theDBName = [self cStringFromString:dbName]; [self lockConnection]; if (0 == mysql_select_db(mConnection, theDBName)) { @@ -1274,6 +1278,8 @@ void performThreadedKeepAlive(void *ptr) return YES; } [self unlockConnection]; + + if (changeEncoding) [self restoreStoredEncoding]; } [self setLastErrorMessage:nil]; @@ -1391,7 +1397,7 @@ void performThreadedKeepAlive(void *ptr) */ - (NSString *)prepareString:(NSString *)theString { - NSData *theCData = [theString dataUsingEncoding:mEncoding allowLossyConversion:YES]; + NSData *theCData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]; unsigned long theLength = [theCData length]; // const char *theCStringBuffer = [self cStringFromString:theString]; // unsigned long theLength = [theString length]; @@ -1407,7 +1413,7 @@ void performThreadedKeepAlive(void *ptr) // theLength = strlen(theCStringBuffer); theCEscBuffer = (char *)calloc(sizeof(char),(theLength * 2) + 1); theEscapedLength = mysql_real_escape_string(mConnection, theCEscBuffer, [theCData bytes], theLength); - theReturn = [[NSString alloc] initWithData:[NSData dataWithBytes:theCEscBuffer length:theEscapedLength] encoding:mEncoding]; + theReturn = [[NSString alloc] initWithData:[NSData dataWithBytes:theCEscBuffer length:theEscapedLength] encoding:stringEncoding]; // theReturn = [self stringWithCString:theCEscBuffer]; free(theCEscBuffer); @@ -1456,7 +1462,7 @@ void performThreadedKeepAlive(void *ptr) */ - (MCPResult *)queryString:(NSString *)query { - return [self queryString:query usingEncoding:mEncoding streamingResult:MCPStreamingNone]; + return [self queryString:query usingEncoding:stringEncoding streamingResult:MCPStreamingNone]; } /** @@ -1466,7 +1472,7 @@ void performThreadedKeepAlive(void *ptr) */ - (MCPStreamingResult *)streamingQueryString:(NSString *)query { - return [self queryString:query usingEncoding:mEncoding streamingResult:MCPStreamingFast]; + return [self queryString:query usingEncoding:stringEncoding streamingResult:MCPStreamingFast]; } /** @@ -1478,7 +1484,7 @@ void performThreadedKeepAlive(void *ptr) */ - (MCPStreamingResult *)streamingQueryString:(NSString *)query useLowMemoryBlockingStreaming:(BOOL)fullStream { - return [self queryString:query usingEncoding:mEncoding streamingResult:(fullStream?MCPStreamingLowMem:MCPStreamingFast)]; + return [self queryString:query usingEncoding:stringEncoding streamingResult:(fullStream?MCPStreamingLowMem:MCPStreamingFast)]; } /** @@ -1603,16 +1609,16 @@ void performThreadedKeepAlive(void *ptr) // For normal result sets, fetch the results and unlock the connection if (streamResultType == MCPStreamingNone) { - theResult = [[MCPResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone]; + theResult = [[MCPResult alloc] initWithMySQLPtr:mConnection encoding:stringEncoding timeZone:mTimeZone]; if (!queryCancelled || !queryCancelUsedReconnect) { [self unlockConnection]; } // For streaming result sets, fetch the result pointer and leave the connection locked } else if (streamResultType == MCPStreamingFast) { - theResult = [[MCPStreamingResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone connection:self withFullStreaming:NO]; + theResult = [[MCPStreamingResult alloc] initWithMySQLPtr:mConnection encoding:stringEncoding timeZone:mTimeZone connection:self withFullStreaming:NO]; } else if (streamResultType == MCPStreamingLowMem) { - theResult = [[MCPStreamingResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone connection:self withFullStreaming:YES]; + theResult = [[MCPStreamingResult alloc] initWithMySQLPtr:mConnection encoding:stringEncoding timeZone:mTimeZone connection:self withFullStreaming:YES]; } // Ensure no problem occurred during the result fetch @@ -1922,11 +1928,16 @@ void performThreadedKeepAlive(void *ptr) MYSQL_RES *theResPtr; if (![self checkConnection]) return [[[MCPResult alloc] init] autorelease]; - + + // Ensure UTF8 - where supported - when getting database list. + NSString *currentEncoding = [NSString stringWithString:encoding]; + BOOL currentEncodingUsesLatin1Transport = encodingUsesLatin1Transport; + [self setEncoding:@"utf8"]; + [self lockConnection]; if ((dbsName == nil) || ([dbsName isEqualToString:@""])) { if (theResPtr = mysql_list_dbs(mConnection, NULL)) { - theResult = [[MCPResult alloc] initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + theResult = [[MCPResult alloc] initWithResPtr: theResPtr encoding:stringEncoding timeZone:mTimeZone]; } else { theResult = [[MCPResult alloc] init]; @@ -1936,13 +1947,17 @@ void performThreadedKeepAlive(void *ptr) const char *theCDBsName = (const char *)[self cStringFromString:dbsName]; if (theResPtr = mysql_list_dbs(mConnection, theCDBsName)) { - theResult = [[MCPResult alloc] initWithResPtr:theResPtr encoding:mEncoding timeZone:mTimeZone]; + theResult = [[MCPResult alloc] initWithResPtr:theResPtr encoding:stringEncoding timeZone:mTimeZone]; } else { theResult = [[MCPResult alloc] init]; } } [self unlockConnection]; + + // Restore the connection encoding if necessary + [self setEncoding:currentEncoding]; + [self setEncodingUsesLatin1Transport:currentEncodingUsesLatin1Transport]; if (theResult) { [theResult autorelease]; @@ -1979,7 +1994,7 @@ void performThreadedKeepAlive(void *ptr) [self lockConnection]; if ((tablesName == nil) || ([tablesName isEqualToString:@""])) { if (theResPtr = mysql_list_tables(mConnection, NULL)) { - theResult = [[MCPResult alloc] initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + theResult = [[MCPResult alloc] initWithResPtr: theResPtr encoding:stringEncoding timeZone:mTimeZone]; } else { theResult = [[MCPResult alloc] init]; @@ -1988,7 +2003,7 @@ void performThreadedKeepAlive(void *ptr) else { const char *theCTablesName = (const char *)[self cStringFromString:tablesName]; if (theResPtr = mysql_list_tables(mConnection, theCTablesName)) { - theResult = [[MCPResult alloc] initWithResPtr: theResPtr encoding: mEncoding timeZone:mTimeZone]; + theResult = [[MCPResult alloc] initWithResPtr: theResPtr encoding:stringEncoding timeZone:mTimeZone]; } else { theResult = [[MCPResult alloc] init]; @@ -2276,7 +2291,7 @@ void performThreadedKeepAlive(void *ptr) unsigned long queryCStringLength; // Get the doc encoding due to pref settings etc, defaulting to UTF8 - NSString *docEncoding = [[self delegate] connectionEncoding]; + NSString *docEncoding = [self encoding]; if (!docEncoding) docEncoding = @"utf8"; NSStringEncoding theConnectionEncoding = [MCPConnection encodingForMySQLEncoding:[self cStringFromString:docEncoding]]; @@ -2559,7 +2574,7 @@ void performThreadedKeepAlive(void *ptr) if (mConnected && (mConnection != NULL)) { if (theResPtr = mysql_list_processes(mConnection)) { - result = [[MCPResult alloc] initWithResPtr:theResPtr encoding:mEncoding timeZone:mTimeZone]; + result = [[MCPResult alloc] initWithResPtr:theResPtr encoding:stringEncoding timeZone:mTimeZone]; } else { result = [[MCPResult alloc] init]; @@ -2619,23 +2634,118 @@ void performThreadedKeepAlive(void *ptr) #pragma mark Encoding /** - * Sets the encoding used by the server for data transfer. - * Used to make sure the output of the query result is ok even for non-ascii characters - * The character set (encoding) used by the db is passed to the MCPConnection object upon connection, - * so most likely the encoding (from -encoding) method is already the proper one. - * That is to say : It's unlikely you will need to call this method directly, and #{if ever you use it, do it at your own risks}. + * Sets the encoding for the database connection. + * This sends a "SET NAMES" command to the server, as appropriate, and + * also updates the class to decode the returned strings correctly. + * If an encoding name unsupported by MySQL is encountered, a FALSE + * status will be returned, and errors will be updated. + * If an encoding name not supported by this class is encountered, a + * warning will be logged to console but the MySQL connection will still + * be updated. + * This resets any setting to use Latin1 transport for the connection. */ -- (void)setEncoding:(NSStringEncoding)theEncoding +- (BOOL)setEncoding:(NSString *)theEncoding { - mEncoding = theEncoding; + if ([theEncoding isEqualToString:encoding] && !encodingUsesLatin1Transport) return YES; + + // MySQL < 4.1 will fail + if ([self serverMajorVersion] < 4 + || ([self serverMinorVersion] == 4 && [self serverMinorVersion] < 1)) + { + return NO; + } + + // Attempt to set the encoding of the connection, restoring the connection on failure + [self queryString:[NSString stringWithFormat:@"SET NAMES %@", [theEncoding tickQuotedString]]]; + if ([self queryErrored]) { + [self queryString:[NSString stringWithFormat:@"SET NAMES %@", [encoding tickQuotedString]]]; + if (encodingUsesLatin1Transport) [self queryString:@"SET CHARACTER_SET_RESULTS=latin1"]; + return NO; + } + + // The connection set was successful - update stored details + [encoding release]; + encoding = [[NSString alloc] initWithString:theEncoding]; + stringEncoding = [MCPConnection encodingForMySQLEncoding:[encoding UTF8String]]; + encodingUsesLatin1Transport = NO; + return YES; } /** - * Gets the encoding for the connection + * Returns the currently active encoding. */ -- (NSStringEncoding)encoding +- (NSString *)encoding { - return mEncoding; + return [NSString stringWithString:encoding]; +} + +/** + * Gets the string encoding for the connection + */ +- (NSStringEncoding)stringEncoding +{ + return stringEncoding; +} + +/** + * Sets whether the connection encoding should be transmitted via Latin1. + * This is a method purely for backwards compatibility: old codebases or + * applications often believed they stored UTF8 data in UTF8 tables, but + * for the purposes of storing and reading the data, the MySQL connecttion + * was never changed from the default Latin1. UTF8 data was therefore + * altered during transit and stored as UTF8 encoding Latin1 pairs which + * together make up extended UTF8 characters. Reading these characters back + * over Latin1 makes the data editable in a compatible fashion. + */ +- (BOOL)setEncodingUsesLatin1Transport:(BOOL)useLatin1 +{ + if (encodingUsesLatin1Transport == useLatin1) return YES; + + // If disabling Latin1 transport, restore the connection encoding + if (!useLatin1) return [self setEncoding:encoding]; + + // Otherwise attempt to set Latin1 transport + [self queryString:@"SET CHARACTER_SET_RESULTS=latin1"]; + if ([self queryErrored]) return NO; + [self queryString:@"SET CHARACTER_SET_CLIENT=latin1"]; + if ([self queryErrored]) { + [self setEncoding:encoding]; + return NO; + } + encodingUsesLatin1Transport = YES; + return YES; +} + +/** + * Return whether the current connection is set to use Latin1 tranport. + */ +- (BOOL)encodingUsesLatin1Transport +{ + return encodingUsesLatin1Transport; +} + +/** + * Store a previous encoding setting. This allows easy restoration + * later - useful if certain tasks require the encoding to be + * temporarily changed. + */ +- (void)storeEncodingForRestoration +{ + if (previousEncoding) [previousEncoding release]; + previousEncoding = [[NSString alloc] initWithString:encoding]; + previousEncodingUsesLatin1Transport = encodingUsesLatin1Transport; +} + +/** + * Restore a previously stored encoding setting, if one is stored. + * Useful if certain tasks required the encoding to be temporarily changed. + */ +- (void)restoreStoredEncoding +{ + if (!previousEncoding || !mConnected) return; + + [self setEncoding:previousEncoding]; + [self setEncodingUsesLatin1Transport:previousEncodingUsesLatin1Transport]; } #pragma mark - @@ -2734,7 +2844,7 @@ void performThreadedKeepAlive(void *ptr) [self lockConnection]; if (0 == mysql_query(mConnection, queryString)) { if (mysql_field_count(mConnection) != 0) { - MCPResult *r = [[MCPResult alloc] initWithMySQLPtr:mConnection encoding:mEncoding timeZone:mTimeZone]; + MCPResult *r = [[MCPResult alloc] initWithMySQLPtr:mConnection encoding:stringEncoding timeZone:mTimeZone]; [r setReturnDataAsStrings:YES]; NSArray *a = [r fetchRowAsArray]; [r autorelease]; @@ -2757,7 +2867,7 @@ void performThreadedKeepAlive(void *ptr) - (NSInteger)getMaxAllowedPacket { MCPResult *r; - r = [self queryString:@"SELECT @@global.max_allowed_packet" usingEncoding:mEncoding streamingResult:NO]; + r = [self queryString:@"SELECT @@global.max_allowed_packet" usingEncoding:stringEncoding streamingResult:NO]; if (![[self getLastErrorMessage] isEqualToString:@""]) { if ([self isConnected]) { NSString *errorMessage = [NSString stringWithFormat:@"An error occured while retrieving max_allowed_packet size:\n\n%@", [self getLastErrorMessage]]; @@ -2828,7 +2938,7 @@ void performThreadedKeepAlive(void *ptr) return (const char *)NULL; } - theData = [NSMutableData dataWithData:[theString dataUsingEncoding:mEncoding allowLossyConversion:YES]]; + theData = [NSMutableData dataWithData:[theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]]; [theData increaseLengthBy:1]; return (const char *)[theData bytes]; @@ -2864,7 +2974,7 @@ void performThreadedKeepAlive(void *ptr) if (theCString == NULL) return @""; theData = [NSData dataWithBytes:theCString length:(strlen(theCString))]; - theString = [[NSString alloc] initWithData:theData encoding:mEncoding]; + theString = [[NSString alloc] initWithData:theData encoding:stringEncoding]; if (theString) { [theString autorelease]; @@ -2922,7 +3032,7 @@ void performThreadedKeepAlive(void *ptr) if (theTextData == nil) return nil; - theString = [[NSString alloc] initWithData:theTextData encoding:mEncoding]; + theString = [[NSString alloc] initWithData:theTextData encoding:stringEncoding]; if (theString) { [theString autorelease]; @@ -2951,6 +3061,8 @@ void performThreadedKeepAlive(void *ptr) [connectionProxy disconnect]; } + [encoding release]; + if (previousEncoding) [previousEncoding release]; [keepAliveTimer invalidate]; [keepAliveTimer release]; [NSObject cancelPreviousPerformRequestsWithTarget:self]; diff --git a/Resources/English.lproj/DBView.strings b/Resources/English.lproj/DBView.strings Binary files differindex a726b3f5..9310758a 100644 --- a/Resources/English.lproj/DBView.strings +++ b/Resources/English.lproj/DBView.strings diff --git a/Resources/English.lproj/Localizable.strings b/Resources/English.lproj/Localizable.strings Binary files differindex e345185b..62fc3d58 100644 --- a/Resources/English.lproj/Localizable.strings +++ b/Resources/English.lproj/Localizable.strings diff --git a/Resources/English.lproj/Navigator.strings b/Resources/English.lproj/Navigator.strings Binary files differindex 4ba3c740..d7debae9 100644 --- a/Resources/English.lproj/Navigator.strings +++ b/Resources/English.lproj/Navigator.strings diff --git a/Resources/English.lproj/Preferences.strings b/Resources/English.lproj/Preferences.strings Binary files differindex 90c165a3..97922977 100644 --- a/Resources/English.lproj/Preferences.strings +++ b/Resources/English.lproj/Preferences.strings diff --git a/Resources/English.lproj/QueryFavoriteManager.strings b/Resources/English.lproj/QueryFavoriteManager.strings Binary files differindex aa062ee2..f34470a9 100644 --- a/Resources/English.lproj/QueryFavoriteManager.strings +++ b/Resources/English.lproj/QueryFavoriteManager.strings diff --git a/Source/SPConnectionDelegate.m b/Source/SPConnectionDelegate.m index 5231c996..b1e586f1 100644 --- a/Source/SPConnectionDelegate.m +++ b/Source/SPConnectionDelegate.m @@ -70,15 +70,6 @@ } /** - * Invoked when the framework is in the process of reconnecting to the server and needs to know - * what encoding to use for the connection. - */ -- (NSString *)onReconnectShouldUseEncoding:(id)connection -{ - return _encoding; -} - -/** * Invoked when the current connection needs a password from the Keychain. */ - (NSString *)keychainPasswordForConnection:(MCPConnection *)connection diff --git a/Source/SPCopyTable.m b/Source/SPCopyTable.m index b1b663a4..db8ce49d 100644 --- a/Source/SPCopyTable.m +++ b/Source/SPCopyTable.m @@ -295,7 +295,7 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2003; else if ([cellData isSPNotLoaded]) [result appendFormat:@"%@\t", NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields")]; else if ([cellData isKindOfClass:[NSData class]]) { - NSString *displayString = [[NSString alloc] initWithData:cellData encoding:connectionEncoding]; + NSString *displayString = [[NSString alloc] initWithData:cellData encoding:[mySQLConnection stringEncoding]]; if (!displayString) displayString = [[NSString alloc] initWithData:cellData encoding:NSASCIIStringEncoding]; if (displayString) { [result appendFormat:@"%@\t", displayString]; @@ -520,7 +520,7 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2003; NSUInteger rowIndex = [selectedRows firstIndex]; NSString *nullString = [prefs objectForKey:SPNullValue]; Class nsDataClass = [NSData class]; - NSStringEncoding connectionEncoding = [mySQLConnection encoding]; + NSStringEncoding connectionEncoding = [mySQLConnection stringEncoding]; while ( rowIndex != NSNotFound ) { for ( c = 0; c < numColumns; c++) { @@ -681,7 +681,7 @@ NSInteger MENU_EDIT_COPY_AS_SQL = 2003; // Otherwise, ensure the cell is represented as a short string if ([contentString isKindOfClass:[NSData class]]) { - contentString = [contentString shortStringRepresentationUsingEncoding:[mySQLConnection encoding]]; + contentString = [contentString shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; } else if ([contentString length] > 500) { contentString = [contentString substringToIndex:500]; } diff --git a/Source/SPCustomQuery.m b/Source/SPCustomQuery.m index 82b40465..adb806ad 100644 --- a/Source/SPCustomQuery.m +++ b/Source/SPCustomQuery.m @@ -1792,7 +1792,7 @@ } if ([theValue isKindOfClass:[NSData class]]) - return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection encoding]]; + return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; if ([theValue isNSNull]) return [prefs objectForKey:SPNullValue]; @@ -2262,9 +2262,9 @@ id editData = [[fieldEditor editWithObject:originalData fieldName:[columnDefinition objectForKey:@"name"] - usingEncoding:[mySQLConnection encoding] - isObjectBlob:isBlob - isEditable:isFieldEditable + usingEncoding:[mySQLConnection stringEncoding] + isObjectBlob:isBlob + isEditable:isFieldEditable withWindow:[tableDocumentInstance parentWindow]] retain]; if ( editData ) diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index ac1eadc0..98129f19 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -233,7 +233,7 @@ if ([[[importFormatPopup selectedItem] title] isEqualToString:@"SQL"]) encoding = NSUTF8StringEncoding; else - encoding = [MCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] UTF8String]]; + encoding = [mySQLConnection stringEncoding]; if(![[[NSPasteboard generalPasteboard] stringForType:NSStringPboardType] writeToFile:importFileName atomically:NO encoding:encoding error:nil]) { NSBeep(); @@ -406,7 +406,7 @@ sqlEncoding = [fileEncodingDetector encoding]; [fileEncodingDetector release]; if ([MCPConnection mySQLEncodingForStringEncoding:sqlEncoding]) { - connectionEncodingToRestore = [tableDocumentInstance connectionEncoding]; + connectionEncodingToRestore = [mySQLConnection encoding]; [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", [MCPConnection mySQLEncodingForStringEncoding:sqlEncoding]]]; } } @@ -673,7 +673,7 @@ BOOL allDataRead = NO; BOOL insertBaseStringHasEntries; - NSStringEncoding csvEncoding = [MCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] UTF8String]]; + NSStringEncoding csvEncoding = [mySQLConnection stringEncoding]; fieldMappingArray = nil; fieldMappingGlobalValueArray = nil; diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h index 28ee97ad..07c3f7bb 100644 --- a/Source/SPDatabaseDocument.h +++ b/Source/SPDatabaseDocument.h @@ -139,8 +139,6 @@ NSMenu *selectEncodingMenu; BOOL _supportsEncoding; - NSString *_encoding; - BOOL _encodingViaLatin1; BOOL _isConnected; NSInteger _isWorkingLevel; BOOL _mainNibLoaded; @@ -238,8 +236,6 @@ // Encoding methods - (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews; - (NSString *)databaseEncoding; -- (NSString *)connectionEncoding; -- (BOOL)connectionEncodingViaLatin1:(id)connection; - (IBAction)chooseEncoding:(id)sender; - (BOOL)supportsEncoding; - (void)updateEncodingMenuWithSelectedEncoding:(NSNumber *)encodingTag; diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index e99bc702..00831c85 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -83,7 +83,6 @@ if ((self = [super init])) { _mainNibLoaded = NO; - _encoding = [[NSString alloc] initWithString:@"utf8"]; _isConnected = NO; _isWorkingLevel = 0; _isSavedInBundle = NO; @@ -552,7 +551,7 @@ [mainToolbar setVisible:[[spfSession objectForKey:@"isToolbarVisible"] boolValue]]; // Reset database view encoding if differs from default - if([spfSession objectForKey:@"connectionEncoding"] && ![[self connectionEncoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]]) + if([spfSession objectForKey:@"connectionEncoding"] && ![[mySQLConnection encoding] isEqualToString:[spfSession objectForKey:@"connectionEncoding"]]) [self setConnectionEncoding:[spfSession objectForKey:@"connectionEncoding"] reloadingViews:YES]; if(isSelectedTableDefined) { @@ -658,14 +657,6 @@ // ...but hide the icon while the document is temporary if ([self isUntitled]) [[parentWindow standardWindowButton:NSWindowDocumentIconButton] setImage:nil]; - // Set the connection encoding - NSNumber *encodingType = [prefs objectForKey:SPDefaultEncoding]; - if ( [encodingType intValue] == SPEncodingAutodetect ) { - [self setConnectionEncoding:[self databaseEncoding] reloadingViews:NO]; - } else { - [self setConnectionEncoding:[self mysqlEncodingFromEncodingTag:encodingType] reloadingViews:NO]; - } - // Get the mysql version mySQLVersion = [[NSString alloc] initWithString:[mySQLConnection serverVersionString]]; @@ -676,12 +667,24 @@ [spHistoryControllerInstance updateHistoryEntries]; } + // Ensure the connection encoding is set to utf8 for database/table name retrieval + [mySQLConnection setEncoding:@"utf8"]; + // Update the database list [self setDatabases:self]; [chooseDatabaseButton setEnabled:!_isWorkingLevel]; - // For each of the main controllers, assign the current connection + // Set the connection on the tables list instance - this updates the table list while the connection + // is still UTF8 [tablesListInstance setConnection:mySQLConnection]; + + // Set the connection encoding if necessary + NSNumber *encodingType = [prefs objectForKey:SPDefaultEncoding]; + if ( [encodingType intValue] != SPEncodingAutodetect ) { + [self setConnectionEncoding:[self mysqlEncodingFromEncodingTag:encodingType] reloadingViews:NO]; + } + + // For each of the main controllers, assign the current connection [tableSourceInstance setConnection:mySQLConnection]; [tableContentInstance setConnection:mySQLConnection]; [tableRelationsInstance setConnection:mySQLConnection]; @@ -692,9 +695,8 @@ [tableDataInstance setConnection:mySQLConnection]; [extendedTableInfoInstance setConnection:mySQLConnection]; [databaseDataInstance setConnection:mySQLConnection]; -// userManagerInstance.mySqlConnection = mySQLConnection; - // Set the cutom query editor's MySQL version + // Set the custom query editor's MySQL version [customQueryInstance setMySQLversion:mySQLVersion]; [self updateWindowTitle:self]; @@ -1561,41 +1563,32 @@ */ - (void)setConnectionEncoding:(NSString *)mysqlEncoding reloadingViews:(BOOL)reloadViews { - _encodingViaLatin1 = NO; + BOOL useLatin1Transport = NO; // Special-case UTF-8 over latin 1 to allow viewing/editing of mangled data. if ([mysqlEncoding isEqualToString:@"utf8-"]) { - _encodingViaLatin1 = YES; + useLatin1Transport = YES; mysqlEncoding = @"utf8"; } - // set encoding of connection and client - [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", mysqlEncoding]]; - - if (![mySQLConnection queryErrored]) { - if (_encodingViaLatin1) - [mySQLConnection queryString:@"SET CHARACTER_SET_RESULTS=latin1"]; - [mySQLConnection setEncoding:[MCPConnection encodingForMySQLEncoding:[mysqlEncoding UTF8String]]]; - [_encoding release]; - _encoding = [[NSString alloc] initWithString:mysqlEncoding]; - } else { - [mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", [self databaseEncoding]]]; - _encodingViaLatin1 = NO; - if ([mySQLConnection queryErrored]) { - NSLog(@"Error: could not set encoding to %@ nor fall back to database encoding on MySQL %@", mysqlEncoding, [self mySQLVersion]); - return; - } + // Set the connection encoding + if (![mySQLConnection setEncoding:mysqlEncoding]) { + NSLog(@"Error: could not set encoding to %@ nor fall back to database encoding on MySQL %@", mysqlEncoding, [self mySQLVersion]); + return; } + [mySQLConnection setEncodingUsesLatin1Transport:useLatin1Transport]; - // update the selected menu item - if (_encodingViaLatin1) { + // Update the selected menu item + if (useLatin1Transport) { [self updateEncodingMenuWithSelectedEncoding:[self encodingTagFromMySQLEncoding:[NSString stringWithFormat:@"%@-", mysqlEncoding]]]; } else { [self updateEncodingMenuWithSelectedEncoding:[self encodingTagFromMySQLEncoding:mysqlEncoding]]; } + // Update the stored connection encoding to prevent switches + [mySQLConnection storeEncodingForRestoration]; + // Reload stuff as appropriate - [tableDataInstance resetAllData]; if (reloadViews) { if ([tablesListInstance structureLoaded]) [tableSourceInstance reloadTable:self]; if ([tablesListInstance contentLoaded]) [tableContentInstance reloadTable:self]; @@ -1604,23 +1597,6 @@ } /** - * returns the current mysql encoding for this object - */ -- (NSString *)connectionEncoding -{ - return _encoding; -} - -/** - * Returns whether the current encoding should display results via Latin1 transport for backwards compatibility. - * This is a delegate method of MCPKit's MCPConnection class. - */ -- (BOOL)connectionEncodingViaLatin1:(id)connection -{ - return _encodingViaLatin1; -} - -/** * updates the currently selected item in the encoding menu * * @param NSString *encoding - the title of the menu item which will be selected @@ -1707,26 +1683,29 @@ } /** - * Detect and return the database connection encoding. - * TODO: See http://code.google.com/p/sequel-pro/issues/detail?id=134 - some question over why this [historically] uses _connection not _database... + * Detect and return the database encoding. + * Falls back to Latin1. */ - (NSString *)databaseEncoding { MCPResult *charSetResult; - NSString *mysqlEncoding; + NSString *mysqlEncoding = nil; - // MySQL > 4.0 - charSetResult = [mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set_connection'"]; - [charSetResult setReturnDataAsStrings:YES]; - mysqlEncoding = [[charSetResult fetchRowAsDictionary] objectForKey:@"Value"]; - _supportsEncoding = (mysqlEncoding != nil); + // MySQL >= 4.1 + if ([mySQLConnection serverMajorVersion] > 4 + || ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1)) + { + charSetResult = [mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set_database'"]; + [charSetResult setReturnDataAsStrings:YES]; + mysqlEncoding = [[charSetResult fetchRowAsDictionary] objectForKey:@"Value"]; + _supportsEncoding = (mysqlEncoding != nil); // mysql 4.0 or older -> only default character set possible, cannot choose others using "set names xy" - if ( !mysqlEncoding ) { + } else { mysqlEncoding = [[[mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set'"] fetchRowAsDictionary] objectForKey:@"Value"]; } - // older version? -> set encoding to mysql default encoding latin1 + // Fallback or older version? -> set encoding to mysql default encoding latin1 if ( !mysqlEncoding ) { NSLog(@"Error: no character encoding found, mysql version is %@", [self mySQLVersion]); mysqlEncoding = @"latin1"; @@ -3380,7 +3359,7 @@ [session setObject:aString forKey:@"view"]; [session setObject:[NSNumber numberWithBool:[[parentWindow toolbar] isVisible]] forKey:@"isToolbarVisible"]; - [session setObject:[self connectionEncoding] forKey:@"connectionEncoding"]; + [session setObject:[mySQLConnection encoding] forKey:@"connectionEncoding"]; [session setObject:[NSNumber numberWithBool:[tableContentInstance sortColumnIsAscending]] forKey:@"contentSortColIsAsc"]; [session setObject:[NSNumber numberWithInteger:[tableContentInstance pageNumber]] forKey:@"contentPageNumber"]; @@ -4675,7 +4654,6 @@ for (id retainedObject in nibObjectsToRelease) [retainedObject release]; [nibObjectsToRelease release]; - [_encoding release]; [allDatabases release]; [allSystemDatabases release]; [printWebView release]; @@ -4773,6 +4751,9 @@ SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, parentWindow, self, nil, nil, NSLocalizedString(@"Database must have a name.", @"message of panel when no db name is given")); return; } + + // As we're amending identifiers, ensure UTF8 + if (![[mySQLConnection encoding] isEqualToString:@"utf8"]) [mySQLConnection setEncoding:@"utf8"]; NSString *createStatement = [NSString stringWithFormat:@"CREATE DATABASE %@", [[databaseNameField stringValue] backtickQuotedString]]; diff --git a/Source/SPExportController.h b/Source/SPExportController.h index c5dade4a..befde1b9 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -198,12 +198,12 @@ * User defaults */ NSUserDefaults *prefs; - + /** * Previous connection encoding */ NSString *sqlPreviousConnectionEncoding; - + /** * Previous connection encoding was via Latin1 */ diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m index 74e992e4..23eb7666 100644 --- a/Source/SPExportFileUtilities.m +++ b/Source/SPExportFileUtilities.m @@ -65,7 +65,7 @@ NSLocalizedString(@"Table", @"csv export table heading"), [[tables objectAtIndex:0] objectAtIndex:0], lineEnding, - lineEnding] dataUsingEncoding:[connection encoding]]]; + lineEnding] dataUsingEncoding:[connection stringEncoding]]]; } /** diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index cc9aa16e..a34f729b 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -269,8 +269,8 @@ [sqlExporter setSqlInsertDivider:[exportSQLInsertDividerPopUpButton indexOfSelectedItem]]; // Cache the current connection encoding then change it to UTF-8 to allow SQL dumps to work - sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[tableDocumentInstance connectionEncoding]]; - sqlPreviousConnectionEncodingViaLatin1 = [tableDocumentInstance connectionEncodingViaLatin1:nil]; + sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[connection encoding]]; + sqlPreviousConnectionEncodingViaLatin1 = [connection encodingUsesLatin1Transport]; [tableDocumentInstance setConnectionEncoding:@"utf8" reloadingViews:NO]; @@ -354,8 +354,8 @@ [dotExporter setDotDatabaseVersion:[tableDocumentInstance mySQLVersion]]; // Cache the current connection encoding then change it to UTF-8 to allow SQL dumps to work - sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[tableDocumentInstance connectionEncoding]]; - sqlPreviousConnectionEncodingViaLatin1 = [tableDocumentInstance connectionEncodingViaLatin1:nil]; + sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[connection encoding]]; + sqlPreviousConnectionEncodingViaLatin1 = [connection encodingUsesLatin1Transport]; [tableDocumentInstance setConnectionEncoding:@"utf8" reloadingViews:NO]; @@ -386,7 +386,7 @@ for (SPExporter *exporter in exporters) { [exporter setConnection:connection]; - [exporter setExportOutputEncoding:[connection encoding]]; + [exporter setExportOutputEncoding:[connection stringEncoding]]; [exporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; [exporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; [exporter setExportOutputCompressionFormat:[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; diff --git a/Source/SPExtendedTableInfo.m b/Source/SPExtendedTableInfo.m index aac89bb9..f9e0bdb8 100644 --- a/Source/SPExtendedTableInfo.m +++ b/Source/SPExtendedTableInfo.m @@ -348,9 +348,11 @@ [tableSizeFree setStringValue:[self _formatValueWithKey:@"Data_free" inDictionary:statusFields]]; // Set comments + NSString *commentText = [statusFields objectForKey:@"Comment"]; + if (!commentText) commentText = @""; [tableCommentsTextView setEditable:YES]; - [tableCommentsTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCommentsTextView string] length]) replacementString:[statusFields objectForKey:@"Comment"]]; - [tableCommentsTextView setString:[statusFields objectForKey:@"Comment"]]; + [tableCommentsTextView shouldChangeTextInRange:NSMakeRange(0, [[tableCommentsTextView string] length]) replacementString:commentText]; + [tableCommentsTextView setString:commentText]; [tableCommentsTextView didChangeText]; [tableCommentsTextView setEditable:enableInteraction]; diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index 86a960e5..14150607 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -2342,24 +2342,7 @@ [queryString appendFormat:@" WHERE %@", [self argumentForRow:-2]]; } - // If UTF-8 via Latin1 view encoding is set convert the queryString into Latin1 and - // set the MySQL connection to Latin1 before executing this query to allow editing. - // After executing reset all. - if([tableDocumentInstance connectionEncodingViaLatin1:mySQLConnection]) { - - NSStringEncoding currentEncoding = [mySQLConnection encoding]; - NSString *latin1String = [[NSString alloc] initWithCString:[queryString UTF8String] encoding:NSISOLatin1StringEncoding]; - [mySQLConnection setEncoding:NSISOLatin1StringEncoding]; - [mySQLConnection queryString:@"SET NAMES 'latin1'"]; - [mySQLConnection queryString:latin1String]; - [mySQLConnection setEncoding:currentEncoding]; - [mySQLConnection queryString:@"SET NAMES 'utf8'"]; - [mySQLConnection queryString:@"SET CHARACTER_SET_RESULTS=latin1"]; - [latin1String release]; - - } else { - [mySQLConnection queryString:queryString]; - } + [mySQLConnection queryString:queryString]; [fieldValues release]; [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; @@ -3072,7 +3055,7 @@ return [prefs objectForKey:SPNullValue]; if ([theValue isKindOfClass:[NSData class]]) - return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection encoding]]; + return [theValue shortStringRepresentationUsingEncoding:[mySQLConnection stringEncoding]]; if ([theValue isSPNotLoaded]) return NSLocalizedString(@"(not loaded)", @"value shown for hidden blob and text fields"); @@ -3534,9 +3517,9 @@ id editData = [[fieldEditor editWithObject:cellValue fieldName:[[aTableColumn headerCell] stringValue] - usingEncoding:[mySQLConnection encoding] - isObjectBlob:isBlob - isEditable:isFieldEditable + usingEncoding:[mySQLConnection stringEncoding] + isObjectBlob:isBlob + isEditable:isFieldEditable withWindow:[tableDocumentInstance parentWindow]] retain]; if (editData) { diff --git a/Source/SPTableData.m b/Source/SPTableData.m index 8ec18cae..a6a5819a 100644 --- a/Source/SPTableData.m +++ b/Source/SPTableData.m @@ -337,6 +337,7 @@ NSString *encodingString = nil; NSUInteger i, stringStart; unichar quoteCharacter; + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; [columns removeAllObjects]; [columnNames removeAllObjects]; @@ -345,6 +346,12 @@ // Catch unselected tables and return nil if ([tableName isEqualToString:@""] || !tableName) return nil; + // Ensure the encoding is set to UTF8 + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + // Retrieve the CREATE TABLE syntax for the table MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]]; [theResult setReturnDataAsStrings:YES]; @@ -361,6 +368,7 @@ [[tableListInstance valueForKeyPath:@"tablesListView"] deselectAll:nil]; [tableListInstance updateTables:self]; } + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } return nil; @@ -384,6 +392,7 @@ informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")] beginSheetModalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:NULL contextInfo:NULL]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; return nil; } @@ -637,6 +646,8 @@ [encodingString release]; [tableColumns release]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + return tableData; } @@ -689,10 +700,17 @@ NSDictionary *resultRow; NSMutableDictionary *tableColumn, *viewData; NSUInteger i; + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; // Catch unselected views and return nil if ([viewName isEqualToString:@""] || !viewName) return nil; + // Ensure that queries are made in UTF8 + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + // Retrieve the CREATE TABLE syntax for the table MCPResult *theResult = [mySQLConnection queryString: [NSString stringWithFormat: @"SHOW CREATE TABLE %@", [viewName backtickQuotedString] @@ -706,6 +724,7 @@ nil, nil, [NSApp mainWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection getLastErrorMessage]]); + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } return nil; } @@ -722,6 +741,7 @@ informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")] beginSheetModalForWindow:[NSApp mainWindow] modalDelegate:self didEndSelector:NULL contextInfo:NULL]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; return nil; } @@ -738,6 +758,7 @@ nil, nil, [NSApp mainWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving information.\nMySQL said: %@", @"message of panel when retrieving information failed"), [mySQLConnection getLastErrorMessage]]); + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } return nil; } @@ -787,6 +808,8 @@ [tableColumns release]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + return viewData; } @@ -797,6 +820,7 @@ */ - (BOOL)updateStatusInformationForCurrentTable { + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; // Catch unselected tables and return false if ([[tableListInstance tableName] isEqualToString:@""] || ![tableListInstance tableName]) @@ -809,6 +833,12 @@ return TRUE; } + // Ensure queries are run as UTF8 + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + // Run the status query and retrieve as a dictionary. NSMutableString *escapedTableName = [NSMutableString stringWithString:[tableListInstance tableName]]; [escapedTableName replaceOccurrencesOfString:@"'" withString:@"\\\'" options:0 range:NSMakeRange(0, [escapedTableName length])]; @@ -837,6 +867,7 @@ nil, nil, [NSApp mainWindow], self, nil, nil, [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving status data.\nMySQL said: %@", @"message of panel when retrieving view information failed"), [mySQLConnection getLastErrorMessage]]); + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } return FALSE; } @@ -854,6 +885,7 @@ // If the "Engine" key is NULL, a problem occurred when retrieving the table information. if ([[status objectForKey:@"Engine"] isNSNull]) { [status setDictionary:[NSDictionary dictionaryWithObjectsAndKeys:@"Error", @"Engine", [NSString stringWithFormat:NSLocalizedString(@"An error occurred retrieving table information. MySQL said: %@", @"MySQL table info retrieval error message"), [status objectForKey:@"Comment"]], @"Comment", [tableListInstance tableName], @"Name", nil]]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; return FALSE; } @@ -874,6 +906,8 @@ } } + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + return TRUE; } @@ -882,6 +916,14 @@ */ - (BOOL) updateTriggersForCurrentTable { + + // Ensure queries are made in UTF8 + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + MCPResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */", [[tableListInstance tableName] tickQuotedString]]]; [theResult setReturnDataAsStrings:YES]; @@ -894,6 +936,7 @@ [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the trigger information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"), [tableListInstance tableName], [mySQLConnection getLastErrorMessage]]); if (triggers) [triggers release], triggers = nil; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; } return NO; @@ -905,6 +948,8 @@ [triggers addObject:[theResult fetchRowAsDictionary]]; } + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + return YES; } @@ -1109,6 +1154,13 @@ - (NSArray *)primaryKeyColumnNames { + // Ensure that identifier queries occur over UTF8 + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + NSString *selectedTable = [tableListInstance tableName]; if(![selectedTable length]) return nil; @@ -1122,11 +1174,16 @@ r = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@ /*!50003 WHERE `key` = 'PRI'*/", [selectedTable backtickQuotedString]]]; [r setReturnDataAsStrings:YES]; - if([r numOfRows] < 1) return nil; + if([r numOfRows] < 1) { + if (changeEncoding && [mySQLConnection isConnected]) [mySQLConnection restoreStoredEncoding]; + return nil; + } if ([mySQLConnection queryErrored]) { - if ([mySQLConnection isConnected]) + if ([mySQLConnection isConnected]) { NSRunAlertPanel(@"Error", [NSString stringWithFormat:NSLocalizedString(@"An error occured while retrieving the PRIMARY KEY data:\n\n%@",@"message when the query that fetches the primary keys fails"), [mySQLConnection getLastErrorMessage]], @"OK", nil, nil); + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + } return nil; } @@ -1139,6 +1196,8 @@ } } + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + if([keyColumns count]) return keyColumns; return nil; diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m index 7ae16742..b0e4594e 100644 --- a/Source/SPTablesList.m +++ b/Source/SPTablesList.m @@ -70,6 +70,7 @@ NSInteger i; NSString *previousSelectedTable = nil; BOOL previousTableListIsSelectable = tableListIsSelectable; + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; if (selectedTableName) previousSelectedTable = [[NSString alloc] initWithString:selectedTableName]; if (isTableListFiltered) { @@ -92,6 +93,12 @@ // Notify listeners that a query has started [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; + // Use UTF8 for identifier-based queries + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + // 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"]; @@ -104,7 +111,8 @@ } else { for ( i = 0 ; i < [theResult numOfRows] ; i++ ) { resultRow = [theResult fetchRowAsArray]; - [tables addObject:[resultRow objectAtIndex:0]]; + NSString *tableName = [NSString stringWithUTF8String:[[resultRow objectAtIndex:0] cStringUsingEncoding:[mySQLConnection stringEncoding]]]; + [tables addObject:tableName]; if ([[resultRow objectAtIndex:1] isEqualToString:@"VIEW"]) { [tableTypes addObject:[NSNumber numberWithInteger:SPTableTypeView]]; tableListContainsViews = YES; @@ -206,7 +214,11 @@ } } } - */ + */ + + // Restore encoding if appropriate + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + // Notify listeners that the query has finished [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:tableDocumentInstance]; } @@ -695,6 +707,12 @@ { NSAutoreleasePool *selectionChangePool = [[NSAutoreleasePool alloc] init]; NSString *tableEncoding = nil; + NSString *previousEncoding = [mySQLConnection encoding]; + BOOL changeEncoding = ![previousEncoding isEqualToString:@"utf8"]; + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } // Update selection variables and interface NSDictionary *selectionDetails = [NSDictionary dictionaryWithObjectsAndKeys: @@ -702,8 +720,11 @@ [filteredTableTypes objectAtIndex:[tablesListView selectedRow]], @"type", nil]; [self performSelectorOnMainThread:@selector(setSelection:) withObject:selectionDetails waitUntilDone:YES]; - - // Check the encoding if appropriate to determine if an encoding change and reset is required + + // Ensure status information is cached on the working thread + [tableDataInstance updateStatusInformationForCurrentTable]; + + // Check the encoding if appropriate to determine if an encoding change is required if( selectedTableType == SPTableTypeView || selectedTableType == SPTableTypeTable) { // tableEncoding == nil indicates that there was an error while retrieving table data @@ -712,16 +733,14 @@ // If encoding is set to Autodetect, update the connection character set encoding // based on the newly selected table's encoding - but only if it differs from the current encoding. if ([[[NSUserDefaults standardUserDefaults] objectForKey:SPDefaultEncoding] intValue] == SPEncodingAutodetect) { - if (tableEncoding != nil && ![tableEncoding isEqualToString:[tableDocumentInstance connectionEncoding]]) { + if (tableEncoding != nil && ![tableEncoding isEqualToString:previousEncoding]) { [tableDocumentInstance setConnectionEncoding:tableEncoding reloadingViews:NO]; - [tableDataInstance resetAllData]; - tableEncoding = [tableDataInstance tableEncoding]; + changeEncoding = NO; } } } - // Ensure status information is cached on the working thread - [tableDataInstance updateStatusInformationForCurrentTable]; + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; // Notify listeners of the table change now that the state is fully set up. [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:SPTableChangedNotification object:tableDocumentInstance]; @@ -2154,6 +2173,13 @@ NSString *tableType = [tableTypeButton title]; NSString *tableName = [tableNameField stringValue]; + + // Ensure the use of UTF8 when creating new tables + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } // If there is an encoding selected other than the default we must specify it in CREATE TABLE statement if ([tableEncodingButton indexOfSelectedItem] > 0) { @@ -2226,7 +2252,8 @@ NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, @selector(sheetDidEnd:returnCode:contextInfo:), @"addRow", [NSString stringWithFormat:NSLocalizedString(@"An error occurred while trying to add the new table '%@'.\n\nMySQL said: %@", @"error adding new table informative message"), tableName, [mySQLConnection getLastErrorMessage]]); - + + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; [tablesListView reloadData]; } diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m index 17748bdc..e5073aed 100644 --- a/Source/SPXMLExporterDelegate.m +++ b/Source/SPXMLExporterDelegate.m @@ -71,7 +71,7 @@ // If we're exporting to multiple files then close the file handle of the exporter // that just finished, ensuring its data is written to disk. if (exportToMultipleFiles) { - [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection encoding]]]; + [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection stringEncoding]]]; [[exporter exportOutputFile] close]; } @@ -85,7 +85,7 @@ // Otherwise if the exporter list is empty, close the progress sheet else { if (exportSource == SPTableExport) { - [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection encoding]]]; + [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection stringEncoding]]]; } // Close the last exporter's file handle |