diff options
author | Max <post@wickenrode.com> | 2016-03-03 21:37:47 +0100 |
---|---|---|
committer | Max <post@wickenrode.com> | 2016-03-03 21:37:47 +0100 |
commit | 43cafb58805e846a0e8da20518de7575cfb23166 (patch) | |
tree | a40f24843840766938687a26278e6911559ff402 | |
parent | ba04386f728a8606c6a0c3f4b121cbe74c1d051f (diff) | |
download | sequelpro-43cafb58805e846a0e8da20518de7575cfb23166.tar.gz sequelpro-43cafb58805e846a0e8da20518de7575cfb23166.tar.bz2 sequelpro-43cafb58805e846a0e8da20518de7575cfb23166.zip |
Fix #2150 & #2173 on 1.1.x branch
Backport of 2b52f76ed2103bc6d458767906753814ee8ba9e1 & 2b52f76ed2103bc6d458767906753814ee8ba9e1
4 files changed, 50 insertions, 5 deletions
diff --git a/Frameworks/SPMySQLFramework/SPMySQLEmptyResult.m b/Frameworks/SPMySQLFramework/SPMySQLEmptyResult.m index 6a49daf4..db63e12e 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLEmptyResult.m +++ b/Frameworks/SPMySQLFramework/SPMySQLEmptyResult.m @@ -126,6 +126,11 @@ return nil; } +- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy +{ + return nil; +} + - (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength { return nil; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index 199d4790..2419d7a9 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -93,6 +93,7 @@ @interface SPMySQLResult (Private_API) - (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length; +- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy; - (void)_setQueryExecutionTime:(double)theExecutionTime; @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m index b70d2032..e74f061f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m @@ -44,6 +44,7 @@ @interface SPMySQLResult (Private_API) - (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length; +- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy; @end @@ -231,24 +232,33 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] = // Record the original column position within the result set [eachField setObject:[NSString stringWithFormat:@"%llu", (unsigned long long)i] forKey:@"datacolumnindex"]; + // mysqlField.name might point to an empty string or NULL (theoretically). + // _stringWithBytes:... will return @"" if either bytes is NULL or length is 0. + // For now let's interpret (bytes != NULL) as a valid string (possibly empty) + // and otherwise as 'value not set'. + // Record the column name, or alias if one is being used - [eachField setObject:[self _stringWithBytes:mysqlField.name length:mysqlField.name_length] forKey:@"name"]; + if (mysqlField.name) { + [eachField setObject:[self _lossyStringWithBytes:mysqlField.name length:mysqlField.name_length wasLossy:NULL] forKey:@"name"]; + } // Record the original column name if using an alias - [eachField setObject:[self _stringWithBytes:mysqlField.org_name length:mysqlField.org_name_length] forKey:@"org_name"]; + if (mysqlField.org_name) { + [eachField setObject:[self _stringWithBytes:mysqlField.org_name length:mysqlField.org_name_length] forKey:@"org_name"]; + } // If the column had an underlying table, record the table name, respecting aliases - if (mysqlField.table_length) { + if (mysqlField.table) { [eachField setObject:[self _stringWithBytes:mysqlField.table length:mysqlField.table_length] forKey:@"table"]; } // If the column had an underlying table, record the original table name, ignoring aliases - if (mysqlField.org_table_length) { + if (mysqlField.org_table) { [eachField setObject:[self _stringWithBytes:mysqlField.org_table length:mysqlField.org_table_length] forKey:@"org_table"]; } // If the column had an underlying database, record the database name - if (mysqlField.db_length) { + if (mysqlField.db) { [eachField setObject:[self _stringWithBytes:mysqlField.db length:mysqlField.db_length] forKey:@"db"]; } diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m index 56b50d9a..6d2bdfd6 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m @@ -318,6 +318,35 @@ static id NSNullPointer; return [[[NSString alloc] initWithBytes:bytes length:length encoding:stringEncoding] autorelease]; } +- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy +{ + //mysql protocol limits column names to 256 bytes. + //with inline columns and multibyte charsets this can result in a character + //being split in half at which the method above will fail. + //Let's first try removing stuff from the end to create something valid. + NSUInteger removed = 0; + do { + NSString *res = [self _stringWithBytes:bytes length:(length-removed)]; + if(res) { + if(outLossy) *outLossy = (removed != 0); + return (removed? [res stringByAppendingString:@"…"] : res); + } + removed++; + } while(removed <= 10 && removed < length); // 10 is arbitrary + + //if that fails, ascii should accept all values from 0-255 as input + NSString *ascii = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; + if(ascii){ + if(outLossy) *outLossy = YES; + return [ascii autorelease]; + } + + //if even that failed we lose. + NSDictionary *info = @{ @"data": [NSData dataWithBytes:bytes length:length] }; + NSString *reason = [NSString stringWithFormat:@"Failed to convert byte sequence %@ to string (encoding = %lu)",info[@"data"],stringEncoding]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:reason userInfo:info]; +} + /** * Allow setting the execution time for the original query (including connection lag) * so it can be requested later without relying on connection state. |