//
// $Id$
//
// SPDatabaseData.m
// sequel-pro
//
// Created by Stuart Connolly (stuconnolly.com) on May 20, 2009
// Copyright (c) 2009 Stuart Connolly. All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// More info at
#import "SPDatabaseData.h"
#import "SPServerSupport.h"
#import "SPDatabaseCharacterSets.h"
#ifndef SP_REFACTOR
#import "SPMySQL.h"
#else
#import
#endif
@interface SPDatabaseData (PrivateAPI)
- (NSArray *)_getDatabaseDataForQuery:(NSString *)query;
NSInteger _sortMySQL4CharsetEntry(NSDictionary *itemOne, NSDictionary *itemTwo, void *context);
@end
@implementation SPDatabaseData
@synthesize connection;
@synthesize serverSupport;
#pragma mark -
#pragma mark Initialization
/**
* Initialize cache arrays.
*/
- (id)init
{
if ((self = [super init])) {
characterSetEncoding = 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) [characterSetEncoding release], characterSetEncoding = nil;
[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"]];
}
// If that failed, get the list of collations from the hard-coded list
if (![collations count]) {
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
do {
[collations addObject:[NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding]];
++c;
}
while (c[0].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];
[characterSetCollations removeAllObjects];
characterSetEncoding = [[NSString alloc] initWithString:encoding];
if([cachedCollationsByEncoding objectForKey:characterSetEncoding] && [[cachedCollationsByEncoding objectForKey:characterSetEncoding] count])
return [cachedCollationsByEncoding objectForKey:characterSetEncoding];
// 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]]];
}
// If that failed, get the list of collations matching the supplied encoding from the hard-coded list
if (![characterSetCollations count]) {
const SPDatabaseCharSets *c = SPGetDatabaseCharacterSets();
do {
NSString *charSet = [NSString stringWithCString:c->name encoding:NSUTF8StringEncoding];
if ([charSet isEqualToString:characterSetEncoding]) {
[characterSetCollations addObject:[NSDictionary dictionaryWithObject:[NSString stringWithCString:c->collation encoding:NSUTF8StringEncoding] forKey:@"COLLATION_NAME"]];
}
++c;
}
while (c[0].nr != 0);
}
if (characterSetCollations && [characterSetCollations count]) {
[cachedCollationsByEncoding setObject:[NSArray arrayWithArray:characterSetCollations] forKey:characterSetEncoding];
}
}
return characterSetCollations;
}
/**
* Returns all of the database's available storage engines.
*/
- (NSArray *)getDatabaseStorageEngines
{
if ([storageEngines count] == 0) {
if ([serverSupport isMySQL3] || [serverSupport isMySQL4]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"MyISAM" forKey:@"Engine"]];
// Check if InnoDB support is enabled
SPMySQLResult *result = [connection queryString:@"SHOW VARIABLES LIKE 'have_innodb'"];
[result setReturnDataAsStrings:YES];
if ([result numberOfRows] == 1) {
if ([[[result getRowAsDictionary] objectForKey:@"Value"] isEqualToString:@"YES"]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"InnoDB" forKey:@"Engine"]];
}
}
// Before MySQL 4.1 the MEMORY engine was known as HEAP and the ISAM engine was included
if ([serverSupport supportsPre41StorageEngines]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"HEAP" forKey:@"Engine"]];
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"ISAM" forKey:@"Engine"]];
}
else {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"MEMORY" forKey:@"Engine"]];
}
// BLACKHOLE storage engine was added in MySQL 4.1.11
if ([serverSupport supportsBlackholeStorageEngine]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"BLACKHOLE" forKey:@"Engine"]];
}
// ARCHIVE storage engine was added in MySQL 4.1.3
if ([serverSupport supportsArchiveStorageEngine]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"ARCHIVE" forKey:@"Engine"]];
}
// CSV storage engine was added in MySQL 4.1.4
if ([serverSupport supportsCSVStorageEngine]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"CSV" forKey:@"Engine"]];
}
}
// 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;
}
/**
* Returns all of the database's currently available character set encodings by querying
* information_schema.character_sets.
*/
- (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();
do {
[characterSetEncodings addObject:[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithCString:c->name encoding:NSUTF8StringEncoding], @"CHARACTER_SET_NAME",
[NSString stringWithCString:c->description encoding:NSUTF8StringEncoding], @"DESCRIPTION",
nil]];
++c;
}
while (c[0].nr != 0);
}
}
return characterSetEncodings;
}
#pragma mark -
#pragma mark Other
/**
* Deallocate ivars.
*/
- (void)dealloc
{
if (characterSetEncoding != nil) {
[characterSetEncoding release], characterSetEncoding = nil;
}
[collations release], collations = nil;
[characterSetCollations release], characterSetCollations = nil;
[storageEngines release], storageEngines = nil;
[characterSetEncodings release], characterSetEncodings = nil;
[cachedCollationsByEncoding release], cachedCollationsByEncoding = nil;
[super dealloc];
}
#pragma mark -
#pragma mark Private API
/**
* 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 [NSArray array];
[result setReturnDataAsStrings:YES];
return [result getAllRows];
}
/**
* 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"]];
}
@end