-
Notifications
You must be signed in to change notification settings - Fork 842
/
SPDatabaseData.m
561 lines (471 loc) · 19.9 KB
/
SPDatabaseData.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
//
// SPDatabaseData.m
// sequel-pro
//
// Created by Stuart Connolly (stuconnolly.com) on May 20, 2009.
// Copyright (c) 2009 Stuart Connolly. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// More info at <https://github.com/sequelpro/sequelpro>
#import "SPDatabaseData.h"
#import "SPServerSupport.h"
#import "SPDatabaseCharacterSets.h"
#import <SPMySQL/SPMySQL.h>
@interface SPDatabaseData ()
- (NSString *)_getSingleVariableValue:(NSString *)variable;
- (NSArray *)_getDatabaseDataForQuery:(NSString *)query;
+ (NSArray *)_relabelCollationResult:(NSArray *)data;
NSInteger _sortMySQL4CharsetEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context);
NSInteger _sortMySQL4CollationEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context);
NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context);
@end
@implementation SPDatabaseData
@synthesize connection;
@synthesize serverSupport;
#pragma mark -
#pragma mark Initialisation
- (id)init
{
if ((self = [super init])) {
characterSetEncoding = nil;
defaultCollationForCharacterSet = nil;
defaultCollation = nil;
defaultCharacterSetEncoding = nil;
serverDefaultCollation = nil;
serverDefaultCharacterSetEncoding = nil;
collations = [[NSMutableArray alloc] init];
characterSetCollations = [[NSMutableArray alloc] init];
storageEngines = [[NSMutableArray alloc] init];
characterSetEncodings = [[NSMutableArray alloc] init];
cachedCollationsByEncoding = [[NSMutableDictionary alloc] init];
}
return self;
}
#pragma mark -
#pragma mark Public API
/**
* Reset all the cached values.
*/
- (void)resetAllData
{
if (characterSetEncoding != nil) SPClear(characterSetEncoding);
if (defaultCollationForCharacterSet != nil) SPClear(defaultCollationForCharacterSet);
if (defaultCollation != nil) SPClear(defaultCollation);
if (defaultCharacterSetEncoding != nil) SPClear(defaultCharacterSetEncoding);
if (serverDefaultCharacterSetEncoding) SPClear(serverDefaultCharacterSetEncoding);
if (serverDefaultCollation) SPClear(serverDefaultCollation);
[collations removeAllObjects];
[characterSetCollations removeAllObjects];
[storageEngines removeAllObjects];
[characterSetEncodings removeAllObjects];
}
/**
* Returns all of the database's currently available collations by querying information_schema.collations.
*/
- (NSArray *)getDatabaseCollations
{
if ([collations count] == 0) {
// Try to retrieve the available collations from the database
if ([serverSupport supportsInformationSchema]) {
[collations addObjectsFromArray:[self _getDatabaseDataForQuery:@"SELECT * FROM `information_schema`.`collations` ORDER BY `collation_name` ASC"]];
}
else if([serverSupport supportsShowCollation]) {
//use the 4.1-style query
NSArray *supportedCollations = [self _getDatabaseDataForQuery:@"SHOW COLLATION"];
//apply the sorting
supportedCollations = [supportedCollations sortedArrayUsingFunction:_sortMySQL4CollationEntry context:nil];
//convert the output to the information_schema style
[collations addObjectsFromArray:[SPDatabaseData _relabelCollationResult:supportedCollations]];
}
// If that failed, get the list of collations from the hard-coded list
if (![collations count]) {
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
#warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding:
do {
[collations addObject:@{
@"ID" : @(c->nr),
@"CHARACTER_SET_NAME" : [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding],
@"COLLATION_NAME" : [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding],
// description is not present in information_schema.collations
}];
++c;
}
while (c->nr != 0);
}
}
return collations;
}
/**
* Returns all of the database's currently available collations allowed for the supplied encoding by
* querying information_schema.collations.
*/
- (NSArray *)getDatabaseCollationsForEncoding:(NSString *)encoding
{
if (encoding && ((characterSetEncoding == nil) || (![characterSetEncoding isEqualToString:encoding]) || ([characterSetCollations count] == 0))) {
[characterSetEncoding release];
SPClear(defaultCollationForCharacterSet); //depends on encoding
[characterSetCollations removeAllObjects];
characterSetEncoding = [[NSString alloc] initWithString:encoding];
NSArray *cachedCollations = [cachedCollationsByEncoding objectForKey:characterSetEncoding];
if([cachedCollations count]) {
[characterSetCollations addObjectsFromArray:cachedCollations];
goto copy_return;
}
// Try to retrieve the available collations for the supplied encoding from the database
if ([serverSupport supportsInformationSchema]) {
[characterSetCollations addObjectsFromArray:[self _getDatabaseDataForQuery:[NSString stringWithFormat:@"SELECT * FROM `information_schema`.`collations` WHERE character_set_name = '%@' ORDER BY `collation_name` ASC", characterSetEncoding]]];
}
else if([serverSupport supportsShowCollation]) {
//use the 4.1-style query (as every collation name starts with the charset name we can use the prefix search)
NSArray *supportedCollations = [self _getDatabaseDataForQuery:[NSString stringWithFormat:@"SHOW COLLATION LIKE '%@%%'",characterSetEncoding]];
//apply the sorting
supportedCollations = [supportedCollations sortedArrayUsingFunction:_sortMySQL4CollationEntry context:nil];
[characterSetCollations addObjectsFromArray:[SPDatabaseData _relabelCollationResult:supportedCollations]];
}
// If that failed, get the list of collations matching the supplied encoding from the hard-coded list
if (![characterSetCollations count]) {
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
#warning I don't think this will work. The hardcoded list is supposed to be used with pre 4.1 mysql servers, \
which don't have information_schema or SHOW COLLATION. But before 4.1 there were no real collations and \
even the charsets had different names (e.g. charset "latin1_de" which now is "latin1" + "latin1_german2_ci")
do {
NSString *charSet = [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding];
if ([charSet isEqualToString:characterSetEncoding]) {
[characterSetCollations addObject:@{
@"COLLATION_NAME" : [NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding]
}];
}
++c;
}
while (c->nr != 0);
}
if ([characterSetCollations count]) {
[cachedCollationsByEncoding setObject:[NSArray arrayWithArray:characterSetCollations] forKey:characterSetEncoding];
}
}
copy_return:
return [NSArray arrayWithArray:characterSetCollations]; //copy because it is a mutable array and we keep changing it
}
/** Get the collation that is marked as default for a given encoding by the server
* @param encoding The encoding, e.g. @"latin1"
* @return The default collation (e.g. @"latin1_swedish_ci") or
* nil if either encoding was nil or the server does not provide the neccesary details
*/
- (NSString *)getDefaultCollationForEncoding:(NSString *)encoding
{
if(!encoding) return nil;
// if (
// - we have not yet fetched info about the default collation OR
// - encoding is different than the one we currently know about
// ) => we need to load it from server, otherwise just return cached value
if ((defaultCollationForCharacterSet == nil) || (![characterSetEncoding isEqualToString:encoding])) {
NSArray *cols = [self getDatabaseCollationsForEncoding:encoding]; //will clear stored encoding and collation if neccesary
for (NSDictionary *collation in cols) {
#warning This won't work for the hardcoded list (see above)
if([[[collation objectForKey:@"IS_DEFAULT"] lowercaseString] isEqualToString:@"yes"]) {
defaultCollationForCharacterSet = [[NSString alloc] initWithString:[collation objectForKey:@"COLLATION_NAME"]];
break;
}
}
}
return [[defaultCollationForCharacterSet copy] autorelease]; // -copy accepts nil, -stringWithString: does not
}
/** Get the name of the mysql charset a given collation belongs to.
* @param collation Name of the collation (e.g. "latin1_swedish_ci")
* @return name of the charset (e.g. "latin1") or nil if unknown
*
* According to the MySQL doc every collation can only ever belong to a single charset.
*/
- (NSString *)getEncodingFromCollation:(NSString *)collation {
if([collation length]) { //shortcut for nil and @""
for(NSDictionary *coll in [self getDatabaseCollations]) {
if([[coll objectForKey:@"COLLATION_NAME"] isEqualToString:collation]) {
return [coll objectForKey:@"CHARACTER_SET_NAME"];
}
}
}
return nil;
}
/**
* Returns all of the database's available storage engines.
*/
- (NSArray *)getDatabaseStorageEngines
{
if ([storageEngines count] == 0) {
if ([serverSupport isMySQL3] || [serverSupport isMySQL4]) {
[storageEngines addObject:@{@"Engine" : @"MyISAM"}];
// Check if InnoDB support is enabled
NSString *result = [self _getSingleVariableValue:@"have_innodb"];
if(result && [result isEqualToString:@"YES"])
[storageEngines addObject:@{@"Engine" : @"InnoDB"}];
// Before MySQL 4.1 the MEMORY engine was known as HEAP and the ISAM engine was included
if ([serverSupport supportsPre41StorageEngines]) {
[storageEngines addObject:@{@"Engine" : @"HEAP"}];
[storageEngines addObject:@{@"Engine" : @"ISAM"}];
}
else {
[storageEngines addObject:@{@"Engine" : @"MEMORY"}];
}
// BLACKHOLE storage engine was added in MySQL 4.1.11
if ([serverSupport supportsBlackholeStorageEngine]) {
[storageEngines addObject:@{@"Engine" : @"BLACKHOLE"}];
}
// ARCHIVE storage engine was added in MySQL 4.1.3
if ([serverSupport supportsArchiveStorageEngine]) {
[storageEngines addObject:@{@"Engine" : @"ARCHIVE"}];
}
// CSV storage engine was added in MySQL 4.1.4
if ([serverSupport supportsCSVStorageEngine]) {
[storageEngines addObject:@{@"Engine" : @"CSV"}];
}
}
// The table information_schema.engines didn't exist until MySQL 5.1.5
else {
if ([serverSupport supportsInformationSchemaEngines])
{
// Check the information_schema.engines table is accessible
SPMySQLResult *result = [connection queryString:@"SHOW TABLES IN information_schema LIKE 'ENGINES'"];
if ([result numberOfRows] == 1) {
// Table is accessible so get available storage engines
// Note, that the case of the column names specified in this query are important.
[storageEngines addObjectsFromArray:[self _getDatabaseDataForQuery:@"SELECT Engine, Support FROM `information_schema`.`engines` WHERE SUPPORT IN ('DEFAULT', 'YES')"]];
}
}
else {
// Get storage engines
NSArray *engines = [self _getDatabaseDataForQuery:@"SHOW STORAGE ENGINES"];
// We only want to include engines that are supported
for (NSDictionary *engine in engines)
{
if (([[engine objectForKey:@"Support"] isEqualToString:@"DEFAULT"]) ||
([[engine objectForKey:@"Support"] isEqualToString:@"YES"]))
{
[storageEngines addObject:engine];
}
}
}
}
}
return [storageEngines sortedArrayUsingFunction:_sortStorageEngineEntry context:nil];
}
/**
* Returns all of the database's currently available character set encodings
* @return [{Charset: 'utf8',Description: 'UTF-8 Unicode', Default collation: 'utf8_general_ci',Maxlen: 3},...]
* The Array is never empty and never nil but results might be unreliable.
*
* On MySQL 5+ this will query information_schema.character_sets
* On MySQL 4.1+ this will query SHOW CHARACTER SET
* Else a hardcoded list will be returned
*/
- (NSArray *)getDatabaseCharacterSetEncodings
{
if ([characterSetEncodings count] == 0) {
// Try to retrieve the available character set encodings from the database
// Check the information_schema.character_sets table is accessible
if ([serverSupport supportsInformationSchema]) {
[characterSetEncodings addObjectsFromArray:[self _getDatabaseDataForQuery:@"SELECT * FROM `information_schema`.`character_sets` ORDER BY `character_set_name` ASC"]];
}
else if ([serverSupport supportsShowCharacterSet]) {
NSArray *supportedEncodings = [self _getDatabaseDataForQuery:@"SHOW CHARACTER SET"];
supportedEncodings = [supportedEncodings sortedArrayUsingFunction:_sortMySQL4CharsetEntry context:nil];
for (NSDictionary *anEncoding in supportedEncodings)
{
NSDictionary *convertedEncoding = [NSDictionary dictionaryWithObjectsAndKeys:
[anEncoding objectForKey:@"Charset"], @"CHARACTER_SET_NAME",
[anEncoding objectForKey:@"Description"], @"DESCRIPTION",
[anEncoding objectForKey:@"Default collation"], @"DEFAULT_COLLATE_NAME",
[anEncoding objectForKey:@"Maxlen"], @"MAXLEN",
nil];
[characterSetEncodings addObject:convertedEncoding];
}
}
// If that failed, get the list of character set encodings from the hard-coded list
if (![characterSetEncodings count]) {
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
#warning This probably won't work as intended. See my comment in getDatabaseCollationsForEncoding:
do {
[characterSetEncodings addObject:@{
@"CHARACTER_SET_NAME" : [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding],
@"DESCRIPTION" : [NSString stringWithCString:c->description encoding:NSUTF8StringEncoding]
}];
++c;
}
while (c->nr != 0);
}
}
return characterSetEncodings;
}
/**
* Returns the databases's default character set encoding.
*
* @return The default encoding as a string
*/
- (NSString *)getDatabaseDefaultCharacterSet
{
if (!defaultCharacterSetEncoding) {
NSString *variable = [serverSupport supportsCharacterSetAndCollationVars] ? @"character_set_database" : @"character_set";
defaultCharacterSetEncoding = [[self _getSingleVariableValue:variable] retain];
}
return defaultCharacterSetEncoding;
}
/**
* Returns the database's default collation.
*
* @return The default collation as a string
*/
- (NSString *)getDatabaseDefaultCollation
{
if (!defaultCollation && [serverSupport supportsCharacterSetAndCollationVars]) {
defaultCollation = [[self _getSingleVariableValue:@"collation_database"] retain];
}
return defaultCollation;
}
/**
* Returns the server's default character set encoding.
*
* @return The default encoding as a string
*/
- (NSString *)getServerDefaultCharacterSet
{
if (!serverDefaultCharacterSetEncoding) {
NSString *variable = [serverSupport supportsCharacterSetAndCollationVars] ? @"character_set_server" : @"character_set";
serverDefaultCharacterSetEncoding = [[self _getSingleVariableValue:variable] retain];
}
return serverDefaultCharacterSetEncoding;
}
/**
* Returns the server's default collation.
*
* @return The default collation as a string (nil on MySQL 3 databases)
*/
- (NSString *)getServerDefaultCollation
{
if (!serverDefaultCollation) {
serverDefaultCollation = [[self _getSingleVariableValue:@"collation_server"] retain];
}
return serverDefaultCollation;
}
/**
* Returns the database's default storage engine.
*
* @return The default storage engine as a string
*/
- (NSString *)getDatabaseDefaultStorageEngine
{
if (!defaultStorageEngine) {
// Determine which variable to use based on server version. 'table_type' has been available since MySQL 3.23.0.
NSString *storageEngineKey = @"table_type";
// Post 5.5, storage_engine was deprecated; use default_storage_engine
if ([serverSupport isEqualToOrGreaterThanMajorVersion:5 minor:5 release:0]) {
storageEngineKey = @"default_storage_engine";
// For the rest of 5.x, use storage_engine
} else if ([serverSupport isEqualToOrGreaterThanMajorVersion:5 minor:0 release:0]) {
storageEngineKey = @"storage_engine";
}
// Retrieve the corresponding value for the determined key, ensuring return as a string
defaultStorageEngine = [[self _getSingleVariableValue:storageEngineKey] retain];
}
return defaultStorageEngine;
}
#pragma mark -
#pragma mark Private API
/**
* Look up the value of a single server variable
* @param variable The name of a server variable. Must not contain wildcards
* @return The value as string or nil if no such variable exists or the result is ambigious
*/
- (NSString *)_getSingleVariableValue:(NSString *)variable
{
SPMySQLResult *result = [connection queryString:[NSString stringWithFormat:@"SHOW VARIABLES LIKE %@", [variable tickQuotedString]]];;
[result setReturnDataAsStrings:YES];
if([connection queryErrored])
SPLog(@"server variable lookup failed for '%@': %@ (%lu)",variable,[connection lastErrorMessage],[connection lastErrorID]);
if ([result numberOfRows] != 1)
return nil;
return [[result getRowAsDictionary] objectForKey:@"Value"];
}
/**
* Executes the supplied query against the current connection and returns the result as an array of
* NSDictionarys, one for each row.
*/
- (NSArray *)_getDatabaseDataForQuery:(NSString *)query
{
SPMySQLResult *result = [connection queryString:query];
if ([connection queryErrored]) return @[];
[result setReturnDataAsStrings:YES];
return [result getAllRows];
}
/**
* Converts the output of a MySQL 4.1 style "SHOW COLLATION" to the format of a MySQL 5.0 style "SELECT * FROM information_schema.collations"
*/
+ (NSArray *)_relabelCollationResult:(NSArray *)data
{
NSMutableArray *outData = [[NSMutableArray alloc] initWithCapacity:[data count]];
for (NSDictionary *aCollation in data)
{
NSDictionary *convertedCollation = [NSDictionary dictionaryWithObjectsAndKeys:
[aCollation objectForKey:@"Collation"], @"COLLATION_NAME",
[aCollation objectForKey:@"Charset"], @"CHARACTER_SET_NAME",
[aCollation objectForKey:@"Id"], @"ID",
[aCollation objectForKey:@"Default"], @"IS_DEFAULT",
[aCollation objectForKey:@"Compiled"], @"IS_COMPILED",
[aCollation objectForKey:@"Sortlen"], @"SORTLEN",
nil];
[outData addObject:convertedCollation];
}
return [outData autorelease];
}
/**
* Sorts a 4.1-style SHOW CHARACTER SET result by the Charset key.
*/
NSInteger _sortMySQL4CharsetEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context)
{
return [[itemOne objectForKey:@"Charset"] compare:[itemTwo objectForKey:@"Charset"]];
}
/**
* Sorts a 4.1-style SHOW COLLATION result by the Collation key.
*/
NSInteger _sortMySQL4CollationEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context)
{
return [[itemOne objectForKey:@"Collation"] compare:[itemTwo objectForKey:@"Collation"]];
}
/**
* Sorts a storage engine array by the Engine key.
*/
NSInteger _sortStorageEngineEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context)
{
return [[itemOne objectForKey:@"Engine"] compare:[itemTwo objectForKey:@"Engine"]];
}
#pragma mark -
#pragma mark Other
- (void)dealloc
{
[self resetAllData];
SPClear(collations);
SPClear(characterSetCollations);
SPClear(storageEngines);
SPClear(characterSetEncodings);
SPClear(cachedCollationsByEncoding);
[super dealloc];
}
@end