Skip to content

Commit 2a8528d

Browse files
committed
Improve the way Sequel Pro inferrs the collation of a column. (#2237)
This does not entirely fix the bug of SP sometimes displaying the wrong collation, but should work in >= 99% of cases.
1 parent e7cf9cd commit 2a8528d

5 files changed

+98
-34
lines changed

Source/SPDatabaseData.h

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
@interface SPDatabaseData : NSObject
4343
{
4444
NSString *characterSetEncoding;
45+
NSString *defaultCollationForCharacterSet;
4546
NSString *defaultCharacterSetEncoding;
4647
NSString *defaultCollation;
4748
NSString *serverDefaultCharacterSetEncoding;
@@ -72,6 +73,7 @@
7273

7374
- (NSArray *)getDatabaseCollations;
7475
- (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding;
76+
- (NSString *)getDefaultCollationForEncoding:(NSString *)encoding;
7577
- (NSArray *)getDatabaseStorageEngines;
7678
- (NSArray *)getDatabaseCharacterSetEncodings;
7779

Source/SPDatabaseData.m

+47-9
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ - (id)init
5858
{
5959
if ((self = [super init])) {
6060
characterSetEncoding = nil;
61+
defaultCollationForCharacterSet = nil;
6162
defaultCollation = nil;
6263
defaultCharacterSetEncoding = nil;
6364
serverDefaultCollation = nil;
@@ -83,6 +84,7 @@ - (id)init
8384
- (void)resetAllData
8485
{
8586
if (characterSetEncoding != nil) SPClear(characterSetEncoding);
87+
if (defaultCollationForCharacterSet != nil) SPClear(defaultCollationForCharacterSet);
8688
if (defaultCollation != nil) SPClear(defaultCollation);
8789
if (defaultCharacterSetEncoding != nil) SPClear(defaultCharacterSetEncoding);
8890
if (serverDefaultCharacterSetEncoding) SPClear(serverDefaultCharacterSetEncoding);
@@ -117,9 +119,14 @@ - (NSArray *)getDatabaseCollations
117119
// If that failed, get the list of collations from the hard-coded list
118120
if (![collations count]) {
119121
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
120-
122+
#warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding:
121123
do {
122-
[collations addObject:[NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding]];
124+
[collations addObject:@{
125+
@"ID" : @(c->nr),
126+
@"CHARACTER_SET_NAME" : [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding],
127+
@"COLLATION_NAME" : [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding],
128+
// description is not present in information_schema.collations
129+
}];
123130

124131
++c;
125132
}
@@ -139,12 +146,16 @@ - (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding
139146
if (encoding && ((characterSetEncoding == nil) || (![characterSetEncoding isEqualToString:encoding]) || ([characterSetCollations count] == 0))) {
140147

141148
[characterSetEncoding release];
149+
SPClear(defaultCollationForCharacterSet); //depends on encoding
142150
[characterSetCollations removeAllObjects];
143151

144152
characterSetEncoding = [[NSString alloc] initWithString:encoding];
145153

146-
if([cachedCollationsByEncoding objectForKey:characterSetEncoding] && [[cachedCollationsByEncoding objectForKey:characterSetEncoding] count])
147-
return [cachedCollationsByEncoding objectForKey:characterSetEncoding];
154+
NSArray *cachedCollations = [cachedCollationsByEncoding objectForKey:characterSetEncoding];
155+
if([cachedCollations count]) {
156+
[characterSetCollations addObjectsFromArray:cachedCollations];
157+
goto copy_return;
158+
}
148159

149160
// Try to retrieve the available collations for the supplied encoding from the database
150161
if ([serverSupport supportsInformationSchema]) {
@@ -162,7 +173,9 @@ - (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding
162173
// If that failed, get the list of collations matching the supplied encoding from the hard-coded list
163174
if (![characterSetCollations count]) {
164175
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
165-
176+
#warning I don't think this will work. The hardcoded list is supposed to be used with pre 4.1 mysql servers, \
177+
which don't have information_schema or SHOW COLLATION. But before 4.1 there were no real collations and \
178+
even the charsets had different names (e.g. charset "latin1_de" which now is "latin1" + "latin1_german2_ci")
166179
do {
167180
NSString *charSet = [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding];
168181
@@ -175,13 +188,38 @@ - (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding
175188
while (c[0].nr != 0);
176189
}
177190
178-
if (characterSetCollations && [characterSetCollations count]) {
191+
if ([characterSetCollations count]) {
179192
[cachedCollationsByEncoding setObject:[NSArray arrayWithArray:characterSetCollations] forKey:characterSetEncoding];
180193
}
181194
182195
}
183-
184-
return characterSetCollations;
196+
copy_return:
197+
return [NSArray arrayWithArray:characterSetCollations]; //copy because it is a mutable array and we keep changing it
198+
}
199+
200+
/** Get the collation that is marked as default for a given encoding by the server
201+
* @param encoding The encoding, e.g. @"latin1"
202+
* @return The default collation (e.g. @"latin1_swedish_ci") or
203+
* nil if either encoding was nil or the server does not provide the neccesary details
204+
*/
205+
- (NSString *)getDefaultCollationForEncoding:(NSString *)encoding
206+
{
207+
if(!encoding) return nil;
208+
// if (
209+
// - we have not yet fetched info about the default collation OR
210+
// - encoding is different than the one we currently know about
211+
// ) => we need to load it from server, otherwise just return cached value
212+
if ((defaultCollationForCharacterSet == nil) || (![characterSetEncoding isEqualToString:encoding])) {
213+
NSArray *cols = [self getDatabaseCollationsForEncoding:encoding]; //will clear stored encoding and collation if neccesary
214+
for (NSDictionary *collation in cols) {
215+
#warning This won't work for the hardcoded list (see above)
216+
if([[[collation objectForKey:@"IS_DEFAULT"] lowercaseString] isEqualToString:@"yes"]) {
217+
defaultCollationForCharacterSet = [[NSString alloc] initWithString:[collation objectForKey:@"COLLATION_NAME"]];
218+
break;
219+
}
220+
}
221+
}
222+
return defaultCollationForCharacterSet;
185223
}
186224

187225
/**
@@ -296,7 +334,7 @@ - (NSArray *)getDatabaseCharacterSetEncodings
296334
// If that failed, get the list of character set encodings from the hard-coded list
297335
if (![characterSetEncodings count]) {
298336
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
299-
337+
#warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding:
300338
do {
301339
[characterSetEncodings addObject:[NSDictionary dictionaryWithObjectsAndKeys:
302340
[NSString stringWithCString:c->name encoding:NSUTF8StringEncoding], @"CHARACTER_SET_NAME",

Source/SPTableInfo.m

+7-6
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ - (void)tableChanged:(NSNotification *)notification
170170
if (![[tableStatus objectForKey:@"Create_time"] isNSNull]) {
171171

172172
// Add the creation date to the infoTable
173-
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"created: %@", @"created: %@"), [self _getUserDefinedDateStringFromMySQLDate:[tableStatus objectForKey:@"Create_time"]]]];
173+
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"created: %@", @"Table Info Section : time+date table was created at"), [self _getUserDefinedDateStringFromMySQLDate:[tableStatus objectForKey:@"Create_time"]]]];
174174
}
175175

176176
// Check for 'Update_time' == NULL - InnoDB tables don't have an update time
@@ -187,15 +187,16 @@ - (void)tableChanged:(NSNotification *)notification
187187

188188
// Check for 'Rows' == NULL - information_schema database doesn't report row count for it's tables
189189
if (![[tableStatus objectForKey:@"Rows"] isNSNull]) {
190-
[info addObject:[NSString stringWithFormat:[[tableStatus objectForKey:@"RowsCountAccurate"] boolValue] ? NSLocalizedString(@"rows: %@", @"rows: %@") : NSLocalizedString(@"rows: ~%@", @"rows: ~%@"),
190+
[info addObject:[NSString stringWithFormat:[[tableStatus objectForKey:@"RowsCountAccurate"] boolValue] ? NSLocalizedString(@"rows: %@", @"Table Info Section : number of rows (exact value)") : NSLocalizedString(@"rows: ~%@", @"Table Info Section : number of rows (estimated value)"),
191191
[numberFormatter stringFromNumber:[NSNumber numberWithLongLong:[[tableStatus objectForKey:@"Rows"] longLongValue]]]]];
192192
}
193193

194-
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"size: %@", @"size: %@"), [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] longLongValue]]]];
195-
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %@", @"encoding: %@"), [tableDataInstance tableEncoding]]];
196-
194+
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"size: %@", @"Table Info Section : table size on disk"), [NSString stringForByteSize:[[tableStatus objectForKey:@"Data_length"] longLongValue]]]];
195+
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"encoding: %@", @"Table Info Section : table charset"), [tableDataInstance tableEncoding]]];
196+
//[info addObject:[NSString stringWithFormat:NSLocalizedString(@"collation: %@", @"Table Info Section : table collation"), [tableStatus objectForKey:@"Collation"]]];
197+
197198
if (![[tableStatus objectForKey:@"Auto_increment"] isNSNull]) {
198-
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"auto_increment: %@", @"auto_increment: %@"),
199+
[info addObject:[NSString stringWithFormat:NSLocalizedString(@"auto_increment: %@", @"Table Info Section : current value of auto_increment"),
199200
[numberFormatter stringFromNumber:[NSNumber numberWithLongLong:[[tableStatus objectForKey:@"Auto_increment"] longLongValue]]]]];
200201
}
201202

Source/SPTableStructureDelegate.m

+26-11
Original file line numberDiff line numberDiff line change
@@ -62,35 +62,53 @@ - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColum
6262
if ((NSUInteger)rowIndex >= [tableFields count]) return @"...";
6363

6464
if ([[tableColumn identifier] isEqualToString:@"collation"]) {
65+
NSString *tableEncoding = [tableDataInstance tableEncoding];
66+
NSString *columnEncoding = nil;
6567
NSInteger idx = 0;
6668

6769
if ((idx = [[NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"encoding"] integerValue]) > 0 && idx < [encodingPopupCell numberOfItems]) {
6870
NSString *enc = [[encodingPopupCell itemAtIndex:idx] title];
6971

7072
NSUInteger start = [enc rangeOfString:@"("].location + 1;
7173

72-
collations = [databaseDataInstance getDatabaseCollationsForEncoding:[enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)]];
74+
columnEncoding = [enc substringWithRange:NSMakeRange(start, [enc length] - start - 1)];
75+
collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding];
7376
}
7477
else {
7578
// If the structure has loaded (not still loading!) and the table encoding
7679
// is set, use the appropriate collations.
77-
collations = ([tableDocumentInstance structureLoaded] && [tableDataInstance tableEncoding] != nil) ? [databaseDataInstance getDatabaseCollationsForEncoding:[tableDataInstance tableEncoding]] : @[];
80+
collations = @[];
81+
if([tableDocumentInstance structureLoaded]) {
82+
columnEncoding = [tableDataInstance tableEncoding];
83+
if(columnEncoding) collations = [databaseDataInstance getDatabaseCollationsForEncoding:columnEncoding];
84+
}
7885
}
7986

80-
[[tableColumn dataCell] removeAllItems];
87+
NSPopUpButtonCell *collationCell = [tableColumn dataCell];
88+
89+
[collationCell removeAllItems];
8190

8291
if ([collations count] > 0) {
83-
NSString *defaultCollation = [[tableDataInstance statusValues] objectForKey:@"collation"];
92+
NSString *tableCollation = [[tableDataInstance statusValues] objectForKey:@"Collation"];
8493

85-
if (!defaultCollation) {
86-
defaultCollation = [databaseDataInstance getDatabaseDefaultCollation];
94+
if (![tableCollation length]) {
95+
tableCollation = [databaseDataInstance getDefaultCollationForEncoding:tableEncoding];
96+
}
97+
98+
NSString *columnCollation = [NSArrayObjectAtIndex(tableFields, rowIndex) objectForKey:@"collationName"];
99+
100+
if (![columnCollation length]) {
101+
columnCollation = [databaseDataInstance getDefaultCollationForEncoding:columnEncoding];
87102
}
88103

89104
[[tableColumn dataCell] addItemWithTitle:@""];
90105

91106
BOOL useMonospacedFont = [prefs boolForKey:SPUseMonospacedFonts];
92107
CGFloat monospacedFontSize = [prefs floatForKey:SPMonospacedFontSize] > 0 ? [prefs floatForKey:SPMonospacedFontSize] : [NSFont smallSystemFontSize];
108+
NSMutableDictionary *menuAttributes = [NSMutableDictionary dictionaryWithObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName];
109+
[menuAttributes setObject:useMonospacedFont ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName];
93110

111+
BOOL columnUsesTableDefaultEncoding = ([columnEncoding isEqualToString:tableEncoding]);
94112
// Populate collation popup button
95113
for (NSDictionary *collation in collations)
96114
{
@@ -99,12 +117,9 @@ - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColum
99117
[[tableColumn dataCell] addItemWithTitle:collationName];
100118

101119
// If this matches the table's collation, draw in gray
102-
if ([collationName length] && [collationName isEqualToString:defaultCollation]) {
120+
if (columnUsesTableDefaultEncoding && [collationName isEqualToString:tableCollation]) {
103121
NSMenuItem *collationMenuItem = [(NSPopUpButtonCell *)[tableColumn dataCell] itemAtIndex:([[tableColumn dataCell] numberOfItems] - 1)];
104-
NSMutableDictionary *menuAttributes = [NSMutableDictionary dictionaryWithObject:[NSColor lightGrayColor] forKey:NSForegroundColorAttributeName];
105-
106-
[menuAttributes setObject:useMonospacedFont ? [NSFont fontWithName:SPDefaultMonospacedFontName size:monospacedFontSize] : [NSFont systemFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName];
107-
122+
108123
NSAttributedString *itemString = [[[NSAttributedString alloc] initWithString:collationName attributes:menuAttributes] autorelease];
109124

110125
[collationMenuItem setAttributedTitle:itemString];

Source/SPTableStructureLoading.m

+16-8
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,20 @@ - (void)loadTable:(NSString *)aTable
173173
NSString *encoding = nil;
174174

175175
if ([fieldValidation isFieldTypeString:type]) {
176-
177-
collation = [theField objectForKey:@"collation"] ? [theField objectForKey:@"collation"] : [[tableDataInstance statusValues] objectForKey:@"collation"];
178-
encoding = [theField objectForKey:@"encoding"] ? [theField objectForKey:@"encoding"] : [tableDataInstance tableEncoding];
179176

180-
// If we still don't have a collation then fallback on the database default (not available on MySQL < 4.1.1).
181-
if (!collation) {
182-
collation = [databaseDataInstance getDatabaseDefaultCollation];
183-
}
177+
// The MySQL 4.1 manual says:
178+
//
179+
// MySQL chooses the column character set and collation in the following manner:
180+
// 1. If both CHARACTER SET X and COLLATE Y were specified, then character set X and collation Y are used.
181+
// 2. If CHARACTER SET X was specified without COLLATE, then character set X and its default collation are used.
182+
// 3. If COLLATE Y was specified without CHARACTER SET, then the character set associated with Y and collation Y.
183+
// 4. Otherwise, the table character set and collation are used.
184+
#warning This is not correct, see the comment above. \
185+
However MySQL ususally outputs the CREATE TABLE statement in a way for this to still get the result right.
186+
encoding = [theField objectForKey:@"encoding"] ? [theField objectForKey:@"encoding"] : [tableDataInstance tableEncoding];
187+
collation = [theField objectForKey:@"collation"] ? [theField objectForKey:@"collation"] : [databaseDataInstance getDefaultCollationForEncoding:encoding];
188+
189+
// MySQL < 4.1 does not support collations (they are part of the charset), it will be nil there
184190
}
185191

186192
if (encoding) {
@@ -190,7 +196,8 @@ - (void)loadTable:(NSString *)aTable
190196
fieldEncoding = encoding;
191197

192198
// Set the selected index as the match index +1 due to the leading @"" in the popup list
193-
[theField setObject:[NSNumber numberWithInteger:(selectedIndex + 1)] forKey:@"encoding"];
199+
[theField setObject:@(selectedIndex + 1) forKey:@"encoding"];
200+
[theField setObject:encoding forKey:@"encodingName"];
194201
break;
195202
}
196203

@@ -210,6 +217,7 @@ - (void)loadTable:(NSString *)aTable
210217

211218
// Set the selected index as the match index +1 due to the leading @"" in the popup list
212219
[theField setObject:[NSNumber numberWithInteger:(selectedIndex + 1)] forKey:@"collation"];
220+
[theField setObject:collation forKey:@"collationName"];
213221

214222
// Set BINARY if collation ends with _bin for convenience
215223
if ([[col objectForKey:@"COLLATION_NAME"] hasSuffix:@"_bin"]) {

0 commit comments

Comments
 (0)