diff options
Diffstat (limited to 'Frameworks/MCPKit')
3 files changed, 352 insertions, 259 deletions
diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m index c24c8c8f..d5e11445 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m @@ -34,6 +34,7 @@ #import "MCPConnectionProxy.h" #import "MCPStringAdditions.h" #import "RegexKitLite.h" +#import "NSNotificationAdditions.h" #include <unistd.h> #include <mach/mach_time.h> @@ -1376,7 +1377,7 @@ void performThreadedKeepAlive(void *ptr) if ([delegate respondsToSelector:@selector(queryGaveError:connection:)]) [delegate queryGaveError:@"No connection available!" connection:self]; // Notify that the query has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:delegate]; + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:delegate]; // Inform the delegate that there is no connection available if (delegate && [delegate respondsToSelector:@selector(noConnectionAvailable:)]) { @@ -1427,11 +1428,14 @@ void performThreadedKeepAlive(void *ptr) [self setLastErrorMessage:errorMessage]; // Notify that the query has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:delegate]; + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:delegate]; + // Show an error alert while resetting - NSBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), - nil, nil, [delegate valueForKeyPath:@"tableWindow"], self, nil, nil, nil, errorMessage); - + if ([delegate respondsToSelector:@selector(showErrorWithTitle:message:)]) + [delegate showErrorWithTitle:NSLocalizedString(@"Error", @"error") message:errorMessage]; + else + NSRunAlertPanel(NSLocalizedString(@"Error", @"error"), errorMessage, @"OK", nil, nil); + return nil; } } @@ -1445,7 +1449,7 @@ void performThreadedKeepAlive(void *ptr) if (queryErrorMessage) [queryErrorMessage release], queryErrorMessage = nil; // Notify that the query has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SMySQLQueryHasBeenPerformed" object:delegate]; + [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryHasBeenPerformed" object:delegate]; return nil; } } @@ -1483,7 +1487,8 @@ void performThreadedKeepAlive(void *ptr) // Ensure no problem occurred during the result fetch if (mysql_errno(mConnection) != 0) { - queryErrorMessage = [[NSString alloc] initWithString:[self stringWithCString:mysql_error(mConnection)]]; + queryErrorMessage = [self stringWithCString:mysql_error(mConnection)]; + if (queryErrorMessage) [queryErrorMessage retain]; queryErrorId = mysql_errno(mConnection); break; } @@ -1511,7 +1516,8 @@ void performThreadedKeepAlive(void *ptr) queryErrorId = 1317; } else { if (queryErrorMessage) [queryErrorMessage release], queryErrorMessage = nil; - queryErrorMessage = [[NSString alloc] initWithString:[self stringWithCString:mysql_error(mConnection)]]; + queryErrorMessage = [self stringWithCString:mysql_error(mConnection)]; + if (queryErrorMessage) [queryErrorMessage retain]; queryErrorId = mysql_errno(mConnection); // If the error was a connection error, retry once @@ -1600,55 +1606,58 @@ void performThreadedKeepAlive(void *ptr) // Set queryCancelled to prevent query retries queryCancelled = YES; - // For MySQL server versions >=5, try to kill the connection. This requires - // setting up a new connection, and running a KILL QUERY via it. - if ([self serverMajorVersion] >= 5) { - - MYSQL *killerConnection = mysql_init(NULL); - if (killerConnection) { - const char *theLogin = [self cStringFromString:connectionLogin]; - const char *theHost; - const char *thePass = NULL; - const char *theSocket; - void *connectionSetupStatus; + // Set up a new connection, and running a KILL QUERY via it. + MYSQL *killerConnection = mysql_init(NULL); + if (killerConnection) { + const char *theLogin = [self cStringFromString:connectionLogin]; + const char *theHost; + const char *thePass = NULL; + const char *theSocket; + void *connectionSetupStatus; - mysql_options(killerConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); + mysql_options(killerConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); - // Set up the host, socket and password as per the connect method - if (!connectionHost || ![connectionHost length]) { - theHost = NULL; - } else { - theHost = [self cStringFromString:connectionHost]; + // Set up the host, socket and password as per the connect method + if (!connectionHost || ![connectionHost length]) { + theHost = NULL; + } else { + theHost = [self cStringFromString:connectionHost]; + } + if (connectionSocket == nil || ![connectionSocket length]) { + theSocket = kMCPConnectionDefaultSocket; + } else { + theSocket = [self cStringFromString:connectionSocket]; + } + if (!connectionPassword) { + if (delegate && [delegate respondsToSelector:@selector(keychainPasswordForConnection:)]) { + thePass = [self cStringFromString:[delegate keychainPasswordForConnection:self]]; } - if (connectionSocket == nil || ![connectionSocket length]) { - theSocket = kMCPConnectionDefaultSocket; + } else { + thePass = [self cStringFromString:connectionPassword]; + } + + // Connect + connectionSetupStatus = mysql_real_connect(killerConnection, theHost, theLogin, thePass, NULL, connectionPort, theSocket, mConnectionFlags); + thePass = NULL; + if (connectionSetupStatus) { + + // Set up a KILL query. For MySQL 5+, kill just the query; otherwise, kill the thread. + NSStringEncoding killerConnectionEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(killerConnection)]; + NSString *killQueryString; + if ([self serverMajorVersion] >= 5) { + killQueryString = [NSString stringWithFormat:@"KILL QUERY %lu", mConnection->thread_id]; } else { - theSocket = [self cStringFromString:connectionSocket]; + killQueryString = [NSString stringWithFormat:@"KILL %lu", mConnection->thread_id]; } - if (!connectionPassword) { - if (delegate && [delegate respondsToSelector:@selector(keychainPasswordForConnection:)]) { - thePass = [self cStringFromString:[delegate keychainPasswordForConnection:self]]; - } - } else { - thePass = [self cStringFromString:connectionPassword]; - } - - // Connect - connectionSetupStatus = mysql_real_connect(killerConnection, theHost, theLogin, thePass, NULL, connectionPort, theSocket, mConnectionFlags); - thePass = NULL; - if (connectionSetupStatus) { - NSStringEncoding killerConnectionEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(killerConnection)]; - NSString *killerQueryString = [NSString stringWithFormat:@"KILL QUERY %lu", mConnection->thread_id]; - NSData *encodedKillerQueryData = NSStringDataUsingLossyEncoding(killerQueryString, killerConnectionEncoding, 1); - const char *killerQueryCString = [encodedKillerQueryData bytes]; - unsigned long killerQueryCStringLength = [encodedKillerQueryData length]; - if (mysql_real_query(killerConnection, killerQueryCString, killerQueryCStringLength) == 0) { - mysql_close(killerConnection); - queryCancelUsedReconnect = NO; - return; - } + NSData *encodedKillQueryData = NSStringDataUsingLossyEncoding(killQueryString, killerConnectionEncoding, 1); + const char *killQueryCString = [encodedKillQueryData bytes]; + unsigned long killQueryCStringLength = [encodedKillQueryData length]; + if (mysql_real_query(killerConnection, killQueryCString, killQueryCStringLength) == 0) { mysql_close(killerConnection); + queryCancelUsedReconnect = NO; + return; } + mysql_close(killerConnection); } } @@ -1877,6 +1886,7 @@ void performThreadedKeepAlive(void *ptr) - (void)queryDbStructureWithUserInfo:(NSDictionary*)userInfo { NSAutoreleasePool *queryPool = [[NSAutoreleasePool alloc] init]; + BOOL structureWasUpdated = NO; // if 'cancelQuerying' is set try to interrupt any current querying if(userInfo && [userInfo objectForKey:@"cancelQuerying"]) @@ -1900,37 +1910,35 @@ void performThreadedKeepAlive(void *ptr) // Re-init with already cached data from navigator controller NSMutableDictionary *queriedStructure = [NSMutableDictionary dictionary]; NSDictionary *dbstructure = [[self delegate] getDbStructure]; - [queriedStructure setDictionary:[NSMutableDictionary dictionaryWithDictionary:dbstructure]]; + if (dbstructure) [queriedStructure setDictionary:[NSMutableDictionary dictionaryWithDictionary:dbstructure]]; NSMutableArray *queriedStructureKeys = [NSMutableArray array]; NSArray *dbStructureKeys = [[self delegate] allSchemaKeys]; - [queriedStructureKeys setArray:dbStructureKeys]; + if (dbStructureKeys) [queriedStructureKeys setArray:dbStructureKeys]; - BOOL removeAddFlag = NO; + // Retrieve all the databases known of by the delegate + NSMutableArray *connectionDatabases = [NSMutableArray array]; + [connectionDatabases addObjectsFromArray:[[self delegate] allSystemDatabaseNames]]; + [connectionDatabases addObjectsFromArray:[[self delegate] allDatabaseNames]]; // Add all known databases coming from connection if they aren't parsed yet - NSArray *dbs = [[NSString stringWithFormat:@"%@%@%@", - [[[self delegate] allSystemDatabaseNames] componentsJoinedByString:SPUniqueSchemaDelimiter], - SPUniqueSchemaDelimiter, - [[[self delegate] allDatabaseNames] componentsJoinedByString:SPUniqueSchemaDelimiter]] - componentsSeparatedByString:SPUniqueSchemaDelimiter]; - - for(id db in dbs) { + for (id db in connectionDatabases) { NSString *dbid = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, db]; if(![queriedStructure objectForKey:dbid]) { - removeAddFlag = YES; + structureWasUpdated = YES; [queriedStructure setObject:db forKey:dbid]; [queriedStructureKeys addObject:dbid]; } } - // Remove deleted databases in structure and keys in allKeysofDbStructure - // Use a dict to avoid <NSCFDictionary> was mutated while being enumerated. while iterating via allKeys + // Check the existing databases in the 'structure' and 'allKeysOfDbStructure' stores, + // and remove any that are no longer found in the connectionDatabases list (indicating deletion). + // Iterate through extracted keys to avoid <NSCFDictionary> mutation while being enumerated. NSArray *keys = [queriedStructure allKeys]; for(id key in keys) { NSString *db = [[key componentsSeparatedByString:SPUniqueSchemaDelimiter] objectAtIndex:1]; - if(![dbs containsObject:db]) { - removeAddFlag = YES; + if(![connectionDatabases containsObject:db]) { + structureWasUpdated = YES; [queriedStructure removeObjectForKey:key]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", key, SPUniqueSchemaDelimiter]]; [queriedStructureKeys filterUsingPredicate:predicate]; @@ -1942,79 +1950,73 @@ void performThreadedKeepAlive(void *ptr) if([delegate respondsToSelector:@selector(database)]) currentDatabase = [[self delegate] database]; + // Determine whether the database details need to be queried. + BOOL shouldQueryStructure = YES; + NSString *db_id = nil; + + // If no database is selected, no need to check further if(!currentDatabase || (currentDatabase && ![currentDatabase length])) { + shouldQueryStructure = NO; + + // Otherwise, build up the schema key for the database to be retrieved. + } else { + db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; + + // Check to see if a cache already exists for the database. + if ([queriedStructure objectForKey:db_id] && [[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { + + // The cache is available. If the `mysql` or `information_schema` databases are being queried, + // never requery as their structure will never change. + if ([currentDatabase isEqualToString:@"mysql"] || [currentDatabase isEqualToString:@"information_schema"]) { + shouldQueryStructure = NO; + + // Otherwise, if the forceUpdate flag wasn't supplied or evaluates to false, also don't update. + } else if (userInfo == nil || ![userInfo objectForKey:@"forceUpdate"] || ![[userInfo objectForKey:@"forceUpdate"] boolValue]) { + shouldQueryStructure = NO; + } + } + } + + // If it has been determined that no new structure needs to be retrieved, clean up and return. + if (!shouldQueryStructure) { - // Updating the global variables and make sure that no request reads these global variables + // Update the global variables and make sure that no request reads these global variables // while updating [self performSelectorOnMainThread:@selector(lockQuerying) withObject:nil waitUntilDone:YES]; [self performSelectorOnMainThread:@selector(updateGlobalVariablesWith:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:queriedStructure, @"structure", queriedStructureKeys, @"keys", nil] waitUntilDone:YES]; [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; - if(removeAddFlag) + if (structureWasUpdated) [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; [queryPool release]; return; } - NSString *db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; - - // mysql and information_schema's schema will never change thus query data only once - if([currentDatabase isEqualToString:@"mysql"]) { - if([queriedStructure objectForKey:db_id] && [[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { - - // Updating the global variables and make sure that no request reads these global variables - // while updating - [self performSelectorOnMainThread:@selector(lockQuerying) withObject:nil waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(updateGlobalVariablesWith:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:queriedStructure, @"structure", queriedStructureKeys, @"keys", nil] waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; - if(removeAddFlag) - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - [queryPool release]; - return; - } - } - if([currentDatabase isEqualToString:@"information_schema"]) { - if([queriedStructure objectForKey:db_id] && [[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { - // Updating the global variables and make sure that no request reads these global variables - // while updating - [self performSelectorOnMainThread:@selector(lockQuerying) withObject:nil waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(updateGlobalVariablesWith:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:queriedStructure, @"structure", queriedStructureKeys, @"keys", nil] waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; - if(removeAddFlag) - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - [queryPool release]; - return; - } + // Retrieve the tables and views for this database from tablesList (TODO: split out of MCPKit) + NSMutableArray *tablesAndViews = [NSMutableArray array]; + for (id aTable in [[[self delegate] valueForKeyPath:@"tablesListInstance"] allTableNames]) { + NSDictionary *aTableDict = [NSDictionary dictionaryWithObjectsAndKeys: + aTable, @"name", + @"0", @"type", + nil]; + [tablesAndViews addObject:aTableDict]; } - - if(userInfo == nil || ![userInfo objectForKey:@"forceUpdate"]) { - if([queriedStructure objectForKey:db_id] && [[queriedStructure objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { - // Updating the global variables and make sure that no request reads these global variables - // while updating - [self performSelectorOnMainThread:@selector(lockQuerying) withObject:nil waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(updateGlobalVariablesWith:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:queriedStructure, @"structure", queriedStructureKeys, @"keys", nil] waitUntilDone:YES]; - [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; - if(removeAddFlag) - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - [queryPool release]; - return; - } + for (id aView in [[[self delegate] valueForKeyPath:@"tablesListInstance"] allViewNames]) { + NSDictionary *aViewDict = [NSDictionary dictionaryWithObjectsAndKeys: + aView, @"name", + @"0", @"type", + nil]; + [tablesAndViews addObject:aViewDict]; } - NSArray *tables = [[[self delegate] valueForKeyPath:@"tablesListInstance"] allTableNames]; - NSArray *tableviews = [[[self delegate] valueForKeyPath:@"tablesListInstance"] allViewNames]; - - NSUInteger numberOfTables = 0; - if(tables && [tables count]) numberOfTables += [tables count]; - if(tableviews && [tableviews count]) numberOfTables += [tableviews count]; - // Do not parse more than 2000 tables/views per db - if(numberOfTables > 2000) { - NSLog(@"%ld items in database %@. Only 2000 items can be parsed. Stopped parsing.", numberOfTables, currentDatabase); - [queryPool release]; - return; - } + if([tablesAndViews count] > 2000) { + NSLog(@"%ld items in database %@. Only 2000 items can be parsed. Stopped parsing.", [tablesAndViews count], currentDatabase); + [queryPool release]; + return; + } - // For future usage + // For future usage - currently unused + // If the affected item name and type - for example, table type and table name - were supplied, extract it. NSString *affectedItem = nil; NSInteger affectedItemType = -1; if(userInfo && [userInfo objectForKey:@"affectedItem"]) { @@ -2025,15 +2027,14 @@ void performThreadedKeepAlive(void *ptr) affectedItem = nil; } - // Delete all stored data for to be queried db - if([queriedStructure isKindOfClass:[NSDictionary class]]) - [queriedStructure removeObjectForKey:db_id]; + // Delete all stored data for the database to be updated, leaving the structure key + [queriedStructure removeObjectForKey:db_id]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", db_id, SPUniqueSchemaDelimiter]]; [queriedStructureKeys filterUsingPredicate:predicate]; - [queriedStructureKeys removeObject:db_id]; - // Re-add currentDatabase in case that structure querying will fail - [queriedStructure setObject:currentDatabase forKey:db_id]; + // Set up the database as an empty mutable dictionary ready for tables, and store a reference + [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; + NSMutableDictionary *databaseStructure = [queriedStructure objectForKey:db_id]; NSString *currentDatabaseEscaped = [currentDatabase stringByReplacingOccurrencesOfString:@"`" withString:@"``"]; @@ -2076,105 +2077,68 @@ void performThreadedKeepAlive(void *ptr) NSString *charset; NSUInteger uniqueCounter = 0; // used to make field data unique - - // Get the doc encoding due to pref settings etc. - NSString *docEncoding = [[self delegate] connectionEncoding]; - NSStringEncoding theConnectionEncoding = [MCPConnection encodingForMySQLEncoding:[self cStringFromString:docEncoding]]; - - // Try to set connection encoding - NSString *query = [NSString stringWithFormat:@"SET NAMES '%@'", docEncoding]; - NSData *encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); - const char *queryCString = [encodedQueryData bytes]; - unsigned long queryCStringLength = [encodedQueryData length]; - if (mysql_real_query(structConnection, queryCString, queryCStringLength) != 0) { - NSLog(@"Error while querying the database structure. Could not set encoding to %@", docEncoding); - [queryPool release]; - return; - } + NSString *query; + NSData *encodedQueryData; + const char *queryCString; + unsigned long queryCStringLength; + + // Get the doc encoding due to pref settings etc, defaulting to UTF8 + NSString *docEncoding = [[self delegate] connectionEncoding]; + if (!docEncoding) docEncoding = @"utf8"; + NSStringEncoding theConnectionEncoding = [MCPConnection encodingForMySQLEncoding:[self cStringFromString:docEncoding]]; + + // Try to set connection encoding for MySQL >= 4.1 + if ([self serverMajorVersion] > 4 || ([self serverMajorVersion] >= 4 && [self serverMinorVersion] >= 1)) { + query = [NSString stringWithFormat:@"SET NAMES '%@'", docEncoding]; + encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); + queryCString = [encodedQueryData bytes]; + queryCStringLength = [encodedQueryData length]; + if (mysql_real_query(structConnection, queryCString, queryCStringLength) != 0) { + NSLog(@"Error while querying the database structure. Could not set encoding to %@", docEncoding); + [queryPool release]; + return; + } + } // Increase global query-db-counter [self performSelectorOnMainThread:@selector(incrementQueryingDbStructure) withObject:nil waitUntilDone:YES]; - [queriedStructureKeys addObject:db_id]; + // Loop through the known tables and views, retrieving details for each + for (NSDictionary *aTableDict in tablesAndViews) { - // Query all tables - for(NSString* table in tables) { - if(cancelQueryingDbStructure) { + // If cancelled, abort without saving + if (cancelQueryingDbStructure) { [self performSelectorOnMainThread:@selector(decrementQueryingDbStructure) withObject:nil waitUntilDone:YES]; [queryPool release]; return; } - NSString *query = [NSString stringWithFormat:@"SHOW FULL COLUMNS FROM `%@` FROM `%@`", - [table stringByReplacingOccurrencesOfString:@"`" withString:@"``"], - currentDatabaseEscaped]; - NSData *encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); - const char *queryCString = [encodedQueryData bytes]; - unsigned long queryCStringLength = [encodedQueryData length]; - if (mysql_real_query(structConnection, queryCString, queryCStringLength) != 0) { - // NSLog(@"error %@", table); - continue; - } - theResult = mysql_use_result(structConnection); - NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, table]; - - [queriedStructureKeys addObject:table_id]; - - while(row = mysql_fetch_row(theResult)) { - NSString *field = [self stringWithCString:row[0] usingEncoding:theConnectionEncoding] ; - NSString *type = [self stringWithCString:row[1] usingEncoding:theConnectionEncoding] ; - NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; - NSString *coll = [self stringWithCString:row[2] usingEncoding:theConnectionEncoding] ; - NSString *isnull = [self stringWithCString:row[3] usingEncoding:theConnectionEncoding] ; - NSString *key = [self stringWithCString:row[4] usingEncoding:theConnectionEncoding] ; - NSString *def = [self stringWithCString:row[5] usingEncoding:theConnectionEncoding] ; - NSString *extra = [self stringWithCString:row[6] usingEncoding:theConnectionEncoding] ; - NSString *priv = [self stringWithCString:row[7] usingEncoding:theConnectionEncoding] ; - NSString *comment = [self stringWithCString:row[8] usingEncoding:theConnectionEncoding] ; - NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; - NSArray *a = [coll componentsSeparatedByString:@"_"]; - charset = ([a count]) ? [a objectAtIndex:0] : @""; + // Extract the name + NSString *aTableName = [aTableDict objectForKey:@"name"]; - [queriedStructureKeys addObject:field_id]; - - if(![queriedStructure valueForKey:db_id] || [[queriedStructure valueForKey:db_id] isKindOfClass:[NSString class]] ) - [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; - - if(![[queriedStructure valueForKey:db_id] valueForKey:table_id]) - [[queriedStructure valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; - - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:[NSArray arrayWithObjects:type, def, isnull, charset, coll, key, extra, priv, comment, type_display, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:@"0" forKey:@" struct_type "]; - uniqueCounter++; - } - mysql_free_result(theResult); - usleep(10); - } - // Query all views - for(NSString* table in tableviews) { - if(cancelQueryingDbStructure) { - [self performSelectorOnMainThread:@selector(decrementQueryingDbStructure) withObject:nil waitUntilDone:YES]; - [queryPool release]; - return; - } + // Retrieve the column details NSString *query = [NSString stringWithFormat:@"SHOW FULL COLUMNS FROM `%@` FROM `%@`", - [table stringByReplacingOccurrencesOfString:@"`" withString:@"``"], + [aTableName stringByReplacingOccurrencesOfString:@"`" withString:@"``"], currentDatabaseEscaped]; NSData *encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); const char *queryCString = [encodedQueryData bytes]; unsigned long queryCStringLength = [encodedQueryData length]; if (mysql_real_query(structConnection, queryCString, queryCStringLength) != 0) { - // NSLog(@"error %@", table); + // NSLog(@"error %@", aTableName); continue; } - theResult = mysql_use_result(structConnection); - NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, table]; + // Add a structure key for this table + NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, aTableName]; [queriedStructureKeys addObject:table_id]; - NSString *charset; - while(row = mysql_fetch_row(theResult)) { + // Add a mutable dictionary to the structure and store a reference + [databaseStructure setObject:[NSMutableDictionary dictionary] forKey:table_id]; + NSMutableDictionary *tableStructure = [databaseStructure objectForKey:table_id]; + + // Loop through the fields, extracting details for each + while (row = mysql_fetch_row(theResult)) { NSString *field = [self stringWithCString:row[0] usingEncoding:theConnectionEncoding] ; NSString *type = [self stringWithCString:row[1] usingEncoding:theConnectionEncoding] ; NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; @@ -2184,80 +2148,88 @@ void performThreadedKeepAlive(void *ptr) NSString *def = [self stringWithCString:row[5] usingEncoding:theConnectionEncoding] ; NSString *extra = [self stringWithCString:row[6] usingEncoding:theConnectionEncoding] ; NSString *priv = [self stringWithCString:row[7] usingEncoding:theConnectionEncoding] ; - NSString *comment = [self stringWithCString:row[8] usingEncoding:theConnectionEncoding] ; - NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; + NSString *comment; + if (sizeof(row) > 8) { + comment = [self stringWithCString:row[8] usingEncoding:theConnectionEncoding] ; + } else { + comment = @""; + } NSArray *a = [coll componentsSeparatedByString:@"_"]; charset = ([a count]) ? [a objectAtIndex:0] : @""; + // Add a structure key for this field + NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; [queriedStructureKeys addObject:field_id]; - - if(![queriedStructure valueForKey:db_id] || [[[queriedStructure valueForKey:connectionID] valueForKey:db_id] isKindOfClass:[NSString class]] ) - [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; - - if(![[queriedStructure valueForKey:db_id] valueForKey:table_id]) - [[queriedStructure valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; - - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:[NSArray arrayWithObjects:type, def, isnull, charset, coll, key, extra, priv, comment, type_display, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:@"1" forKey:@" struct_type "]; + + [tableStructure setObject:[NSArray arrayWithObjects:type, def, isnull, charset, coll, key, extra, priv, comment, type_display, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; + [tableStructure setObject:[aTableDict objectForKey:@"type"] forKey:@" struct_type "]; uniqueCounter++; } mysql_free_result(theResult); usleep(10); } + // If the MySQL version is higher than 5, also retrieve function/procedure details via the information_schema table if([self serverMajorVersion] >= 5) { - // information_schema is UTF-8 encoded + + // The information_schema table is UTF-8 encoded - alter the connection query = @"SET NAMES 'utf8'"; encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); queryCString = [encodedQueryData bytes]; queryCStringLength = [encodedQueryData length]; if (mysql_real_query(structConnection, queryCString, queryCStringLength) == 0) { + // Query for procedures and functions - query = [NSString stringWithFormat:@"SELECT * FROM `information_schema`.`ROUTINES` WHERE `information_schema`.`ROUTINES`.`ROUTINE_SCHEMA` = '%@'", [currentDatabase stringByReplacingOccurrencesOfString:@"'" withString:@"\'"]]; - encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); - queryCString = [encodedQueryData bytes]; - queryCStringLength = [encodedQueryData length]; - if (mysql_real_query(structConnection, queryCString, queryCStringLength) == 0) { - theResult = mysql_use_result(structConnection); - NSUInteger numberOfFields = mysql_num_fields(theResult); - while(row = mysql_fetch_row(theResult)) { + query = [NSString stringWithFormat:@"SELECT * FROM `information_schema`.`ROUTINES` WHERE `information_schema`.`ROUTINES`.`ROUTINE_SCHEMA` = '%@'", [currentDatabase stringByReplacingOccurrencesOfString:@"'" withString:@"\'"]]; + encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); + queryCString = [encodedQueryData bytes]; + queryCStringLength = [encodedQueryData length]; + if (mysql_real_query(structConnection, queryCString, queryCStringLength) == 0) { + theResult = mysql_use_result(structConnection); + NSUInteger numberOfFields = mysql_num_fields(theResult); + + // Loop through the rows and extract the function details + while(row = mysql_fetch_row(theResult)) { + + // If cancelled, abort without saving the new structure if(cancelQueryingDbStructure) { [self performSelectorOnMainThread:@selector(decrementQueryingDbStructure) withObject:nil waitUntilDone:YES]; [queryPool release]; return; } - NSString *field = [self stringWithUTF8CString:row[0]]; - NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, field]; - NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; - NSString *type = ([[self stringWithUTF8CString:row[4]] isEqualToString:@"FUNCTION"]) ? @"3" : @"2"; - NSString *dtd = [self stringWithUTF8CString:row[5]]; - NSString *det = [self stringWithUTF8CString:row[11]]; - NSString *access = [self stringWithUTF8CString:row[12]]; - NSString *security_type = [self stringWithUTF8CString:row[14]]; - NSString *definer = [self stringWithUTF8CString:row[19]]; - - [queriedStructureKeys addObject:table_id]; - [queriedStructureKeys addObject:field_id]; - - if(![queriedStructure valueForKey:db_id] || [[queriedStructure valueForKey:db_id] isKindOfClass:[NSString class]] ) - [queriedStructure setObject:[NSMutableDictionary dictionary] forKey:db_id]; - - if(![[queriedStructure valueForKey:db_id] valueForKey:table_id]) - [[queriedStructure valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; - - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject: - [NSArray arrayWithObjects:dtd, access, det, security_type, definer, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; - [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:type forKey:@" struct_type "]; - uniqueCounter++; - } - mysql_free_result(theResult); - } else { - NSLog(@"Error while querying the database structure for procedures and functions. Could not set encoding to utf8"); - } + + NSString *fname = [self stringWithUTF8CString:row[0]]; + NSString *type = ([[self stringWithUTF8CString:row[4]] isEqualToString:@"FUNCTION"]) ? @"3" : @"2"; + NSString *dtd = [self stringWithUTF8CString:row[5]]; + NSString *det = [self stringWithUTF8CString:row[11]]; + NSString *access = [self stringWithUTF8CString:row[12]]; + NSString *security_type = [self stringWithUTF8CString:row[14]]; + NSString *definer = [self stringWithUTF8CString:row[19]]; + + // Generate "table" and "field" names and add to structure key store + NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, fname]; + NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, fname]; + [queriedStructureKeys addObject:table_id]; + [queriedStructureKeys addObject:field_id]; + + // Ensure that a dictionary exists for this "table" name + if(![[queriedStructure valueForKey:db_id] valueForKey:table_id]) + [[queriedStructure valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; + + // Add the "field" details + [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject: + [NSArray arrayWithObjects:dtd, access, det, security_type, definer, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; + [[[queriedStructure valueForKey:db_id] valueForKey:table_id] setObject:type forKey:@" struct_type "]; + uniqueCounter++; + } + mysql_free_result(theResult); + } else { + NSLog(@"Error while querying the database structure for procedures and functions. Could not set encoding to utf8"); + } } } - // Updating the global variables and make sure that no request reads these global variables + // Update the global variables and make sure that no request reads these global variables // while updating [self performSelectorOnMainThread:@selector(lockQuerying) withObject:nil waitUntilDone:YES]; [self performSelectorOnMainThread:@selector(updateGlobalVariablesWith:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:queriedStructure, @"structure", queriedStructureKeys, @"keys", nil] waitUntilDone:YES]; @@ -2282,7 +2254,10 @@ void performThreadedKeepAlive(void *ptr) - (void)updateGlobalVariablesWith:(NSDictionary*)object { NSString *connectionID = [[self delegate] connectionID]; + + // Return if the delegate indicates disconnection if([connectionID length] < 2) return; + if(![structure valueForKey:connectionID]) [structure setObject:[NSMutableDictionary dictionary] forKey:connectionID]; [structure setObject:[object objectForKey:@"structure"] forKey:connectionID]; @@ -2420,14 +2395,16 @@ void performThreadedKeepAlive(void *ptr) NSArray *possibleSocketLocations = [NSArray arrayWithObjects: @"/tmp/mysql.sock", // Default + @"/Applications/MAMP/tmp/mysql/mysql.sock", // MAMP default location + @"/Applications/xampp/xamppfiles/var/mysql/mysql.sock", // XAMPP default location + @"/var/mysql/mysql.sock", // Mac OS X Server default + @"/opt/local/var/run/mysqld/mysqld.sock", // Darwinports MySQL + @"/opt/local/var/run/mysql4/mysqld.sock", // Darwinports MySQL 4 + @"/opt/local/var/run/mysql5/mysqld.sock", // Darwinports MySQL 5 @"/var/run/mysqld/mysqld.sock", // As used on Debian/Gentoo @"/var/tmp/mysql.sock", // As used on FreeBSD @"/var/lib/mysql/mysql.sock", // As used by Fedora @"/opt/local/lib/mysql/mysql.sock", // Alternate fedora - @"/opt/local/var/run/mysqld/mysqld.sock", // Darwinports MySQL - @"/opt/local/var/run/mysql4/mysqld.sock", // Darwinports MySQL 4 - @"/opt/local/var/run/mysql5/mysqld.sock", // Darwinports MySQL 5 - @"/Applications/MAMP/tmp/mysql/mysql.sock", // MAMP default location nil]; for (NSInteger i = 0; i < [possibleSocketLocations count]; i++) @@ -2583,8 +2560,13 @@ void performThreadedKeepAlive(void *ptr) MCPResult *r; r = [self queryString:@"SELECT @@global.max_allowed_packet" usingEncoding:mEncoding streamingResult:NO]; if (![[self getLastErrorMessage] isEqualToString:@""]) { - if ([self isConnected]) - NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"An error occured while retrieving max_allowed_packet size:\n\n%@", [self getLastErrorMessage]], @"OK", nil, nil); + if ([self isConnected]) { + NSString *errorMessage = [NSString stringWithFormat:@"An error occured while retrieving max_allowed_packet size:\n\n%@", [self getLastErrorMessage]]; + if ([delegate respondsToSelector:@selector(showErrorWithTitle:message:)]) + [delegate showErrorWithTitle:NSLocalizedString(@"Error", @"error") message:errorMessage]; + else + NSRunAlertPanel(@"Error", errorMessage, @"OK", nil, nil); + } return -1; } NSArray *a = [r fetchRowAsArray]; diff --git a/Frameworks/MCPKit/Support files/NSNotificationAdditions.h b/Frameworks/MCPKit/Support files/NSNotificationAdditions.h new file mode 100644 index 00000000..33c8ec10 --- /dev/null +++ b/Frameworks/MCPKit/Support files/NSNotificationAdditions.h @@ -0,0 +1,35 @@ +// +// $Id: NSNotificationAdditions.h 2045 2010-03-31 18:01:50Z stuart02 $ +// +// NSNotificationAdditions.h +// sequel-pro +// +// Copied from the Colloquy project; original code available from Trac at +// http://colloquy.info/project/browser/trunk/Additions/NSNotificationAdditions.h +// +// 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/> + +@interface NSNotificationCenter (NSNotificationCenterAdditions) + +- (void) postNotificationOnMainThread:(NSNotification *) notification; +- (void) postNotificationOnMainThread:(NSNotification *) notification waitUntilDone:(BOOL) wait; + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object; +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo; +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo waitUntilDone:(BOOL) wait; + +@end diff --git a/Frameworks/MCPKit/Support files/NSNotificationAdditions.m b/Frameworks/MCPKit/Support files/NSNotificationAdditions.m new file mode 100644 index 00000000..f27a4d5c --- /dev/null +++ b/Frameworks/MCPKit/Support files/NSNotificationAdditions.m @@ -0,0 +1,76 @@ +// +// $Id: NSNotificationAdditions.m 2045 2010-03-31 18:01:50Z stuart02 $ +// +// NSNotificationAdditions.m +// sequel-pro +// +// Copied from the Colloquy project; original code available from Trac at +// http://colloquy.info/project/browser/trunk/Additions/NSNotificationAdditions.m +// +// 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 "NSNotificationAdditions.h" +#import <pthread.h> + +@implementation NSNotificationCenter (NSNotificationCenterAdditions) + +- (void) postNotificationOnMainThread:(NSNotification *) notification { + if( pthread_main_np() ) return [self postNotification:notification]; + [self postNotificationOnMainThread:notification waitUntilDone:NO]; +} + +- (void) postNotificationOnMainThread:(NSNotification *) notification waitUntilDone:(BOOL) wait { + if( pthread_main_np() ) return [self postNotification:notification]; + [[self class] performSelectorOnMainThread:@selector( _postNotification: ) withObject:notification waitUntilDone:wait]; +} + ++ (void) _postNotification:(NSNotification *) notification { + [[self defaultCenter] postNotification:notification]; +} + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object { + if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:nil]; + [self postNotificationOnMainThreadWithName:name object:object userInfo:nil waitUntilDone:NO]; +} + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo { + if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:userInfo]; + [self postNotificationOnMainThreadWithName:name object:object userInfo:userInfo waitUntilDone:NO]; +} + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo waitUntilDone:(BOOL) wait { + if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:userInfo]; + + NSMutableDictionary *info = [[NSMutableDictionary allocWithZone:nil] initWithCapacity:3]; + if( name ) [info setObject:name forKey:@"name"]; + if( object ) [info setObject:object forKey:@"object"]; + if( userInfo ) [info setObject:userInfo forKey:@"userInfo"]; + + [[self class] performSelectorOnMainThread:@selector( _postNotificationName: ) withObject:info waitUntilDone:wait]; + + [info release]; +} + ++ (void) _postNotificationName:(NSDictionary *) info { + NSString *name = [info objectForKey:@"name"]; + id object = [info objectForKey:@"object"]; + NSDictionary *userInfo = [info objectForKey:@"userInfo"]; + + [[self defaultCenter] postNotificationName:name object:object userInfo:userInfo]; +} + +@end |