From 44a5f9e552b3d5e1f9ef1c6d11f34e893d67e85b Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Thu, 7 Oct 2010 18:56:33 +0000 Subject: 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. --- Interfaces/English.lproj/UserManagerView.xib | 83 +++++-- Source/SPDatabaseData.h | 23 +- Source/SPDatabaseData.m | 95 ++++---- Source/SPDatabaseDocument.h | 12 +- Source/SPDatabaseDocument.m | 95 +++++--- Source/SPDatabaseRename.m | 2 + Source/SPFieldMapperController.m | 5 +- Source/SPIndexesController.m | 3 +- Source/SPSQLExporter.m | 8 + Source/SPServerSupport.h | 209 ++++++++++++++++ Source/SPServerSupport.m | 311 ++++++++++++++++++++++++ Source/SPTableCopy.m | 8 +- Source/SPTableData.m | 12 +- Source/SPTableTriggers.m | 11 +- Source/SPTablesList.m | 42 ++-- Source/SPUserManager.h | 8 +- Source/SPUserManager.m | 350 ++++++++++++++++----------- sequel-pro.xcodeproj/project.pbxproj | 11 +- 18 files changed, 1002 insertions(+), 286 deletions(-) create mode 100644 Source/SPServerSupport.h create mode 100644 Source/SPServerSupport.m diff --git a/Interfaces/English.lproj/UserManagerView.xib b/Interfaces/English.lproj/UserManagerView.xib index c4437677..795c247c 100644 --- a/Interfaces/English.lproj/UserManagerView.xib +++ b/Interfaces/English.lproj/UserManagerView.xib @@ -3,7 +3,7 @@ 1050 10F569 - 788 + 804 1038.29 461.00 @@ -15,18 +15,18 @@ YES - 788 - 1.2.2 + 804 + 1.2.5 YES - + YES - com.apple.InterfaceBuilder.CocoaPlugin com.brandonwalkin.BWToolkit + com.apple.InterfaceBuilder.CocoaPlugin YES @@ -56,7 +56,7 @@ User Managment NSWindow - {1.79769e+308, 1.79769e+308} + {3.40282e+38, 3.40282e+38} {752, 506} @@ -148,7 +148,7 @@ controlBackgroundColor 3 - MC42NjY2NjY2NjY3AA + MC42NjY2NjY2ODY1AA @@ -1623,7 +1623,7 @@ 256 _doScroller: - 0.96969696969696972 + 0.96969699859619141 @@ -1633,7 +1633,7 @@ 257 _doScroller: - 0.99236641221374045 + 0.99236643314361572 @@ -1752,7 +1752,7 @@ 256 _doScroller: - 0.96969696969696972 + 0.96969699859619141 @@ -1762,7 +1762,7 @@ 257 _doScroller: - 0.9928057553956835 + 0.99280577898025513 @@ -1978,8 +1978,8 @@ YES YES - , - . + . + , NO YES NO @@ -2044,8 +2044,8 @@ YES YES - , - . + . + , NO YES NO @@ -2110,8 +2110,8 @@ YES YES - , - . + . + , NO YES NO @@ -2224,7 +2224,7 @@ {{0, 0}, {1680, 1028}} {752, 528} - {1.79769e+308, 1.79769e+308} + {3.40282e+38, 3.40282e+38} @@ -4121,6 +4121,30 @@ 979 + + + maxUpdatesTextField + + + + 980 + + + + maxConnectionsTextField + + + + 981 + + + + maxQuestionsTextField + + + + 982 + @@ -5789,7 +5813,7 @@ - 979 + 982 @@ -6018,6 +6042,9 @@ availableTableView grantedController grantedTableView + maxConnectionsTextField + maxQuestionsTextField + maxUpdatesTextField outlineView privsSupportedByServer removeSchemaPrivButton @@ -6035,6 +6062,9 @@ NSTableView NSArrayController NSTableView + NSTextField + NSTextField + NSTextField NSOutlineView NSMutableDictionary NSButton @@ -6055,6 +6085,9 @@ availableTableView grantedController grantedTableView + maxConnectionsTextField + maxQuestionsTextField + maxUpdatesTextField outlineView privsSupportedByServer removeSchemaPrivButton @@ -6087,6 +6120,18 @@ grantedTableView NSTableView + + maxConnectionsTextField + NSTextField + + + maxQuestionsTextField + NSTextField + + + maxUpdatesTextField + NSTextField + outlineView NSOutlineView 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 #import -@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 + +/** + * @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 + +#import "SPServerSupport.h" +#import + +@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 #import -@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]; } diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 868c7ff0..a7b30e04 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 1792C26110AE1A2D00ABE758 /* SPConnectionDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1792C26010AE1A2D00ABE758 /* SPConnectionDelegate.m */; }; 179ECECA11F265FC009C6A40 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 179ECEC611F265EE009C6A40 /* libbz2.dylib */; }; 179F15060F7C433C00579954 /* SPEditorTokens.l in Sources */ = {isa = PBXBuildFile; fileRef = 179F15050F7C433C00579954 /* SPEditorTokens.l */; }; + 17A20AC6124F9B110095CEFB /* SPServerSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 17A20AC5124F9B110095CEFB /* SPServerSupport.m */; }; 17A7773411C52D8E001E27B4 /* SPIndexesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17A7773311C52D8E001E27B4 /* SPIndexesController.m */; }; 17A7773811C52E61001E27B4 /* IndexesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17A7773611C52E61001E27B4 /* IndexesView.xib */; }; 17AF787B11FC41C00073D043 /* SPExportFilenameUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */; }; @@ -547,6 +548,8 @@ 179ECEC611F265EE009C6A40 /* libbz2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbz2.dylib; path = usr/lib/libbz2.dylib; sourceTree = SDKROOT; }; 179F15040F7C433C00579954 /* SPEditorTokens.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEditorTokens.h; sourceTree = ""; }; 179F15050F7C433C00579954 /* SPEditorTokens.l */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.lex; path = SPEditorTokens.l; sourceTree = ""; }; + 17A20AC4124F9B110095CEFB /* SPServerSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPServerSupport.h; sourceTree = ""; }; + 17A20AC5124F9B110095CEFB /* SPServerSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPServerSupport.m; sourceTree = ""; }; 17A66F6A10B1ED59004C9B12 /* old-appicon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "old-appicon.icns"; sourceTree = ""; }; 17A7773211C52D8E001E27B4 /* SPIndexesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIndexesController.h; sourceTree = ""; }; 17A7773311C52D8E001E27B4 /* SPIndexesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPIndexesController.m; sourceTree = ""; }; @@ -1348,10 +1351,12 @@ 173E70D4107FF6E7008733C9 /* Data Controllers */ = { isa = PBXGroup; children = ( - 1740FAB90FC4372F00CF3699 /* SPDatabaseData.h */, - 1740FABA0FC4372F00CF3699 /* SPDatabaseData.m */, 58FEF57C0F3B4E9700518E8E /* SPTableData.h */, 58FEF57D0F3B4E9700518E8E /* SPTableData.m */, + 1740FAB90FC4372F00CF3699 /* SPDatabaseData.h */, + 1740FABA0FC4372F00CF3699 /* SPDatabaseData.m */, + 17A20AC4124F9B110095CEFB /* SPServerSupport.h */, + 17A20AC5124F9B110095CEFB /* SPServerSupport.m */, ); name = "Data Controllers"; sourceTree = ""; @@ -2358,6 +2363,7 @@ isa = PBXProject; buildConfigurationList = C05733CB08A9546B00998B17 /* Build configuration list for PBXProject "sequel-pro" */; compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, @@ -2779,6 +2785,7 @@ BC878A71121A836F00AE5066 /* SPColorWellCell.m in Sources */, BC398A2D121D526200BE3EF4 /* SPCopyTable.m in Sources */, BC32F242121D66260067305E /* SPFileManagerAdditions.m in Sources */, + 17A20AC6124F9B110095CEFB /* SPServerSupport.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- cgit v1.2.3