aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2010-10-07 18:56:33 +0000
committerstuconnolly <stuart02@gmail.com>2010-10-07 18:56:33 +0000
commit44a5f9e552b3d5e1f9ef1c6d11f34e893d67e85b (patch)
treec833ba970d8cae5f756a31bc274e365c0a44b3bf /Source
parent95d2e4acc393e91aa70ed4c71daa1f776454a936 (diff)
downloadsequelpro-44a5f9e552b3d5e1f9ef1c6d11f34e893d67e85b.tar.gz
sequelpro-44a5f9e552b3d5e1f9ef1c6d11f34e893d67e85b.tar.bz2
sequelpro-44a5f9e552b3d5e1f9ef1c6d11f34e893d67e85b.zip
Various improvements to server capability/version checking, including:
- Add a new ServerSupport class, for which an instance is created upon each new connection and is then subsequently accessible via SPDatabaseDocument. - Replace the majority of manual version checking with calls to properties in the above new class. - Improve the user manager's compatibility with MySQL 3 and 4 servers. Fixes issue #811 Other changes include: - Disable the encoding popup button when adding a new table or database to servers running pre MySQL 4.1 as it only contains one option, 'Default'. - Fix various potential memory leaks discovered during static analysis. - General tidy up and comments.
Diffstat (limited to 'Source')
-rw-r--r--Source/SPDatabaseData.h23
-rw-r--r--Source/SPDatabaseData.m95
-rw-r--r--Source/SPDatabaseDocument.h12
-rw-r--r--Source/SPDatabaseDocument.m95
-rw-r--r--Source/SPDatabaseRename.m2
-rw-r--r--Source/SPFieldMapperController.m5
-rw-r--r--Source/SPIndexesController.m3
-rw-r--r--Source/SPSQLExporter.m8
-rw-r--r--Source/SPServerSupport.h209
-rw-r--r--Source/SPServerSupport.m311
-rw-r--r--Source/SPTableCopy.m8
-rw-r--r--Source/SPTableData.m12
-rw-r--r--Source/SPTableTriggers.m11
-rw-r--r--Source/SPTablesList.m42
-rw-r--r--Source/SPUserManager.h8
-rw-r--r--Source/SPUserManager.m350
16 files changed, 929 insertions, 265 deletions
diff --git a/Source/SPDatabaseData.h b/Source/SPDatabaseData.h
index f0b79b45..de98ff9d 100644
--- a/Source/SPDatabaseData.h
+++ b/Source/SPDatabaseData.h
@@ -34,14 +34,20 @@ typedef struct
const char *description;
} SPDatabaseCharSets;
+@class SPServerSupport;
+
+/**
+ * @class SPDatabaseData SPDatabaseData.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * This class provides various convenience methods for obtaining data associated with the current database,
+ * if available. This includes available encodings, collations, etc.
+ */
@interface SPDatabaseData : NSObject
{
NSString *characterSetEncoding;
- NSInteger serverMajorVersion;
- NSInteger serverMinorVersion;
- NSInteger serverReleaseVersion;
-
NSMutableArray *collations;
NSMutableArray *characterSetCollations;
NSMutableArray *storageEngines;
@@ -49,10 +55,19 @@ typedef struct
NSMutableDictionary *cachedCollationsByEncoding;
MCPConnection *connection;
+ SPServerSupport *serverSupport;
}
+/**
+ * @property connection The current database connection
+ */
@property (readwrite, assign) MCPConnection *connection;
+/**
+ * @property serverSupport The connection's associated SPServerSupport instance
+ */
+@property (readwrite, assign) SPServerSupport *serverSupport;
+
- (void)resetAllData;
- (NSArray *)getDatabaseCollations;
diff --git a/Source/SPDatabaseData.m b/Source/SPDatabaseData.m
index bec1264c..b67ee4da 100644
--- a/Source/SPDatabaseData.m
+++ b/Source/SPDatabaseData.m
@@ -25,6 +25,7 @@
#import "SPDatabaseData.h"
#import "SPStringAdditions.h"
+#import "SPServerSupport.h"
@interface SPDatabaseData (PrivateAPI)
@@ -194,6 +195,10 @@ const SPDatabaseCharSets charsets[] =
@implementation SPDatabaseData
@synthesize connection;
+@synthesize serverSupport;
+
+#pragma mark -
+#pragma mark Initialization
/**
* Initialize cache arrays.
@@ -207,32 +212,22 @@ const SPDatabaseCharSets charsets[] =
characterSetCollations = [[NSMutableArray alloc] init];
storageEngines = [[NSMutableArray alloc] init];
characterSetEncodings = [[NSMutableArray alloc] init];
+
cachedCollationsByEncoding = [[NSMutableDictionary alloc] init];
}
return self;
}
-/**
- * Set the current connection the supplied connection instance.
- */
-- (void)setConnection:(MCPConnection *)dbConnection
-{
- connection = dbConnection;
-
- serverMajorVersion = [connection serverMajorVersion];
- serverMinorVersion = [connection serverMinorVersion];
- serverReleaseVersion = [connection serverReleaseVersion];
-}
+#pragma mark -
+#pragma mark Public API
/**
* Reset all the cached values.
*/
- (void)resetAllData
{
- if (characterSetEncoding != nil) {
- [characterSetEncoding release], characterSetEncoding = nil;
- }
+ if (characterSetEncoding != nil) [characterSetEncoding release], characterSetEncoding = nil;
[collations removeAllObjects];
[characterSetCollations removeAllObjects];
@@ -248,9 +243,10 @@ const SPDatabaseCharSets charsets[] =
if ([collations count] == 0) {
// Try to retrieve the available collations from the database
- if (serverMajorVersion >= 5)
- [collations addObjectsFromArray:[self _getDatabaseDataForQuery:@"SELECT * FROM `information_schema.collations` ORDER BY `collation_name` ASC"]];
-
+ 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 = charsets;
@@ -284,8 +280,9 @@ const SPDatabaseCharSets charsets[] =
return [cachedCollationsByEncoding objectForKey:characterSetEncoding];
// Try to retrieve the available collations for the supplied encoding from the database
- if (serverMajorVersion >= 5)
+ 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]) {
@@ -303,8 +300,9 @@ const SPDatabaseCharSets charsets[] =
while (c[0].nr != 0);
}
- if(characterSetCollations && [characterSetCollations count])
+ if (characterSetCollations && [characterSetCollations count]) {
[cachedCollationsByEncoding setObject:[NSArray arrayWithArray:characterSetCollations] forKey:characterSetEncoding];
+ }
}
@@ -317,11 +315,12 @@ const SPDatabaseCharSets charsets[] =
- (NSArray *)getDatabaseStorageEngines
{
if ([storageEngines count] == 0) {
- if (serverMajorVersion < 5) {
+ if ([serverSupport isMySQL3] || [serverSupport isMySQL4]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"MyISAM" forKey:@"Engine"]];
// Check if InnoDB support is enabled
MCPResult *result = [connection queryString:@"SHOW VARIABLES LIKE 'have_innodb'"];
+
[result setReturnDataAsStrings:YES];
if ([result numOfRows] == 1) {
@@ -331,7 +330,7 @@ const SPDatabaseCharSets charsets[] =
}
// Before MySQL 4.1 the MEMORY engine was known as HEAP and the ISAM engine was included
- if ((serverMajorVersion <= 4) && (serverMinorVersion < 100)) {
+ if ([serverSupport supportsPre41StorageEngines]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"HEAP" forKey:@"Engine"]];
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"ISAM" forKey:@"Engine"]];
}
@@ -340,35 +339,32 @@ const SPDatabaseCharSets charsets[] =
}
// BLACKHOLE storage engine was added in MySQL 4.1.11
- if ((serverMajorVersion >= 4) &&
- (serverMinorVersion >= 1) &&
- (serverReleaseVersion >= 11))
- {
+ if ([serverSupport supportsBlackholeStorageEngine]) {
[storageEngines addObject:[NSDictionary dictionaryWithObject:@"BLACKHOLE" forKey:@"Engine"]];
+ }
- // ARCHIVE storage engine was added in MySQL 4.1.3
- if (serverReleaseVersion >= 3) {
- [storageEngines addObject:[NSDictionary dictionaryWithObject:@"ARCHIVE" forKey:@"Engine"]];
- }
-
- // CSV storage engine was added in MySQL 4.1.4
- if (serverReleaseVersion >= 4) {
- [storageEngines addObject:[NSDictionary dictionaryWithObject:@"CSV" 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 ((serverMajorVersion >= 5) &&
- (serverMinorVersion >= 1) &&
- (serverReleaseVersion >= 5))
+ if ([serverSupport supportsInformationSchemaEngines])
{
// Check the information_schema.engines table is accessible
MCPResult *result = [connection queryString:@"SHOW TABLES IN information_schema LIKE 'ENGINES'"];
if ([result numOfRows] == 1) {
+
// Table is accessible so get available storage engines
- [storageEngines addObjectsFromArray:[self _getDatabaseDataForQuery:@"SELECT Engine, Support FROM information_schema.engines WHERE support IN ('DEFAULT', 'YES');"]];
+ // 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 {
@@ -396,23 +392,28 @@ const SPDatabaseCharSets charsets[] =
* 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 (serverMajorVersion >= 5) {
+ if ([serverSupport supportsInformationSchema]) {
[characterSetEncodings addObjectsFromArray:[self _getDatabaseDataForQuery:@"SELECT * FROM `information_schema`.`character_sets` ORDER BY `character_set_name` ASC"]];
- } else if (serverMajorVersion == 4 && serverMinorVersion >= 1) {
+ }
+ else if ([serverSupport supportsShowCharacterSet]) {
NSArray *supportedEncodings = [self _getDatabaseDataForQuery:@"SHOW CHARACTER SET"];
+
supportedEncodings = [supportedEncodings sortedArrayUsingFunction:_sortMySQL4CharsetEntry context:nil];
- for (NSDictionary *anEncoding in supportedEncodings) {
+
+ 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];
}
}
@@ -432,10 +433,13 @@ const SPDatabaseCharSets charsets[] =
while (c[0].nr != 0);
}
}
-
+
return characterSetEncodings;
}
+#pragma mark -
+#pragma mark Other
+
/**
* Deallocate ivars.
*/
@@ -454,6 +458,9 @@ const SPDatabaseCharSets charsets[] =
[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.
diff --git a/Source/SPDatabaseDocument.h b/Source/SPDatabaseDocument.h
index 07c3f7bb..c2cf0e85 100644
--- a/Source/SPDatabaseDocument.h
+++ b/Source/SPDatabaseDocument.h
@@ -29,7 +29,12 @@
#import <MCPKit/MCPKit.h>
#import <WebKit/WebKit.h>
-@class SPConnectionController, SPProcessListController, SPServerVariablesController, SPUserManager, SPWindowController;
+@class SPConnectionController,
+ SPProcessListController,
+ SPServerVariablesController,
+ SPUserManager,
+ SPWindowController,
+ SPServerSupport;
/**
* The SPDatabaseDocument class controls the primary database view window.
@@ -55,6 +60,7 @@
IBOutlet id statusTableCopyChecksum;
SPUserManager *userManagerInstance;
+ SPServerSupport *serverSupport;
IBOutlet NSSearchField *listFilterField;
@@ -190,15 +196,17 @@
@property (readwrite, assign) SPWindowController *parentWindowController;
@property (readwrite, assign) NSTabViewItem *parentTabViewItem;
@property (readwrite, assign) BOOL isProcessing;
+@property (readonly) SPServerSupport *serverSupport;
- (BOOL)isUntitled;
- (BOOL)couldCommitCurrentViewActions;
- (void)initQueryEditorWithString:(NSString *)query;
- (void)initWithConnectionFile:(NSString *)path;
+
// Connection callback and methods
- (void)setConnection:(MCPConnection *)theConnection;
-- (MCPConnection *) getConnection;
+- (MCPConnection *)getConnection;
- (void)setKeychainID:(NSString *)theID;
// Database methods
diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m
index 03db2925..bd5ee773 100644
--- a/Source/SPDatabaseDocument.m
+++ b/Source/SPDatabaseDocument.m
@@ -60,6 +60,7 @@
#import "SPDatabaseCopy.h"
#import "SPTableCopy.h"
#import "SPDatabaseRename.h"
+#import "SPServerSupport.h"
@interface SPDatabaseDocument (PrivateAPI)
@@ -76,10 +77,10 @@
@synthesize parentWindowController;
@synthesize parentTabViewItem;
@synthesize isProcessing;
+@synthesize serverSupport;
- (id)init
{
-
if ((self = [super init])) {
_mainNibLoaded = NO;
@@ -675,11 +676,17 @@
#pragma mark -
#pragma mark Connection callback and methods
-- (void) setConnection:(MCPConnection *)theConnection
+- (void)setConnection:(MCPConnection *)theConnection
{
_isConnected = YES;
mySQLConnection = [theConnection retain];
-
+
+ // Now that we have a connection, determine what functionality the database supports.
+ // Note that this must be done before anything else as it's used by nearly all of the main controllers.
+ serverSupport = [[SPServerSupport alloc] initWithMajorVersion:[mySQLConnection serverMajorVersion]
+ minor:[mySQLConnection serverMinorVersion]
+ release:[mySQLConnection serverReleaseVersion]];
+
// Set the fileURL and init the preferences (query favs, filters, and history) if available for that URL
[self setFileURL:[[SPQueryController sharedQueryController] registerDocumentWithFileURL:[self fileURL] andContextInfo:spfPreferences]];
@@ -701,15 +708,22 @@
// Update the database list
[self setDatabases:self];
+
[chooseDatabaseButton setEnabled:!_isWorkingLevel];
+ [databaseDataInstance setConnection:mySQLConnection];
+
+ // Pass the support class to the data instance
+ [databaseDataInstance setServerSupport:serverSupport];
+
// 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 ) {
+
+ if ([encodingType intValue] != SPEncodingAutodetect) {
[self setConnectionEncoding:[self mysqlEncodingFromEncodingTag:encodingType] reloadingViews:NO];
}
@@ -723,8 +737,7 @@
[exportControllerInstance setConnection:mySQLConnection];
[tableDataInstance setConnection:mySQLConnection];
[extendedTableInfoInstance setConnection:mySQLConnection];
- [databaseDataInstance setConnection:mySQLConnection];
-
+
// Set the custom query editor's MySQL version
[customQueryInstance setMySQLversion:mySQLVersion];
@@ -738,9 +751,10 @@
// Init Custom Query editor with the stored queries in a spf file if given.
[spfDocData setObject:[NSNumber numberWithBool:NO] forKey:@"save_editor_content"];
- if(spfSession != nil && [spfSession objectForKey:@"queries"]) {
+
+ if (spfSession != nil && [spfSession objectForKey:@"queries"]) {
[spfDocData setObject:[NSNumber numberWithBool:YES] forKey:@"save_editor_content"];
- if([[spfSession objectForKey:@"queries"] isKindOfClass:[NSData class]]) {
+ if ([[spfSession objectForKey:@"queries"] isKindOfClass:[NSData class]]) {
NSString *q = [[NSString alloc] initWithData:[[spfSession objectForKey:@"queries"] decompress] encoding:NSUTF8StringEncoding];
[self initQueryEditorWithString:q];
[q release];
@@ -750,7 +764,7 @@
}
// Insert queryEditorInitString into the Query Editor if defined
- if(queryEditorInitString && [queryEditorInitString length]) {
+ if (queryEditorInitString && [queryEditorInitString length]) {
[self viewQuery:self];
[customQueryInstance doPerformLoadQueryService:queryEditorInitString];
[queryEditorInitString release];
@@ -759,12 +773,12 @@
// Set focus to table list filter field if visible
// otherwise set focus to Table List view
- if ( [[tablesListInstance tables] count] > 20 )
+ if ([[tablesListInstance tables] count] > 20)
[parentWindow makeFirstResponder:listFilterField];
else
[parentWindow makeFirstResponder:[tablesListInstance valueForKeyPath:@"tablesListView"]];
- if(spfSession != nil) {
+ if (spfSession != nil) {
// Restore vertical split view divider for tables' list and right view (Structure, Content, etc.)
if([spfSession objectForKey:@"windowVerticalDividerPosition"])
@@ -772,12 +786,13 @@
// Start a task to restore the session details
[self startTaskWithDescription:NSLocalizedString(@"Restoring session...", @"Restoring session task description")];
+
if ([NSThread isMainThread])
[NSThread detachNewThreadSelector:@selector(restoreSession) toTarget:self withObject:nil];
else
[self restoreSession];
-
- } else {
+ }
+ else {
switch ([prefs integerForKey:SPDefaultViewMode] > 0 ? [prefs integerForKey:SPDefaultViewMode] : [prefs integerForKey:SPLastViewMode]) {
default:
case SPStructureViewMode:
@@ -801,10 +816,16 @@
}
}
- (void*)[self databaseEncoding];
+ (void *)[self databaseEncoding];
}
-- (MCPConnection *) getConnection {
+/**
+ * Returns the current connection associated with this document.
+ *
+ * @return The document's connection
+ */
+- (MCPConnection *) getConnection
+{
return mySQLConnection;
}
@@ -876,8 +897,6 @@
}
(![self database]) ? [chooseDatabaseButton selectItemAtIndex:0] : [chooseDatabaseButton selectItemWithTitle:[self database]];
-
-
}
/**
@@ -903,7 +922,6 @@
// Select the database
[self selectDatabase:[chooseDatabaseButton titleOfSelectedItem] item:[self table]];
-
}
/**
@@ -911,7 +929,6 @@
*/
- (void)selectDatabase:(NSString *)aDatabase item:(NSString *)anItem
{
-
// Do not update the navigator since nothing is changed
[[SPNavigatorController sharedNavigatorController] setIgnoreUpdate:NO];
@@ -934,10 +951,10 @@
nil];
if ([NSThread isMainThread]) {
[NSThread detachNewThreadSelector:@selector(_selectDatabaseAndItem:) toTarget:self withObject:selectionDetails];
- } else {
+ }
+ else {
[self _selectDatabaseAndItem:selectionDetails];
}
-
}
/**
@@ -956,12 +973,14 @@
// Retrieve the server-supported encodings and add them to the menu
NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings];
NSString *utf8MenuItemTitle = nil;
- if ([encodings count] > 0
- && ([mySQLConnection serverMajorVersion] > 4
- || ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1)))
- {
+
+ [databaseEncodingButton setEnabled:YES];
+
+ if (([encodings count] > 0) && [serverSupport supportsPost41CharacterSetHandling]) {
[[databaseEncodingButton menu] addItem:[NSMenuItem separatorItem]];
- for (NSDictionary *encoding in encodings) {
+
+ for (NSDictionary *encoding in encodings)
+ {
NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]];
[databaseEncodingButton addItemWithTitle:menuItemTitle];
@@ -977,7 +996,9 @@
[databaseEncodingButton insertItemWithTitle:utf8MenuItemTitle atIndex:2];
}
}
-
+ else {
+ [databaseEncodingButton setEnabled:NO];
+ }
[NSApp beginSheet:databaseSheet
modalForWindow:parentWindow
@@ -1725,15 +1746,13 @@
_supportsEncoding = YES;
// MySQL >= 4.1
- if ([mySQLConnection serverMajorVersion] > 4
- || ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1))
- {
+ if ([serverSupport supportsCharacterSetDatabaseVar]) {
charSetResult = [mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set_database'"];
[charSetResult setReturnDataAsStrings:YES];
mysqlEncoding = [[charSetResult fetchRowAsDictionary] objectForKey:@"Value"];
-
- // mysql 4.0 or older -> only default character set possible, cannot choose others using "set names xy"
- } else {
+ }
+ // MySQL 4.0 or older -> only default character set possible, cannot choose others using "set names xy"
+ else {
mysqlEncoding = [[[mySQLConnection queryString:@"SHOW VARIABLES LIKE 'character_set'"] fetchRowAsDictionary] objectForKey:@"Value"];
}
@@ -2450,7 +2469,9 @@
if (!userManagerInstance)
{
userManagerInstance = [[SPUserManager alloc] init];
- userManagerInstance.mySqlConnection = mySQLConnection;
+
+ [userManagerInstance setMySqlConnection:mySQLConnection];
+ [userManagerInstance setServerSupport:serverSupport];
}
// Before displaying the user manager make sure the current user has access to the mysql.user table.
@@ -3801,9 +3822,7 @@
// Obviously don't add if it already exists. We shouldn't really need this as the menu item validation
// enables or disables the menu item based on the same method. Although to be safe do the check anyway
// as we don't know what's calling this method.
- if ([connectionController selectedFavorite]) {
- return;
- }
+ if ([connectionController selectedFavorite]) return;
// Request the connection controller to add its details to favorites
[connectionController addFavorite:self];
@@ -4690,6 +4709,7 @@
[NSObject cancelPreviousPerformRequestsWithTarget:self];
for (id retainedObject in nibObjectsToRelease) [retainedObject release];
+
[nibObjectsToRelease release];
[allDatabases release];
@@ -4714,6 +4734,7 @@
if (mainToolbar) [mainToolbar release];
if (titleAccessoryView) [titleAccessoryView release];
if (taskProgressWindow) [taskProgressWindow release];
+ if (serverSupport) [serverSupport release];
[super dealloc];
}
diff --git a/Source/SPDatabaseRename.m b/Source/SPDatabaseRename.m
index ed0374a7..dc5a8967 100644
--- a/Source/SPDatabaseRename.m
+++ b/Source/SPDatabaseRename.m
@@ -74,6 +74,8 @@
{
success = [dbActionTableCopy moveTable:currentTable from:sourceDatabaseName to:targetDatabaseName];
}
+
+ [dbActionTableCopy release];
tables = [connection listTablesFromDB:sourceDatabaseName];
diff --git a/Source/SPFieldMapperController.m b/Source/SPFieldMapperController.m
index ac6f46e7..171155d7 100644
--- a/Source/SPFieldMapperController.m
+++ b/Source/SPFieldMapperController.m
@@ -819,7 +819,9 @@
[fieldMappingTableColumnNames removeAllObjects];
[fieldMappingTableDefaultValues removeAllObjects];
[fieldMappingTableTypes removeAllObjects];
+
BOOL serverGreaterThanVersion4 = ([mySQLConnection serverMajorVersion] >= 5) ? YES : NO;
+
if([importFieldNamesHeaderSwitch state] == NSOnState) {
for(id h in NSArrayObjectAtIndex(fieldMappingImportArray, 0)) {
[fieldMappingTableColumnNames addObject:h];
@@ -1010,6 +1012,7 @@
// Retrieve the server-supported encodings and add them to the menu
NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings];
NSString *utf8MenuItemTitle = nil;
+
if ([encodings count] > 0
&& ([mySQLConnection serverMajorVersion] > 4
|| ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1)))
@@ -1032,7 +1035,6 @@
}
[newTableInfoEncodingPopup selectItemWithTitle:[prefs objectForKey:SPLastImportIntoNewTableEncoding]];
-
}
[NSApp beginSheet:newTableInfoWindow
@@ -1047,7 +1049,6 @@
- (IBAction)addGlobalSourceVariable:(id)sender
{
-
addGlobalSheetIsOpen = YES;
[NSApp beginSheet:globalValuesSheet
diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m
index 21e1a66c..bd7d9253 100644
--- a/Source/SPIndexesController.m
+++ b/Source/SPIndexesController.m
@@ -26,6 +26,7 @@
#import "SPIndexesController.h"
#import "SPConstants.h"
#import "SPAlertSheets.h"
+#import "SPServerSupport.h"
// Constants
NSString *SPNewIndexIndexName = @"IndexName";
@@ -164,7 +165,7 @@ NSString *SPNewIndexKeyBlockSize = @"IndexKeyBlockSize";
// The ability to specify an index's key block size was added in MySQL 5.1.10 so disable the textfield
// if it's not supported.
- [indexKeyBlockSizeTextField setEnabled:(([connection serverMajorVersion] >= 5) && ([connection serverMinorVersion] >= 1) && ([connection serverReleaseVersion] >= 10))];
+ [indexKeyBlockSizeTextField setEnabled:[[dbDocument serverSupport] supportsIndexKeyBlockSize]];
// Begin the sheet
[NSApp beginSheet:[self window]
diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m
index 323c01b3..becccab2 100644
--- a/Source/SPSQLExporter.m
+++ b/Source/SPSQLExporter.m
@@ -499,6 +499,8 @@
{
// Check for cancellation flag
if ([self isCancelled]) {
+ [errors release];
+ [sqlString release];
[pool release];
return;
}
@@ -565,6 +567,8 @@
{
// Check for cancellation flag
if ([self isCancelled]) {
+ [errors release];
+ [sqlString release];
[pool release];
return;
}
@@ -595,6 +599,8 @@
{
// Check for cancellation flag
if ([self isCancelled]) {
+ [errors release];
+ [sqlString release];
[pool release];
return;
}
@@ -609,6 +615,8 @@
// Check for cancellation flag
if ([self isCancelled]) {
[proceduresList release];
+ [errors release];
+ [sqlString release];
[pool release];
return;
}
diff --git a/Source/SPServerSupport.h b/Source/SPServerSupport.h
new file mode 100644
index 00000000..b1afc5cd
--- /dev/null
+++ b/Source/SPServerSupport.h
@@ -0,0 +1,209 @@
+//
+// $Id$
+//
+// SPServerSupport.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on September 23, 2010
+// Copyright (c) 2010 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 <http://code.google.com/p/sequel-pro/>
+
+/**
+ * @class SPServerSupport SPServerSupport.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * This class is provided as a convenient method of determining what features/functionality the MySQL server
+ * with the supplied version numbers supports. Note that this class has no direct connection to the server,
+ * all of it's information is simply determined by way of version comparisons using hard coded values of known
+ * versions and the functionality they support.
+ *
+ * Every new MySQL connection that is established should create an instance of this class and make it globally
+ * accessible to the rest of the application to remove the need of manual version comparisons. Calling it's
+ * designated initializer (initWithMajorVersion:major:minor:release:) causes the determination of what
+ * functionality is supported, and so other initializtion is required.
+ *
+ * See the method evaluate for information regarding adding additional functionality checks.
+ */
+@interface SPServerSupport : NSObject
+{
+ // Convenience vars
+ BOOL isMySQL3;
+ BOOL isMySQL4;
+ BOOL isMySQL5;
+ BOOL isMySQL6;
+
+ // General
+ BOOL supportsInformationSchema;
+
+ // Encoding
+ BOOL supportsShowCharacterSet;
+ BOOL supportsCharacterSetDatabaseVar;
+ BOOL supportsPost41CharacterSetHandling;
+
+ // User account related
+ BOOL supportsCreateUser;
+ BOOL supportsDropUser;
+ BOOL supportsFullDropUser;
+ BOOL supportsUserMaxVars;
+ BOOL supportsShowPrivileges;
+
+ // Storage engines
+ BOOL supportsInformationSchemaEngines;
+ BOOL supportsPre41StorageEngines;
+ BOOL supportsBlackholeStorageEngine;
+ BOOL supportsArchiveStorageEngine;
+ BOOL supportsCSVStorageEngine;
+
+ // Triggers
+ BOOL supportsTriggers;
+
+ // Indexes
+ BOOL supportsIndexKeyBlockSize;
+
+ // Server versions
+ NSInteger serverMajorVersion;
+ NSInteger serverMinorVersion;
+ NSInteger serverReleaseVersion;
+}
+
+/**
+ * @property serverMajorVersion
+ */
+@property (readwrite, assign) NSInteger serverMajorVersion;
+
+/**
+ * @property serverMinorVersion
+ */
+@property (readwrite, assign) NSInteger serverMinorVersion;
+
+/**
+ * @property serverReleaseVersion
+ */
+@property (readwrite, assign) NSInteger serverReleaseVersion;
+
+/**
+ * @property isMySQL3 Indicates if the server is MySQL version 3
+ */
+@property (readonly) BOOL isMySQL3;
+
+/**
+ * @property isMySQL4 Indicates if the server is MySQL version 4
+ */
+@property (readonly) BOOL isMySQL4;
+
+/**
+ * @property isMySQL5 Indicates if the server is MySQL version 5
+ */
+@property (readonly) BOOL isMySQL5;
+
+/**
+ * @property isMySQL6 Indicates if the server is MySQL version 6
+ */
+@property (readonly) BOOL isMySQL6;
+
+/**
+ * @property supportsInformationSchema Indicates if the server supports the information_schema database
+ */
+@property (readonly) BOOL supportsInformationSchema;
+
+/**
+ * @property supportsShowCharacterSet Indicates if the server supports the SHOW CHARACTER SET statement
+ */
+@property (readonly) BOOL supportsShowCharacterSet;
+
+/**
+ * @property supportsCharacterSetDatabaseVar Indicates if the server supports the 'character_set_database'
+ * variable.
+ */
+@property (readonly) BOOL supportsCharacterSetDatabaseVar;
+
+/**
+ * @property supportsPost41CharacterSetHandling Indicates whether the server supports post 4.1 character set
+ * handling.
+ */
+@property (readonly) BOOL supportsPost41CharacterSetHandling;
+
+/**
+ * @property supportsCreateUser Indicates if the server supports the CREATE USER statement
+ */
+@property (readonly) BOOL supportsCreateUser;
+
+/**
+ * @property supportsDropUser Indicates if the server supports the DROP USER statement
+ */
+@property (readonly) BOOL supportsDropUser;
+
+/**
+ * @property supportsFullDropUser Indicates if the server supports deleting a user's priveleges when issueing
+ * the DROP USER statement.
+ */
+@property (readonly) BOOL supportsFullDropUser;
+
+/**
+ * @property supportsUserMaxVars Indicates if the server supports setting a user's maximum variables
+ */
+@property (readonly) BOOL supportsUserMaxVars;
+
+/**
+ * @property supportsShowPrivileges Indicates if the server supports the SHOW PRIVILEGES statement
+ */
+@property (readonly) BOOL supportsShowPrivileges;
+
+/**
+ * @property supportsInformationSchemaEngines Indicates if the server supports the information_schema.engines table
+ */
+@property (readonly) BOOL supportsInformationSchemaEngines;
+
+/**
+ * @property supportsPre41StorageEngines Indicates if the server supports storage engines available prior
+ * to MySQL 4.1
+ */
+@property (readonly) BOOL supportsPre41StorageEngines;
+
+/**
+ * @property supportsBlackholeStorageEngine Indicates if the server supports the BLACKHOLE storage engine
+ */
+@property (readonly) BOOL supportsBlackholeStorageEngine;
+
+/**
+ * @property supportsArchiveStorageEngine Indicates if the server supports the ARCHIVE storage engine
+ */
+@property (readonly) BOOL supportsArchiveStorageEngine;
+
+/**
+ * @property supportsCSVStorageEngine Indicates if the server supports the CSV storage engine
+ */
+@property (readonly) BOOL supportsCSVStorageEngine;
+
+/**
+ * @property supportsTriggers Indicates if the server supports table triggers
+ */
+@property (readonly) BOOL supportsTriggers;
+
+/**
+ * @property supportsIndexKeyBlockSize Indicates if the server supports specifying an index's key block size
+ */
+@property (readonly) BOOL supportsIndexKeyBlockSize;
+
+- (id)initWithMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion;
+
+- (void)evaluate;
+- (BOOL)isEqualToOrGreaterThanMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion;
+
+@end
diff --git a/Source/SPServerSupport.m b/Source/SPServerSupport.m
new file mode 100644
index 00000000..3f8227d9
--- /dev/null
+++ b/Source/SPServerSupport.m
@@ -0,0 +1,311 @@
+//
+// $Id$
+//
+// SPServerSupport.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on September 23, 2010
+// Copyright (c) 2010 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 <http://code.google.com/p/sequel-pro/>
+
+#import "SPServerSupport.h"
+#import <objc/runtime.h>
+
+@interface SPServerSupport (PrivateAPI)
+
+- (void)_invalidate;
+- (NSComparisonResult)_compareServerMajorVersion:(NSInteger)majorVersionA
+ minor:(NSInteger)minorVersionA
+ release:(NSInteger)releaseVersionA
+ withServerMajorVersion:(NSInteger)majorVersionB
+ minor:(NSInteger)minorVersionB
+ release:(NSInteger)releaseVersionB;
+
+@end
+
+@implementation SPServerSupport
+
+@synthesize isMySQL3;
+@synthesize isMySQL4;
+@synthesize isMySQL5;
+@synthesize isMySQL6;
+@synthesize supportsInformationSchema;
+@synthesize supportsShowCharacterSet;
+@synthesize supportsCharacterSetDatabaseVar;
+@synthesize supportsPost41CharacterSetHandling;
+@synthesize supportsCreateUser;
+@synthesize supportsDropUser;
+@synthesize supportsFullDropUser;
+@synthesize supportsUserMaxVars;
+@synthesize supportsShowPrivileges;
+@synthesize supportsInformationSchemaEngines;
+@synthesize supportsPre41StorageEngines;
+@synthesize supportsBlackholeStorageEngine;
+@synthesize supportsArchiveStorageEngine;
+@synthesize supportsCSVStorageEngine;
+@synthesize supportsTriggers;
+@synthesize supportsIndexKeyBlockSize;
+@synthesize serverMajorVersion;
+@synthesize serverMinorVersion;
+@synthesize serverReleaseVersion;
+
+#pragma mark -
+#pragma mark Initialization
+
+/**
+ * Creates and returns an instance of SPServerSupport with the supplied version numbers. The caller is
+ * responsible it's memory.
+ *
+ * @param majorVersion The major version number of the server
+ * @param minorVersion The minor version number of the server
+ * @param releaseVersiod The release version number of the server
+ *
+ * @return The initializes SPServerSupport instance
+ */
+- (id)initWithMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion
+{
+ if ((self == [super init])) {
+
+ serverMajorVersion = majorVersion;
+ serverMinorVersion = minorVersion;
+ serverReleaseVersion = releaseVersion;
+
+ // Determine what the server supports
+ [self evaluate];
+ }
+
+ return self;
+}
+
+#pragma mark -
+#pragma mark Public API
+
+/**
+ * Performs the actual version based comparisons to determine what functionaity the server supports. This
+ * method is called automatically as part of the designated initializer (initWithMajorVersion:major:minor:release:)
+ * and shouldn't really need to be called again throughout a connection's lifetime.
+ *
+ * Note that for the sake of simplicity this method does not try to be smart in that it does not assume
+ * the presence of functionality based on a previous version check. This allows adding new ivars in the
+ * future a matter of simply performing a new version comparison.
+ *
+ * To add a new metod for determining a server's support for specific functionality, simply add a new
+ * (read only) ivar with the prefix 'supports' and peform the version checking within this method.
+ */
+- (void)evaluate
+{
+ // By default, assumme the server doesn't support anything
+ [self _invalidate];
+
+ isMySQL3 = (serverMajorVersion == 3);
+ isMySQL4 = (serverMajorVersion == 4);
+ isMySQL5 = (serverMajorVersion == 5);
+ isMySQL6 = (serverMajorVersion == 6);
+
+ // The information schema database wasn't added until MySQL 5
+ supportsInformationSchema = (serverMajorVersion >= 5);
+
+ // The SHOW CHARACTER SET statement wasn't added until MySQL 4.1.0
+ supportsShowCharacterSet = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0];
+
+ // The variable 'character_set_database' wasn't added until MySQL 4.1.1
+ supportsCharacterSetDatabaseVar = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:1];
+
+ // As of MySQL 4.1 encoding support was greatly improved
+ supportsPost41CharacterSetHandling = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0];
+
+ // The table information_schema.engines wasn't added until MySQL 5.1.5
+ supportsInformationSchemaEngines = [self isEqualToOrGreaterThanMajorVersion:5 minor:1 release:1];
+
+ // The CREATE USER statement wasn't added until MySQL 5.0.2
+ supportsCreateUser = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:2];
+
+ // The DROP USER statement wasn't added until MySQL 4.1.1
+ supportsDropUser = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:1];
+
+ // Similarly before MySQL 5.0.2 the DROP USER statement only removed users with no privileges
+ supportsFullDropUser = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:2];
+
+ // The maximum user variable columns (within mysql.user) weren't added until MySQL 4.0.2
+ supportsUserMaxVars = [self isEqualToOrGreaterThanMajorVersion:4 minor:0 release:2];
+
+ // The SHOW PRIVILEGES statement wasn't added until MySQL 4.1.0
+ supportsShowPrivileges = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0];
+
+ // Before MySQL 4.1 the MEMORY engine was known as HEAP and the ISAM engine was available
+ supportsPre41StorageEngines = (![self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:0]);
+
+ // The BLACKHOLE storage engine wasn't added until MySQL 4.1.11
+ supportsBlackholeStorageEngine = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:11];
+
+ // The ARCHIVE storage engine wasn't added until MySQL 4.1.3
+ supportsArchiveStorageEngine = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:3];
+
+ // The CSV storage engine wasn't added until MySQL 4.1.4
+ supportsCSVStorageEngine = [self isEqualToOrGreaterThanMajorVersion:4 minor:1 release:4];
+
+ // Support for triggers wasn't added until MySQL 5.0.2
+ supportsTriggers = [self isEqualToOrGreaterThanMajorVersion:5 minor:0 release:2];
+
+ // Support for specifying an index's key block size wasn't added until MySQL 5.1.10
+ supportsIndexKeyBlockSize = [self isEqualToOrGreaterThanMajorVersion:5 minor:1 release:10];
+}
+
+/**
+ * Convenience method provided as an easy way to determine whether the currently connected server version
+ * is equal to or greater than the supplied version numbers.
+ *
+ * This method should only be used in the case that the build in support ivars don't cover the version/functionality
+ * checking that is required.
+ *
+ * @param majorVersion The major version number of the server
+ * @param minorVersion The minor version number of the server
+ * @param releaseVersiod The release version number of the server
+ *
+ * @return A BOOL indicating the result of the comparison.
+ */
+- (BOOL)isEqualToOrGreaterThanMajorVersion:(NSInteger)majorVersion minor:(NSInteger)minorVersion release:(NSInteger)releaseVersion;
+{
+ return ([self _compareServerMajorVersion:serverMajorVersion
+ minor:serverMinorVersion
+ release:serverReleaseVersion
+ withServerMajorVersion:majorVersion
+ minor:minorVersion
+ release:releaseVersion] > NSOrderedAscending);
+}
+
+/**
+ * Provides a general description of this object instance. Note that this should only be used for debugging purposes.
+ *
+ * @return The string describing the object instance
+ */
+- (NSString *)description
+{
+ unsigned int i;
+ NSString *description = [NSMutableString stringWithFormat:@"<%@: Server is MySQL version %d.%d.%d. Supports:\n", [self className], serverMajorVersion, serverMinorVersion, serverReleaseVersion];
+
+ Ivar *vars = class_copyIvarList([self class], &i);
+
+ for (NSUInteger j = 0; j < i; j++)
+ {
+ NSString *varName = [NSString stringWithUTF8String:ivar_getName(vars[j])];
+
+ if ([varName hasPrefix:@"supports"]) {
+ [description appendFormat:@"\t%@ = %@\n", varName, ((BOOL)object_getIvar(self, vars[j])) ? @"YES" : @"NO"];
+ }
+ }
+
+ [description appendString:@">"];
+
+ free(vars);
+
+ return description;
+}
+
+#pragma mark -
+#pragma mark Private API
+
+/**
+ * Invalidates all knowledge of what we know the server supports by simply reseting all ivars to their
+ * original state, that is, it doesn't support anything.
+ */
+- (void)_invalidate
+{
+ isMySQL3 = NO;
+ isMySQL4 = NO;
+ isMySQL5 = NO;
+ isMySQL6 = NO;
+
+ supportsInformationSchema = NO;
+ supportsShowCharacterSet = NO;
+ supportsCharacterSetDatabaseVar = NO;
+ supportsPost41CharacterSetHandling = NO;
+ supportsCreateUser = NO;
+ supportsDropUser = NO;
+ supportsFullDropUser = NO;
+ supportsUserMaxVars = NO;
+ supportsShowPrivileges = NO;
+ supportsInformationSchemaEngines = NO;
+ supportsPre41StorageEngines = NO;
+ supportsBlackholeStorageEngine = NO;
+ supportsArchiveStorageEngine = NO;
+ supportsCSVStorageEngine = NO;
+ supportsTriggers = NO;
+ supportsIndexKeyBlockSize = NO;
+}
+
+/**
+ * Compares the supplied version numbers to determine their order.
+ *
+ * Note that this method assumes (when comparing MySQL version numbers) that release verions in the form
+ * XX are larger than X. For example, version 5.0.18 is greater than version 5.0.8
+ *
+ * @param majorVersionA The major version number of server A
+ * @param minorVersionA The minor version number of server A
+ * @param releaseVersionA The release version number of server A
+ * @param majorVersionB The major version number of server B
+ * @param minorVersionB The minor version number of server B
+ * @param releaseVersionB The release version number of server B
+ *
+ * @return One of NSComparisonResult constants indicating the order of the comparison
+ */
+- (NSComparisonResult)_compareServerMajorVersion:(NSInteger)majorVersionA
+ minor:(NSInteger)minorVersionA
+ release:(NSInteger)releaseVersionA
+ withServerMajorVersion:(NSInteger)majorVersionB
+ minor:(NSInteger)minorVersionB
+ release:(NSInteger)releaseVersionB
+{
+ if (majorVersionA > majorVersionB) return NSOrderedDescending;
+
+ if (majorVersionA < majorVersionB) return NSOrderedAscending;
+
+ // The major versions are the same so move to checking the minor versions
+ if (minorVersionA > minorVersionB) return NSOrderedDescending;
+
+ if (minorVersionA < minorVersionB) return NSOrderedAscending;
+
+ // The minor versions are the same so move to checking the release versions
+ if (releaseVersionA > releaseVersionB) return NSOrderedDescending;
+
+ if (releaseVersionA < releaseVersionB) return NSOrderedAscending;
+
+ // Both version numbers are the same
+ return NSOrderedSame;
+}
+
+#pragma mark -
+#pragma mark Other
+
+/**
+ * Dealloc. Invalidate all ivars.
+ */
+- (void)dealloc
+{
+ // Reset version integers
+ serverMajorVersion = -1;
+ serverMinorVersion = -1;
+ serverReleaseVersion = -1;
+
+ // Invalidate all ivars
+ [self _invalidate];
+
+ [super dealloc];
+}
+
+@end
diff --git a/Source/SPTableCopy.m b/Source/SPTableCopy.m
index be486cf2..d390243c 100644
--- a/Source/SPTableCopy.m
+++ b/Source/SPTableCopy.m
@@ -53,13 +53,9 @@
[connection queryString:createTableStatement];
- if ([connection queryErrored]) {
- [createTableStatement release];
-
- return NO;
- }
+ [createTableStatement release];
- return YES;
+ return [connection queryErrored];
}
[createTableStatement release];
diff --git a/Source/SPTableData.m b/Source/SPTableData.m
index fc410044..d3e01471 100644
--- a/Source/SPTableData.m
+++ b/Source/SPTableData.m
@@ -32,6 +32,7 @@
#import "SPConstants.h"
#import "SPAlertSheets.h"
#import "RegexKitLite.h"
+#import "SPServerSupport.h"
@implementation SPTableData
@@ -147,19 +148,16 @@
*/
- (NSArray *) triggers
{
-
// Return if CREATE SYNTAX is being parsed
- if(isWorking) return [NSArray array];
+ if (isWorking) return [NSArray array];
// If triggers is nil, the triggers need to be loaded - if a table is selected on MySQL >= 5.0.2
if (!triggers) {
- if ([tableListInstance tableType] == SPTableTypeTable
- && [mySQLConnection serverMajorVersion] >= 5
- && [mySQLConnection serverMinorVersion] >= 0)
- {
+ if (([tableListInstance tableType] == SPTableTypeTable) && [[tableDocumentInstance serverSupport] supportsTriggers]) {
[self updateTriggersForCurrentTable];
- } else {
+ }
+ else {
return [NSArray array];
}
}
diff --git a/Source/SPTableTriggers.m b/Source/SPTableTriggers.m
index 4a33b729..b9956e3c 100644
--- a/Source/SPTableTriggers.m
+++ b/Source/SPTableTriggers.m
@@ -30,6 +30,7 @@
#import "SPStringAdditions.h"
#import "SPConstants.h"
#import "SPAlertSheets.h"
+#import "SPServerSupport.h"
@interface SPTableTriggers (PrivateAPI)
@@ -105,11 +106,7 @@
[labelTextField setStringValue:@""];
// Show a warning if the version of MySQL is too low to support triggers
- if ([connection serverMajorVersion] < 5
- || ([connection serverMajorVersion] == 5
- && [connection serverMinorVersion] == 0
- && [connection serverReleaseVersion] < 2))
- {
+ if (![[tableDocumentInstance serverSupport] supportsTriggers]) {
[labelTextField setStringValue:NSLocalizedString(@"This version of MySQL does not support triggers. Support for triggers was added in MySQL 5.0.2", @"triggers not supported label")];
return;
}
@@ -569,9 +566,7 @@
[tableDataInstance updateTriggersForCurrentTable];
}
- NSArray *triggers = nil;
- if ([connection serverMajorVersion] >= 5 && [connection serverMinorVersion] >= 0)
- triggers = [tableDataInstance triggers];
+ NSArray *triggers = ([[tableDocumentInstance serverSupport] supportsTriggers]) ? [tableDataInstance triggers] : nil;
for (NSDictionary *trigger in triggers)
{
diff --git a/Source/SPTablesList.m b/Source/SPTablesList.m
index 9f409e57..1a694fc2 100644
--- a/Source/SPTablesList.m
+++ b/Source/SPTablesList.m
@@ -42,6 +42,7 @@
#import "SPNavigatorController.h"
#import "SPMainThreadTrampoline.h"
#import "SPHistoryController.h"
+#import "SPServerSupport.h"
@interface SPTablesList (PrivateAPI)
@@ -125,12 +126,12 @@
// Reorder the tables in alphabetical order
[tables sortArrayUsingSelector:@selector(localizedCompare:) withPairedMutableArrays:tableTypes, nil];
- /* grab the procedures and functions
+ /* Grab the procedures and functions
*
- * using information_schema gives us more info (for information window perhaps?) but breaks
+ * Using information_schema gives us more info (for information window perhaps?) but breaks
* backward compatibility with pre 4 I believe. I left the other methods below, in case.
*/
- if ([mySQLConnection serverMajorVersion] >= 5) {
+ if ([[tableDocumentInstance serverSupport] supportsInformationSchema]) {
NSString *pQuery = [NSString stringWithFormat:@"SELECT * FROM information_schema.routines WHERE routine_schema = '%@' ORDER BY routine_name",[tableDocumentInstance database]];
theResult = [mySQLConnection queryString:pQuery];
@@ -226,9 +227,11 @@
// Add the table headers even if no tables were found
if (tableListContainsViews) {
[tables insertObject:NSLocalizedString(@"TABLES & VIEWS",@"header for table & views list") atIndex:0];
- } else {
+ }
+ else {
[tables insertObject:NSLocalizedString(@"TABLES",@"header for table list") atIndex:0];
}
+
[tableTypes insertObject:[NSNumber numberWithInteger:SPTableTypeNone] atIndex:0];
[[tablesListView onMainThread] reloadData];
@@ -237,7 +240,7 @@
// but not if the update was called from SPTableData since it calls that method
// if a selected table doesn't exist - this happens if a table was deleted/renamed by an other user
// or if the table name contains characters which are not supported by the current set encoding
- if( ![sender isKindOfClass:[SPTableData class]] && previousSelectedTable != nil && [tables indexOfObject:previousSelectedTable] < [tables count]) {
+ if ( ![sender isKindOfClass:[SPTableData class]] && previousSelectedTable != nil && [tables indexOfObject:previousSelectedTable] < [tables count]) {
NSInteger itemToReselect = [tables indexOfObject:previousSelectedTable];
tableListIsSelectable = YES;
[[tablesListView onMainThread] selectRowIndexes:[NSIndexSet indexSetWithIndex:itemToReselect] byExtendingSelection:NO];
@@ -245,7 +248,8 @@
if (selectedTableName) [selectedTableName release];
selectedTableName = [[NSString alloc] initWithString:[tables objectAtIndex:itemToReselect]];
selectedTableType = [[tableTypes objectAtIndex:itemToReselect] integerValue];
- } else {
+ }
+ else {
if (selectedTableName) [selectedTableName release];
selectedTableName = nil;
selectedTableType = SPTableTypeNone;
@@ -253,6 +257,7 @@
// Determine whether or not to show the list filter based on the number of tables, and clear it
[[self onMainThread] clearFilter];
+
if ([tables count] > 20) [self showFilter];
else [self hideFilter];
@@ -264,14 +269,12 @@
if (previousSelectedTable) [previousSelectedTable release];
// Query the structure of all databases in the background
- if(sender == self)
+ if (sender == self)
// Invoked by SP
[NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:nil];
else
// User press refresh button ergo force update
[NSThread detachNewThreadSelector:@selector(queryDbStructureWithUserInfo:) toTarget:mySQLConnection withObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], @"forceUpdate", [NSNumber numberWithBool:YES], @"cancelQuerying", nil]];
-
-
}
/**
@@ -279,9 +282,7 @@
*/
- (IBAction)addTable:(id)sender
{
- if ((![tableSourceInstance saveRowOnDeselect]) || (![tableContentInstance saveRowOnDeselect]) || (![tableDocumentInstance database])) {
- return;
- }
+ if ((![tableSourceInstance saveRowOnDeselect]) || (![tableContentInstance saveRowOnDeselect]) || (![tableDocumentInstance database])) return;
[[tableDocumentInstance parentWindow] endEditingFor:nil];
@@ -305,13 +306,16 @@
// Retrieve the server-supported encodings and add them to the menu
NSArray *encodings = [databaseDataInstance getDatabaseCharacterSetEncodings];
+
NSString *utf8MenuItemTitle = nil;
- if ([encodings count] > 0
- && ([mySQLConnection serverMajorVersion] > 4
- || ([mySQLConnection serverMajorVersion] == 4 && [mySQLConnection serverMinorVersion] >= 1)))
- {
+
+ [tableEncodingButton setEnabled:YES];
+
+ if (([encodings count] > 0) && [[tableDocumentInstance serverSupport] supportsPost41CharacterSetHandling]) {
[[tableEncodingButton menu] addItem:[NSMenuItem separatorItem]];
- for (NSDictionary *encoding in encodings) {
+
+ for (NSDictionary *encoding in encodings)
+ {
NSString *menuItemTitle = (![encoding objectForKey:@"DESCRIPTION"]) ? [encoding objectForKey:@"CHARACTER_SET_NAME"] : [NSString stringWithFormat:@"%@ (%@)", [encoding objectForKey:@"DESCRIPTION"], [encoding objectForKey:@"CHARACTER_SET_NAME"]];
[tableEncodingButton addItemWithTitle:menuItemTitle];
@@ -327,6 +331,9 @@
[tableEncodingButton insertItemWithTitle:utf8MenuItemTitle atIndex:2];
}
}
+ else {
+ [tableEncodingButton setEnabled:NO];
+ }
[NSApp beginSheet:tableSheet
modalForWindow:[tableDocumentInstance parentWindow]
@@ -1580,7 +1587,6 @@
*/
- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
{
-
// Disallow selection while the document is working on a task
if ([tableDocumentInstance isWorking]) return NO;
diff --git a/Source/SPUserManager.h b/Source/SPUserManager.h
index 97ed03a4..db13fa93 100644
--- a/Source/SPUserManager.h
+++ b/Source/SPUserManager.h
@@ -25,7 +25,7 @@
#import <Cocoa/Cocoa.h>
#import <MCPKit/MCPKit.h>
-@class BWAnchoredButtonBar;
+@class SPServerSupport, BWAnchoredButtonBar;
@interface SPUserManager : NSWindowController
{
@@ -37,6 +37,7 @@
BOOL isInitializing;
MCPConnection *mySqlConnection;
+ SPServerSupport *serverSupport;
IBOutlet NSOutlineView *outlineView;
IBOutlet NSTabView *tabView;
@@ -53,6 +54,10 @@
IBOutlet NSButton *addSchemaPrivButton;
IBOutlet NSButton *removeSchemaPrivButton;
+ IBOutlet NSTextField *maxUpdatesTextField;
+ IBOutlet NSTextField *maxConnectionsTextField;
+ IBOutlet NSTextField *maxQuestionsTextField;
+
IBOutlet NSTextField *userNameTextField;
IBOutlet BWAnchoredButtonBar *splitViewButtonBar;
@@ -66,6 +71,7 @@
}
@property (nonatomic, retain) MCPConnection *mySqlConnection;
+@property (nonatomic, retain) SPServerSupport *serverSupport;
@property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
diff --git a/Source/SPUserManager.m b/Source/SPUserManager.m
index ba431b58..b7f20a02 100644
--- a/Source/SPUserManager.m
+++ b/Source/SPUserManager.m
@@ -29,6 +29,7 @@
#import "SPStringAdditions.h"
#import "SPGrowlController.h"
#import "SPConnectionController.h"
+#import "SPServerSupport.h"
#define COLUMNIDNAME @"NameColumn"
@@ -46,7 +47,7 @@
- (void)_initializeSchemaPrivs;
- (NSArray *)_fetchPrivsWithUser:(NSString *)username schema:(NSString *)selectedSchema host:(NSString *)host;
- (void)_setSchemaPrivValues:(NSArray *)objects enabled:(BOOL)enabled;
-- (void) _initializeAvailablePrivs;
+- (void)_initializeAvailablePrivs;
@end
@@ -61,7 +62,14 @@
@synthesize grantedSchemaPrivs;
@synthesize availablePrivs;
@synthesize treeSortDescriptors;
+@synthesize serverSupport;
+#pragma mark -
+#pragma mark Initialization
+
+/**
+ * Initialization.
+ */
- (id)init
{
if ((self = [super initWithWindowNibName:@"UserManagerView"])) {
@@ -91,7 +99,7 @@
* UI specific items to set up when the window loads. This is different than awakeFromNib
* as it's only called once.
*/
--(void)windowDidLoad
+- (void)windowDidLoad
{
[tabView selectTabViewItemAtIndex:0];
@@ -110,7 +118,7 @@
treeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"displayName" ascending:YES];
[self setTreeSortDescriptors:[NSArray arrayWithObject:treeSortDescriptor]];
-
+
[super windowDidLoad];
}
@@ -120,7 +128,7 @@
*/
- (void)_initializeUsers
{
- isInitializing = TRUE; // Don't want to do some of the notifications if initializing
+ isInitializing = YES; // Don't want to do some of the notifications if initializing
NSMutableString *privKey;
NSArray *privRow;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@@ -129,7 +137,7 @@
NSMutableArray *usersResultArray = [NSMutableArray array];
// Select users from the mysql.user table
- MCPResult *result = [self.mySqlConnection queryString:@"SELECT * FROM `mysql`.`user` ORDER BY `user`"];
+ MCPResult *result = [self.mySqlConnection queryString:@"SELECT * FROM mysql.user ORDER BY user"];
NSInteger rows = [result numOfRows];
if (rows > 0) {
@@ -148,32 +156,49 @@
// Set up the array of privs supported by this server.
[self.privsSupportedByServer removeAllObjects];
-
- // Attempt to use SHOW PRIVILEGES syntax - supported since 4.1.0
- result = [self.mySqlConnection queryString:@"SHOW PRIVILEGES"];
- [result setReturnDataAsStrings:YES];
- if ([result numOfRows]) {
- while (privRow = [result fetchRowAsArray]) {
+
+ result = nil;
+
+ // Attempt to obtain user privileges if supported
+ if ([serverSupport supportsShowPrivileges]) {
+
+ result = [self.mySqlConnection queryString:@"SHOW PRIVILEGES"];
+
+ [result setReturnDataAsStrings:YES];
+ }
+
+ if (result && [result numOfRows]) {
+ while (privRow = [result fetchRowAsArray])
+ {
privKey = [NSMutableString stringWithString:[[privRow objectAtIndex:0] lowercaseString]];
+
[privKey replaceOccurrencesOfString:@" " withString:@"_" options:NSLiteralSearch range:NSMakeRange(0, [privKey length])];
[privKey appendString:@"_priv"];
+
[self.privsSupportedByServer setValue:[NSNumber numberWithBool:YES] forKey:privKey];
}
-
+ }
// If that fails, base privilege support on the mysql.users columns
- } else {
- result = [self.mySqlConnection queryString:@"SHOW COLUMNS FROM `mysql`.`user`"];
+ else {
+ result = [self.mySqlConnection queryString:@"SHOW COLUMNS FROM mysql.user"];
+
[result setReturnDataAsStrings:YES];
- while (privRow = [result fetchRowAsArray]) {
+
+ while (privRow = [result fetchRowAsArray])
+ {
privKey = [NSMutableString stringWithString:[privRow objectAtIndex:0]];
+
if (![privKey hasSuffix:@"_priv"]) continue;
+
if ([privColumnToGrantMap objectForKey:privKey]) privKey = [privColumnToGrantMap objectForKey:privKey];
+
[self.privsSupportedByServer setValue:[NSNumber numberWithBool:YES] forKey:[privKey lowercaseString]];
}
}
[pool release];
- isInitializing = FALSE;
+
+ isInitializing = NO;
}
/**
@@ -184,15 +209,15 @@
{
// Go through each item that contains a dictionary of key-value pairs
// for each user currently in the database.
- for(NSInteger i = 0; i < [items count]; i++)
+ for (NSInteger i = 0; i < [items count]; i++)
{
NSString *username = [[items objectAtIndex:i] objectForKey:@"User"];
NSArray *parentResults = [[self _fetchUserWithUserName:username] retain];
NSDictionary *item = [items objectAtIndex:i];
// Check to make sure if we already have added the parent.
- if (parentResults != nil && [parentResults count] > 0)
- {
+ if (parentResults != nil && [parentResults count] > 0) {
+
// Add Children
NSManagedObject *parent = [parentResults objectAtIndex:0];
NSManagedObject *child = [self _createNewSPUser];
@@ -202,8 +227,10 @@
NSMutableSet *children = [parent mutableSetValueForKey:@"children"];
[children addObject:child];
+
[self _initializeSchemaPrivsForChild:child];
- } else {
+ }
+ else {
// Add Parent
NSManagedObject *parent = [self _createNewSPUser];
NSManagedObject *child = [self _createNewSPUser];
@@ -219,15 +246,19 @@
NSMutableSet *children = [parent mutableSetValueForKey:@"children"];
[children addObject:child];
+
[self _initializeSchemaPrivsForChild:child];
}
+
// Save the initialized objects so that any new changes will be tracked.
NSError *error = nil;
+
[[self managedObjectContext] save:&error];
- if (error != nil)
- {
+
+ if (error != nil) {
[[NSApplication sharedApplication] presentError:error];
}
+
[parentResults release];
}
@@ -246,17 +277,16 @@
NSEntityDescription *privEntityDescription = [NSEntityDescription entityForName:@"Privileges"
inManagedObjectContext:moc];
NSArray *props = [privEntityDescription attributeKeys];
+
[availablePrivs removeAllObjects];
for (NSString *prop in props)
{
- if ([prop hasSuffix:@"_priv"] && [[self.privsSupportedByServer objectForKey:prop] boolValue])
- {
- NSString *displayName = [[prop stringByReplacingOccurrencesOfString:@"_priv"
- withString:@""] replaceUnderscoreWithSpace];
+ if ([prop hasSuffix:@"_priv"] && [[self.privsSupportedByServer objectForKey:prop] boolValue]) {
+ NSString *displayName = [[prop stringByReplacingOccurrencesOfString:@"_priv" withString:@""] replaceUnderscoreWithSpace];
+
[availablePrivs addObject:[NSDictionary dictionaryWithObjectsAndKeys:displayName, @"displayName", prop, @"name", nil]];
}
-
}
[availableController rearrangeObjects];
@@ -272,11 +302,10 @@
MCPResult *results = [self.mySqlConnection listDBs];
- if ([results numOfRows]) {
- [results dataSeek:0];
- }
+ if ([results numOfRows]) [results dataSeek:0];
- for (NSInteger i = 0; i < [results numOfRows]; i++) {
+ for (NSInteger i = 0; i < [results numOfRows]; i++)
+ {
[schemas addObject:[results fetchRowAsDictionary]];
}
@@ -333,37 +362,43 @@
// Assumes that the child has already been initialized with values from the
// global user table.
// Select rows from the db table that contains schema privs for each user/host
- NSString *queryString = [NSString stringWithFormat:@"SELECT * from `mysql`.`db` d WHERE d.user = %@ and d.host = %@",
+ NSString *queryString = [NSString stringWithFormat:@"SELECT * from mysql.db d WHERE d.user = %@ and d.host = %@",
[[[child parent] valueForKey:@"user"] tickQuotedString], [[child valueForKey:@"host"] tickQuotedString]];
+
MCPResult *queryResults = [self.mySqlConnection queryString:queryString];
- if ([queryResults numOfRows] > 0)
- {
+
+ if ([queryResults numOfRows] > 0) {
// Go to the beginning
[queryResults dataSeek:0];
}
- for (NSInteger i = 0; i < [queryResults numOfRows]; i++) {
+ for (NSInteger i = 0; i < [queryResults numOfRows]; i++)
+ {
NSDictionary *rowDict = [queryResults fetchRowAsDictionary];
NSManagedObject *dbPriv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges"
inManagedObjectContext:[self managedObjectContext]];
for (NSString *key in rowDict)
{
- if ([key hasSuffix:@"_priv"])
- {
+ if ([key hasSuffix:@"_priv"]) {
+
BOOL boolValue = [[rowDict objectForKey:key] boolValue];
+
// Special case keys
- if ([privColumnToGrantMap objectForKey:key])
- {
+ if ([privColumnToGrantMap objectForKey:key]) {
key = [privColumnToGrantMap objectForKey:key];
}
+
[dbPriv setValue:[NSNumber numberWithBool:boolValue] forKey:key];
- } else if ([key isEqualToString:@"Db"]) {
+ }
+ else if ([key isEqualToString:@"Db"]) {
[dbPriv setValue:[[rowDict objectForKey:key] stringByReplacingOccurrencesOfString:@"\\_" withString:@"_"]
forKey:key];
- } else if (![key isEqualToString:@"Host"] && ![key isEqualToString:@"User"]) {
+ }
+ else if (![key isEqualToString:@"Host"] && ![key isEqualToString:@"User"]) {
[dbPriv setValue:[rowDict objectForKey:key] forKey:key];
}
}
+
NSMutableSet *privs = [child mutableSetValueForKey:@"schema_privileges"];
[privs addObject:dbPriv];
}
@@ -412,10 +447,12 @@
if (managedObjectContext != nil) return managedObjectContext;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
+
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
+
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
@@ -453,12 +490,12 @@
- (BOOL)outlineView:(NSOutlineView *)olv isGroupItem:(id)item
{
- return FALSE;
+ return NO;
}
- (BOOL)outlineView:(NSOutlineView *)olv shouldSelectItem:(id)item
{
- return TRUE;
+ return YES;
}
- (BOOL)outlineView:(NSOutlineView *)olv shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
@@ -491,7 +528,7 @@
[availableTableView deselectAll:nil];
}
--(BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)outlineView
+- (BOOL)selectionShouldChangeInOutlineView:(NSOutlineView *)outlineView
{
if ([[treeController selectedObjects] count] > 0)
{
@@ -581,13 +618,13 @@
{
id selectedUser = [[treeController selectedObjects] objectAtIndex:0];
- // Iterate through the supported privs, setting the value of each to true
+ // Iterate through the supported privs, setting the value of each to YES
for (NSString *key in self.privsSupportedByServer) {
if (![key hasSuffix:@"_priv"]) continue;
// Perform the change in a try/catch check to avoid exceptions for unhandled privs
@try {
- [selectedUser setValue:[NSNumber numberWithBool:TRUE] forKey:key];
+ [selectedUser setValue:[NSNumber numberWithBool:YES] forKey:key];
}
@catch (NSException * e) {
}
@@ -601,13 +638,13 @@
{
id selectedUser = [[treeController selectedObjects] objectAtIndex:0];
- // Iterate through the supported privs, setting the value of each to false
+ // Iterate through the supported privs, setting the value of each to NO
for (NSString *key in self.privsSupportedByServer) {
if (![key hasSuffix:@"_priv"]) continue;
// Perform the change in a try/catch check to avoid exceptions for unhandled privs
@try {
- [selectedUser setValue:[NSNumber numberWithBool:FALSE] forKey:key];
+ [selectedUser setValue:[NSNumber numberWithBool:NO] forKey:key];
}
@catch (NSException * e) {
}
@@ -684,7 +721,7 @@
*/
- (void)editNewHost
{
- [outlineView editColumn:0 row:[outlineView selectedRow] withEvent:nil select:TRUE];
+ [outlineView editColumn:0 row:[outlineView selectedRow] withEvent:nil select:YES];
}
/**
@@ -803,7 +840,7 @@
schema:[selectedDb stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"]
host:[selectedHost valueForKey:@"host"]];
NSManagedObject *priv = nil;
- BOOL isNew = FALSE;
+ BOOL isNew = NO;
if ([selectedPrivs count] > 0)
@@ -815,10 +852,10 @@
priv = [NSEntityDescription insertNewObjectForEntityForName:@"Privileges"
inManagedObjectContext:[self managedObjectContext]];
[priv setValue:selectedDb forKey:@"db"];
- isNew = TRUE;
+ isNew = YES;
}
- // Now setup all the items that are selected to true
+ // Now setup all the items that are selected to YES
for (NSDictionary *obj in objects)
{
[priv setValue:[NSNumber numberWithBool:enabled] forKey:[obj valueForKey:@"name"]];
@@ -942,49 +979,50 @@
{
for (NSManagedObject *user in updatedUsers)
{
- if ([[[user entity] name] isEqualToString:@"Privileges"])
- {
+ if ([[[user entity] name] isEqualToString:@"Privileges"]) {
[self grantDbPrivilegesWithPrivilege:user];
-
-
- // If the parent user has changed, either the username or password have been edited.
}
- else if (![user parent])
- {
+ // If the parent user has changed, either the username or password have been edited.
+ else if (![user parent]) {
NSArray *hosts = [user valueForKey:@"children"];
// If the user has been changed, update the username on all hosts. Don't check for errors, as some
// hosts may be new.
if (![[user valueForKey:@"user"] isEqualToString:[user valueForKey:@"originaluser"]]) {
- for (NSManagedObject *child in hosts) {
+
+ for (NSManagedObject *child in hosts)
+ {
NSString *renameUserStatement = [NSString stringWithFormat:
@"RENAME USER %@@%@ TO %@@%@",
[[user valueForKey:@"originaluser"] tickQuotedString],
[[child host] tickQuotedString],
[[user valueForKey:@"user"] tickQuotedString],
[[child host] tickQuotedString]];
+
[self.mySqlConnection queryString:renameUserStatement];
}
}
// If the password has been changed, use the same password on all hosts
if (![[user valueForKey:@"password"] isEqualToString:[user valueForKey:@"originalpassword"]]) {
- for (NSManagedObject *child in hosts) {
+
+ for (NSManagedObject *child in hosts)
+ {
NSString *changePasswordStatement = [NSString stringWithFormat:
@"SET PASSWORD FOR %@@%@ = PASSWORD(%@)",
[[user valueForKey:@"user"] tickQuotedString],
[[child host] tickQuotedString],
[[user valueForKey:@"password"] tickQuotedString]];
+
[self.mySqlConnection queryString:changePasswordStatement];
[self checkAndDisplayMySqlError];
}
}
-
-
- } else {
- [self updateResourcesForUser:user];
+ }
+ else {
+ if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user];
+
[self grantPrivilegesToUser:user];
-
}
}
@@ -997,22 +1035,44 @@
for (NSManagedObject *user in deletedUsers)
{
- if (![[[user entity] name] isEqualToString:@"Privileges"] && [user valueForKey:@"host"] != nil)
+ if (![[[user entity] name] isEqualToString:@"Privileges"] && ([user valueForKey:@"host"] != nil))
{
- [droppedUsers appendFormat:@"%@@%@, ",
- [[user valueForKey:@"user"] tickQuotedString],
- [[user valueForKey:@"host"] tickQuotedString]];
+ [droppedUsers appendFormat:@"%@@%@, ", [[user valueForKey:@"user"] tickQuotedString], [[user valueForKey:@"host"] tickQuotedString]];
}
}
if ([droppedUsers length] > 2) {
- droppedUsers = [[droppedUsers substringToIndex:[droppedUsers length]-2] mutableCopy];
- [self.mySqlConnection queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]];
+ droppedUsers = [[droppedUsers substringToIndex:([droppedUsers length] - 2)] mutableCopy];
+
+ // Before MySQL 5.0.2 DROP USER just removed users with no privileges, so revoke
+ // all their privileges first. Also, REVOKE ALL PRIVILEGES was added in MySQL 4.1.2, so use the
+ // old multiple query approach (damn, I wish there were only one MySQL version!).
+ if (![serverSupport supportsFullDropUser]) {
+ [mySqlConnection queryString:[NSString stringWithFormat:@"REVOKE ALL PRIVILEGES ON *.* FROM %@", droppedUsers]];
+ [mySqlConnection queryString:[NSString stringWithFormat:@"REVOKE GRANT OPTION ON *.* FROM %@", droppedUsers]];
+ }
+
+ // DROP USER was added in MySQL 4.1.1
+ if ([serverSupport supportsDropUser]) {
+ [self.mySqlConnection queryString:[NSString stringWithFormat:@"DROP USER %@", droppedUsers]];
+ }
+ // Otherwise manually remove the user rows from the mysql.user table
+ else {
+ NSArray *users = [droppedUsers componentsSeparatedByString:@", "];
+
+ for (NSString *user in users)
+ {
+ NSArray *userDetails = [user componentsSeparatedByString:@"@"];
+
+ [mySqlConnection queryString:[NSString stringWithFormat:@"DELETE FROM mysql.user WHERE User = %@ and Host = %@", [userDetails objectAtIndex:0], [userDetails objectAtIndex:1]]];
+ }
+ }
+
[droppedUsers release];
}
- return TRUE;
+ return YES;
}
/**
@@ -1025,31 +1085,45 @@
if ([[[user entity] name] isEqualToString:@"Privileges"]) continue;
NSString *createStatement = nil;
-
- if ([user parent] && [[user parent] valueForKey:@"user"] && [[user parent] valueForKey:@"password"])
- {
- createStatement = [NSString stringWithFormat:@"CREATE USER %@@%@ IDENTIFIED BY %@",
- [[[user parent] valueForKey:@"user"] tickQuotedString],
- [[user valueForKey:@"host"] tickQuotedString],
- [[[user parent] valueForKey:@"password"] tickQuotedString]];
+
+ // Note that if the database does not support the use of the CREATE USER statment, then
+ // we must resort to using GRANT. Doing so means we must specify the privileges and the database
+ // for which these apply, so make them as restrictive as possible, but then revoke them to get the
+ // same affect as CREATE USER. That is, a new user with no privleges.
+ NSString *host = [[user valueForKey:@"host"] tickQuotedString];
+
+ if ([user parent] && [[user parent] valueForKey:@"user"] && [[user parent] valueForKey:@"password"]) {
+
+ NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString];
+ NSString *password = [[[user parent] valueForKey:@"password"] tickQuotedString];
+
+ createStatement = ([serverSupport supportsCreateUser]) ?
+ [NSString stringWithFormat:@"CREATE USER %@@%@ IDENTIFIED BY %@", username, host, password] :
+ [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@ IDENTIFIED BY %@", username, host, password];
}
- else
- {
- if ([user parent] && [[user parent] valueForKey:@"user"])
- {
- createStatement = [NSString stringWithFormat:@"CREATE USER %@@%@",
- [[[user parent] valueForKey:@"user"] tickQuotedString],
- [[user valueForKey:@"host"] tickQuotedString]];
- }
+ else if ([user parent] && [[user parent] valueForKey:@"user"]) {
+
+ NSString *username = [[[user parent] valueForKey:@"user"] tickQuotedString];
+
+ createStatement = ([serverSupport supportsCreateUser]) ?
+ [NSString stringWithFormat:@"CREATE USER %@@%@", username, host] :
+ [NSString stringWithFormat:@"GRANT SELECT ON mysql.* TO %@@%@", username, host];
}
-
- if (createStatement)
- {
+
+ if (createStatement) {
+
// Create user in database
- [self.mySqlConnection queryString:createStatement];
+ [mySqlConnection queryString:createStatement];
if ([self checkAndDisplayMySqlError]) {
- [self updateResourcesForUser:user];
+ if ([serverSupport supportsUserMaxVars]) [self updateResourcesForUser:user];
+
+ // If we created the user with the GRANT statment (MySQL < 5), then revoke the
+ // privileges we gave the new user.
+ if (![serverSupport supportsUserMaxVars]) {
+ [mySqlConnection queryString:[NSString stringWithFormat:@"REVOKE SELECT ON mysql.* FROM %@@%@", [[[user parent] valueForKey:@"user"] tickQuotedString], host]];
+ }
+
[self grantPrivilegesToUser:user];
}
}
@@ -1068,24 +1142,22 @@
NSString *dbName = [schemaPriv valueForKey:@"db"];
dbName = [dbName stringByReplacingOccurrencesOfString:@"_" withString:@"\\_"];
- NSString *statement = [NSString stringWithFormat:@"SELECT USER,HOST FROM `mysql`.`db` WHERE USER=%@ AND HOST=%@ AND DB=%@",
+ NSString *statement = [NSString stringWithFormat:@"SELECT USER, HOST FROM mysql.db WHERE USER = %@ AND HOST = %@ AND DB = %@",
[[schemaPriv valueForKeyPath:@"user.parent.user"] tickQuotedString],
[[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString],
[dbName tickQuotedString]];
MCPResult *result = [self.mySqlConnection queryString:statement];
NSUInteger rows = [result numOfRows];
BOOL userExists = YES;
- if (rows == 0)
- {
- userExists = NO;
- }
+
+ if (rows == 0) userExists = NO;
for (NSString *key in self.privsSupportedByServer)
{
if (![key hasSuffix:@"_priv"]) continue;
NSString *privilege = [key stringByReplacingOccurrencesOfString:@"_priv" withString:@""];
@try {
- if ([[schemaPriv valueForKey:key] boolValue] == TRUE)
+ if ([[schemaPriv valueForKey:key] boolValue] == YES)
{
[grantPrivileges addObject:[privilege replaceUnderscoreWithSpace]];
}
@@ -1093,11 +1165,9 @@
if (userExists || [grantPrivileges count] > 0) {
[revokePrivileges addObject:[privilege replaceUnderscoreWithSpace]];
}
-
}
}
- @catch (NSException * e) {
- }
+ @catch (NSException * e) { }
}
// Grant privileges
@@ -1108,6 +1178,7 @@
[dbName backtickQuotedString],
[[schemaPriv valueForKeyPath:@"user.parent.user"] tickQuotedString],
[[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString]];
+
[self.mySqlConnection queryString:grantStatement];
[self checkAndDisplayMySqlError];
}
@@ -1120,11 +1191,12 @@
[dbName backtickQuotedString],
[[schemaPriv valueForKeyPath:@"user.parent.user"] tickQuotedString],
[[schemaPriv valueForKeyPath:@"user.host"] tickQuotedString]];
+
[self.mySqlConnection queryString:revokeStatement];
[self checkAndDisplayMySqlError];
}
- return TRUE;
+ return YES;
}
/**
@@ -1134,7 +1206,7 @@
{
if ([user valueForKey:@"parent"] != nil) {
NSString *updateResourcesStatement = [NSString stringWithFormat:
- @"UPDATE mysql.user SET max_questions=%@,max_updates=%@,max_connections=%@ WHERE User=%@ AND Host=%@",
+ @"UPDATE mysql.user SET max_questions = %@, max_updates = %@, max_connections = %@ WHERE User = %@ AND Host = %@",
[user valueForKey:@"max_questions"],
[user valueForKey:@"max_updates"],
[user valueForKey:@"max_connections"],
@@ -1145,6 +1217,7 @@
}
}
+
/**
* Grant or revoke privileges for the supplied user.
*/
@@ -1155,7 +1228,7 @@
NSMutableArray *grantPrivileges = [NSMutableArray array];
NSMutableArray *revokePrivileges = [NSMutableArray array];
- for(NSString *key in self.privsSupportedByServer)
+ for (NSString *key in self.privsSupportedByServer)
{
if (![key hasSuffix:@"_priv"]) continue;
NSString *privilege = [key stringByReplacingOccurrencesOfString:@"_priv" withString:@""];
@@ -1164,7 +1237,7 @@
// Check the value of the priv and assign to grant or revoke query as appropriate; do this
// in a try/catch check to avoid exceptions for unhandled privs
@try {
- if ([[user valueForKey:key] boolValue] == TRUE) {
+ if ([[user valueForKey:key] boolValue] == YES) {
[grantPrivileges addObject:[privilege replaceUnderscoreWithSpace]];
} else {
[revokePrivileges addObject:[privilege replaceUnderscoreWithSpace]];
@@ -1181,6 +1254,7 @@
[[grantPrivileges componentsJoinedByCommas] uppercaseString],
[[[user parent] valueForKey:@"user"] tickQuotedString],
[[user valueForKey:@"host"] tickQuotedString]];
+
[self.mySqlConnection queryString:grantStatement];
[self checkAndDisplayMySqlError];
}
@@ -1192,6 +1266,7 @@
[[revokePrivileges componentsJoinedByCommas] uppercaseString],
[[[user parent] valueForKey:@"user"] tickQuotedString],
[[user valueForKey:@"host"] tickQuotedString]];
+
[self.mySqlConnection queryString:revokeStatement];
[self checkAndDisplayMySqlError];
}
@@ -1202,7 +1277,7 @@
[self grantDbPrivilegesWithPrivilege:priv];
}
- return TRUE;
+ return YES;
}
/**
@@ -1285,6 +1360,7 @@
#pragma mark -
#pragma mark Tab View Delegate methods
+
- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
BOOL retVal = YES;
@@ -1299,28 +1375,46 @@
if ([[tabViewItem identifier] isEqualToString:@"Global Privileges"]
|| [[tabViewItem identifier] isEqualToString:@"Resources"]
|| [[tabViewItem identifier] isEqualToString:@"Schema Privileges"]) {
- id parent = [selectedObject parent];
- if (parent) {
+
+ id parent = [selectedObject parent];
+
+ if (parent) {
retVal = ([[parent children] count] > 0);
- } else {
+ }
+ else {
retVal = ([[selectedObject children] count] > 0);
}
- if (retVal == NO) {
+
+ if (retVal == NO) {
NSAlert *alert = [NSAlert alertWithMessageText:@"User doesn't have any hosts."
defaultButton:NSLocalizedString(@"Add Host", @"Add Host")
alternateButton:NSLocalizedString(@"Cancel", @"cancel button")
otherButton:nil
informativeTextWithFormat:@"This user doesn't have any hosts associated with it. User will be deleted unless one is added"];
- NSInteger ret = [alert runModal];
- if (ret == NSAlertDefaultReturn) {
+
+ NSInteger ret = [alert runModal];
+
+ if (ret == NSAlertDefaultReturn) {
[self addHost:nil];
}
}
-
+
+ // If this is the resources tab, enable or disable the controls based on the server's support for them
+ if ([[tabViewItem identifier] isEqualToString:@"Resources"]) {
+
+ BOOL serverSupportsUserMaxVars = [serverSupport supportsUserMaxVars];
+
+ // Disable the fields according to the version
+ [maxUpdatesTextField setEnabled:serverSupportsUserMaxVars];
+ [maxConnectionsTextField setEnabled:serverSupportsUserMaxVars];
+ [maxQuestionsTextField setEnabled:serverSupportsUserMaxVars];
+ }
}
+
return retVal;
}
--(void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
+
+- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
if ([[treeController selectedObjects] count] == 0) return;
@@ -1418,27 +1512,11 @@
}
}
- else if ([notification object] == grantedTableView)
- {
- if ([[grantedController selectedObjects] count] > 0)
- {
- [removeSchemaPrivButton setEnabled:YES];
-
- } else {
- [removeSchemaPrivButton setEnabled:NO];
- }
-
-
+ else if ([notification object] == grantedTableView) {
+ [removeSchemaPrivButton setEnabled:([[grantedController selectedObjects] count] > 0)];
}
- else if ([notification object] == availableTableView)
- {
- if ([[availableController selectedObjects] count] > 0)
- {
- [addSchemaPrivButton setEnabled:YES];
- } else {
- [addSchemaPrivButton setEnabled:NO];
- }
-
+ else if ([notification object] == availableTableView) {
+ [addSchemaPrivButton setEnabled:([[availableController selectedObjects] count] > 0)];
}
}
@@ -1450,6 +1528,7 @@
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
+
[managedObjectContext release];
[persistentStoreCoordinator release];
[managedObjectModel release];
@@ -1460,6 +1539,7 @@
[availablePrivs release];
[grantedSchemaPrivs release];
[treeSortDescriptor release];
+ [serverSupport release];
[super dealloc];
}