diff options
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h | 9 | ||||
-rw-r--r-- | Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m | 668 | ||||
-rw-r--r-- | Source/CMTextView.m | 53 | ||||
-rw-r--r-- | Source/SPNarrowDownCompletion.h | 14 | ||||
-rw-r--r-- | Source/SPNarrowDownCompletion.m | 118 | ||||
-rw-r--r-- | Source/SPNavigatorController.h | 4 | ||||
-rw-r--r-- | Source/SPNavigatorController.m | 64 | ||||
-rw-r--r-- | Source/TableDocument.m | 5 |
8 files changed, 558 insertions, 377 deletions
diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h index 05078a60..1945b3de 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.h @@ -173,7 +173,8 @@ BOOL delegateQueryLogging; BOOL delegateResponseToWillQueryString; BOOL delegateSupportsConnectionLostDecisions; - BOOL isQueryingDbStructure; + NSInteger isQueryingDbStructure; + BOOL lockQuerying; // Pointers IMP cStringPtr; @@ -286,6 +287,12 @@ void performThreadedKeepAlive(void *ptr); - (void)queryDbStructureWithUserInfo:(NSDictionary*)userInfo; - (NSDictionary *)getDbStructure; - (NSArray *)getAllKeysOfDbStructure; +- (BOOL)isQueryingDatabaseStructure; +- (void)incrementQueryingDbStructure; +- (void)decrementQueryingDbStructure; +- (void)lockQuerying; +- (void)unlockQuerying; +- (void)updateGlobalVariablesWith:(NSDictionary*)object; // Server information - (NSString *)clientInfo; diff --git a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m index fd9842aa..0bf5d713 100644 --- a/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m +++ b/Frameworks/MCPKit/MCPFoundationKit/MCPConnection.m @@ -118,7 +118,8 @@ static BOOL sTruncateLongFieldInLogs = YES; structure = [[NSMutableDictionary alloc] initWithCapacity:1]; allKeysofDbStructure = [[NSMutableArray alloc] initWithCapacity:20]; - isQueryingDbStructure = NO; + isQueryingDbStructure = 0; + lockQuerying = NO; connectionThreadId = 0; maxAllowedPacketSize = -1; @@ -1861,365 +1862,416 @@ void performThreadedKeepAlive(void *ptr) { NSAutoreleasePool *queryPool = [[NSAutoreleasePool alloc] init]; - if (!isQueryingDbStructure) { + // Requests are queued + while(isQueryingDbStructure > 0) { usleep(1000000); } + + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureIsUpdating" object:delegate]; + + NSString *SPUniqueSchemaDelimiter = @""; + + NSString *connectionID; + if([delegate respondsToSelector:@selector(connectionID)]) + connectionID = [NSString stringWithString:[[self delegate] connectionID]]; + else + connectionID = @"_"; + + // Re-init with already cached data from navigator controller + NSMutableDictionary *queriedStructure = [NSMutableDictionary dictionary]; + NSDictionary *dbstructure = [[[self delegate] getDbStructure] retain]; + [queriedStructure setDictionary:[NSMutableDictionary dictionaryWithDictionary:dbstructure]]; + + NSMutableArray *queriedStructureKeys = [NSMutableArray array]; + NSArray *dbStructureKeys = [[[self delegate] allSchemaKeys] retain]; + [queriedStructureKeys setArray:dbStructureKeys]; + if(dbstructure) [dbstructure release], dbstructure = nil; + if(dbStructureKeys) [dbStructureKeys release], dbStructureKeys = nil; + + BOOL removeAddFlag = NO; + + // 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) { + NSString *dbid = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, db]; + if(![queriedStructure objectForKey:dbid]) { + removeAddFlag = YES; + [queriedStructure setObject:db forKey:dbid]; + [queriedStructureKeys addObject:dbid]; + } + } -// NSLog(@"queryDbStructureWithUserInfo called with %@ and db %@", [userInfo description], [[[self delegate] database] description]); + // Remove deleted databases in structure and keys in allKeysofDbStructure + // Use a dict to avoid <NSCFDictionary> was mutated while being enumerated. while iterating via allKeys + NSArray *keys = [queriedStructure allKeys]; + for(id key in keys) { + NSString *db = [[key componentsSeparatedByString:SPUniqueSchemaDelimiter] objectAtIndex:1]; + if(![dbs containsObject:db]) { + removeAddFlag = YES; + [queriedStructure removeObjectForKey:key]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", key, SPUniqueSchemaDelimiter]]; + [queriedStructureKeys filterUsingPredicate:predicate]; + [queriedStructureKeys removeObject:key]; + } + } - isQueryingDbStructure = YES; + NSString *currentDatabase = nil; + if([delegate respondsToSelector:@selector(database)]) + currentDatabase = [[self delegate] database]; + + if(!currentDatabase || (currentDatabase && ![currentDatabase length])) { + + // 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 retain], @"structure", [queriedStructureKeys retain], @"keys", nil] waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; + if(removeAddFlag) + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + [queryPool release]; + return; + } - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureIsUpdating" object:delegate]; + NSString *db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; - NSString *SPUniqueSchemaDelimiter = @""; + // 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]]) { - NSString *connectionID; - if([delegate respondsToSelector:@selector(connectionID)]) - connectionID = [NSString stringWithString:[[self delegate] connectionID]]; - else - connectionID = @"_"; - - // Re-init with already cached data from navigator controller - NSDictionary *dbstructure = [[[self delegate] getDbStructure] retain]; - [structure removeAllObjects]; - [structure setObject:[NSMutableDictionary dictionaryWithDictionary:dbstructure] forKey:connectionID]; - NSArray *allStructureKeys = [[self delegate] allSchemaKeys]; - if(allStructureKeys && [allStructureKeys count]) { - if(allKeysofDbStructure) [allKeysofDbStructure release]; - allKeysofDbStructure = nil; - allKeysofDbStructure = [[NSMutableArray alloc] initWithCapacity:[allKeysofDbStructure count]]; - [allKeysofDbStructure setArray:allStructureKeys]; - } - if(dbstructure) [dbstructure release], dbstructure = nil; - - BOOL removeAddFlag = NO; - - // 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) { - NSString *dbid = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, db]; - if(![[structure valueForKey:connectionID] objectForKey:dbid]) { -// NSLog(@"added db %@", db); - removeAddFlag = YES; - [[structure valueForKey:connectionID] setObject:db forKey:dbid]; - [allKeysofDbStructure addObject:dbid]; - } + // 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 retain], @"structure", [queriedStructureKeys retain], @"keys", nil] waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; + if(removeAddFlag) + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + [queryPool release]; + return; } - - // Remove deleted databases in structure and keys in allKeysofDbStructure - // Use a dict to avoid <NSCFDictionary> was mutated while being enumerated. while iterating via allKeys - NSArray *keys = [[NSDictionary dictionaryWithDictionary:[structure valueForKey:connectionID]] allKeys]; - for(id key in keys) { - NSString *db = [[key componentsSeparatedByString:SPUniqueSchemaDelimiter] objectAtIndex:1]; - if(![dbs containsObject:db]) { -// NSLog(@"removed db %@", db); - removeAddFlag = YES; - if([[structure valueForKey:connectionID] isKindOfClass:[NSDictionary class]]) { - [[structure valueForKey:connectionID] removeObjectForKey:key]; - } - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", key, SPUniqueSchemaDelimiter]]; - [allKeysofDbStructure filterUsingPredicate:predicate]; - [allKeysofDbStructure removeObject:key]; - } + } + 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 retain], @"structure", [queriedStructureKeys retain], @"keys", nil] waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; + if(removeAddFlag) + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + [queryPool release]; + return; } + } - NSString *currentDatabase = nil; - if([delegate respondsToSelector:@selector(database)]) - currentDatabase = [[self delegate] database]; - - if(!currentDatabase || (currentDatabase && ![currentDatabase length])) { -// NSLog(@"no db - return"); - isQueryingDbStructure = NO; + 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 retain], @"structure", [queriedStructureKeys retain], @"keys", nil] waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; if(removeAddFlag) [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; [queryPool release]; return; } + } - NSString *db_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, currentDatabase]; + NSArray *tables = [[[self delegate] valueForKeyPath:@"tablesListInstance"] allTableNames]; + NSArray *tableviews = [[[self delegate] valueForKeyPath:@"tablesListInstance"] allViewNames]; - // mysql and information_schema's schema will never change thus query data only once - if([currentDatabase isEqualToString:@"mysql"]) { - if([[structure valueForKey:connectionID] objectForKey:db_id] && [[[structure valueForKey:connectionID] objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { -// NSLog(@"mysql was parsed - return"); - if(removeAddFlag) - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - [queryPool release]; - return; - } - } - if([currentDatabase isEqualToString:@"information_schema"]) { - if([[structure valueForKey:connectionID] objectForKey:db_id] && [[[structure valueForKey:connectionID] objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { -// NSLog(@"information_schema was parsed - return"); - if(removeAddFlag) - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - [queryPool release]; - return; - } - } + NSUInteger numberOfTables = 0; + if(tables && [tables count]) numberOfTables += [tables count]; + if(tableviews && [tableviews count]) numberOfTables += [tableviews count]; - if(userInfo == nil || ![userInfo objectForKey:@"forceUpdate"]) { - if([[structure valueForKey:connectionID] objectForKey:db_id] && [[[structure valueForKey:connectionID] objectForKey:db_id] isKindOfClass:[NSDictionary class]]) { -// NSLog(@"no forceUpdate - return"); - isQueryingDbStructure = NO; - if(removeAddFlag) - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; - [queryPool release]; - return; - } - } + // For future usage + NSString *affectedItem = nil; + NSInteger affectedItemType = -1; + if(userInfo && [userInfo objectForKey:@"affectedItem"]) { + affectedItem = [userInfo objectForKey:@"affectedItem"]; + if([userInfo objectForKey:@"affectedItemType"]) + affectedItemType = [[userInfo objectForKey:@"affectedItemType"] intValue]; + else + affectedItem = nil; + } - 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]; - - // For future usage - NSString *affectedItem = nil; - NSInteger affectedItemType = -1; - if(userInfo && [userInfo objectForKey:@"affectedItem"]) { - affectedItem = [userInfo objectForKey:@"affectedItem"]; - if([userInfo objectForKey:@"affectedItemType"]) - affectedItemType = [[userInfo objectForKey:@"affectedItemType"] intValue]; - else - affectedItem = nil; - } + // Delete all stored data for to be queried db + if([queriedStructure isKindOfClass:[NSDictionary class]]) + [queriedStructure removeObjectForKey:db_id]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", db_id, SPUniqueSchemaDelimiter]]; + [queriedStructureKeys filterUsingPredicate:predicate]; + [queriedStructureKeys removeObject:db_id]; - // Delete all stored data for to be queried db - if([[structure valueForKey:connectionID] isKindOfClass:[NSDictionary class]]) - [[structure valueForKey:connectionID] removeObjectForKey:db_id]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT SELF BEGINSWITH %@", [NSString stringWithFormat:@"%@%@", db_id, SPUniqueSchemaDelimiter]]; - [allKeysofDbStructure filterUsingPredicate:predicate]; - [allKeysofDbStructure removeObject:db_id]; - // Re-add currentDatabase in case that structure querying will fail - [[structure valueForKey:connectionID] setObject:currentDatabase forKey:db_id]; + // Re-add currentDatabase in case that structure querying will fail + [queriedStructure setObject:currentDatabase forKey:db_id]; - NSString *currentDatabaseEscaped = [currentDatabase stringByReplacingOccurrencesOfString:@"`" withString:@"``"]; + NSString *currentDatabaseEscaped = [currentDatabase stringByReplacingOccurrencesOfString:@"`" withString:@"``"]; - MYSQL *structConnection = mysql_init(NULL); - if (structConnection) { - const char *theLogin = [self cStringFromString:connectionLogin]; - const char *theHost; - const char *thePass = NULL; - const char *theSocket; - void *connectionSetupStatus; + MYSQL *structConnection = mysql_init(NULL); + if (structConnection) { + const char *theLogin = [self cStringFromString:connectionLogin]; + const char *theHost; + const char *thePass = NULL; + const char *theSocket; + void *connectionSetupStatus; - mysql_options(structConnection, MYSQL_OPT_CONNECT_TIMEOUT, (const void *)&connectionTimeout); + mysql_options(structConnection, 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]; - } - 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]]; - } - } else { - thePass = [self cStringFromString:connectionPassword]; + // 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]]; } + } else { + thePass = [self cStringFromString:connectionPassword]; + } - // Connect - connectionSetupStatus = mysql_real_connect(structConnection, theHost, theLogin, thePass, NULL, connectionPort, theSocket, mConnectionFlags); - thePass = NULL; - if (connectionSetupStatus) { - MYSQL_RES *theResult; - MYSQL_ROW row; + // Connect + connectionSetupStatus = mysql_real_connect(structConnection, theHost, theLogin, thePass, NULL, connectionPort, theSocket, mConnectionFlags); + thePass = NULL; + if (connectionSetupStatus) { + MYSQL_RES *theResult; + MYSQL_ROW row; - NSStringEncoding theConnectionEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(structConnection)]; - NSString *charset; + NSStringEncoding theConnectionEncoding = [MCPConnection encodingForMySQLEncoding:mysql_character_set_name(structConnection)]; + NSString *charset; - if(numberOfTables > 2000) { - NSLog(@"%ld items in database %@. Only 2000 items can be parsed. Stopped parsing.", numberOfTables, currentDatabase); - isQueryingDbStructure = NO; - [queryPool release]; - return; - } + if(numberOfTables > 2000) { + NSLog(@"%ld items in database %@. Only 2000 items can be parsed. Stopped parsing.", numberOfTables, currentDatabase); + [queryPool release]; + return; + } + + [self performSelectorOnMainThread:@selector(incrementQueryingDbStructure) withObject:nil waitUntilDone:YES]; -// NSLog(@"queryDbStructureWithUserInfo started"); + NSUInteger uniqueCounter = 0; // used to make field data unique - NSUInteger uniqueCounter = 0; // used to make field data unique + NSString *query = @"SET NAMES 'utf8'"; + NSData *encodedQueryData = NSStringDataUsingLossyEncoding(query, theConnectionEncoding, 1); + const char *queryCString = [encodedQueryData bytes]; + unsigned long queryCStringLength = [encodedQueryData length]; + if (mysql_real_query(structConnection, queryCString, queryCStringLength) != 0) { + ; + } - NSString *query = @"SET NAMES 'utf8'"; + [queriedStructureKeys addObject:db_id]; + + // Query all tables + for(NSString* table in tables) { + 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 stringWithUTF8CString:row[0]]; + NSString *type = [self stringWithUTF8CString:row[1]]; + NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; + NSString *coll = [self stringWithUTF8CString:row[2]]; + NSString *isnull = [self stringWithUTF8CString:row[3]]; + NSString *key = [self stringWithUTF8CString:row[4]]; + NSString *def = [self stringWithUTF8CString:row[5]]; + NSString *extra = [self stringWithUTF8CString:row[6]]; + NSString *priv = [self stringWithUTF8CString:row[7]]; + NSString *comment = [self stringWithUTF8CString:row[8]]; + NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; + NSArray *a = [coll componentsSeparatedByString:@"_"]; + charset = ([a count]) ? [a objectAtIndex:0] : @""; + + [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) { + 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; } - [allKeysofDbStructure addObject:db_id]; - - // Query all tables - for(NSString* table in tables) { - 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]; + theResult = mysql_use_result(structConnection); + NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, table]; - [allKeysofDbStructure addObject:table_id]; + [queriedStructureKeys addObject:table_id]; - while(row = mysql_fetch_row(theResult)) { - NSString *field = [self stringWithUTF8CString:row[0]]; - NSString *type = [self stringWithUTF8CString:row[1]]; - NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; - NSString *coll = [self stringWithUTF8CString:row[2]]; - NSString *isnull = [self stringWithUTF8CString:row[3]]; - NSString *key = [self stringWithUTF8CString:row[4]]; - NSString *def = [self stringWithUTF8CString:row[5]]; - NSString *extra = [self stringWithUTF8CString:row[6]]; - NSString *priv = [self stringWithUTF8CString:row[7]]; - NSString *comment = [self stringWithUTF8CString:row[8]]; - NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; - NSArray *a = [coll componentsSeparatedByString:@"_"]; - charset = ([a count]) ? [a objectAtIndex:0] : @""; - - [allKeysofDbStructure addObject:field_id]; - - if(![[structure valueForKey:connectionID] valueForKey:db_id] || [[[structure valueForKey:connectionID] valueForKey:db_id] isKindOfClass:[NSString class]] ) - [[structure valueForKey:connectionID] setObject:[NSMutableDictionary dictionary] forKey:db_id]; - - if(![[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id]) - [[[structure valueForKey:connectionID] valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; - - [[[[structure valueForKey:connectionID] 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]; - [[[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id] setObject:@"0" forKey:@" struct_type "]; - uniqueCounter++; - } - mysql_free_result(theResult); - usleep(10); + NSString *charset; + while(row = mysql_fetch_row(theResult)) { + NSString *field = [self stringWithUTF8CString:row[0]]; + NSString *type = [self stringWithUTF8CString:row[1]]; + NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; + NSString *coll = [self stringWithUTF8CString:row[2]]; + NSString *isnull = [self stringWithUTF8CString:row[3]]; + NSString *key = [self stringWithUTF8CString:row[4]]; + NSString *def = [self stringWithUTF8CString:row[5]]; + NSString *extra = [self stringWithUTF8CString:row[6]]; + NSString *priv = [self stringWithUTF8CString:row[7]]; + NSString *comment = [self stringWithUTF8CString:row[8]]; + NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; + NSArray *a = [coll componentsSeparatedByString:@"_"]; + charset = ([a count]) ? [a objectAtIndex:0] : @""; + + [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 "]; + uniqueCounter++; } - // Query all views - for(NSString* table in tableviews) { - 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; - } + mysql_free_result(theResult); + usleep(10); + } + if([self serverMajorVersion] >= 5) { + // 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); - NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, table]; - - [allKeysofDbStructure addObject:table_id]; - - NSString *charset; + NSUInteger numberOfFields = mysql_num_fields(theResult); while(row = mysql_fetch_row(theResult)) { NSString *field = [self stringWithUTF8CString:row[0]]; - NSString *type = [self stringWithUTF8CString:row[1]]; - NSString *type_display = [type stringByReplacingOccurrencesOfRegex:@"\\(.*?,.*?\\)" withString:@"(…)"]; - NSString *coll = [self stringWithUTF8CString:row[2]]; - NSString *isnull = [self stringWithUTF8CString:row[3]]; - NSString *key = [self stringWithUTF8CString:row[4]]; - NSString *def = [self stringWithUTF8CString:row[5]]; - NSString *extra = [self stringWithUTF8CString:row[6]]; - NSString *priv = [self stringWithUTF8CString:row[7]]; - NSString *comment = [self stringWithUTF8CString:row[8]]; + NSString *table_id = [NSString stringWithFormat:@"%@%@%@", db_id, SPUniqueSchemaDelimiter, field]; NSString *field_id = [NSString stringWithFormat:@"%@%@%@", table_id, SPUniqueSchemaDelimiter, field]; - NSArray *a = [coll componentsSeparatedByString:@"_"]; - charset = ([a count]) ? [a objectAtIndex:0] : @""; - - [allKeysofDbStructure addObject:field_id]; - - if(![[structure valueForKey:connectionID] valueForKey:db_id] || [[[structure valueForKey:connectionID] valueForKey:db_id] isKindOfClass:[NSString class]] ) - [[structure valueForKey:connectionID] setObject:[NSMutableDictionary dictionary] forKey:db_id]; - - if(![[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id]) - [[[structure valueForKey:connectionID] valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; - - [[[[structure valueForKey:connectionID] 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]; - [[[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id] setObject:@"1" forKey:@" struct_type "]; + 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); - usleep(10); } + } - if([self serverMajorVersion] >= 5) { - // 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)) { - 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]]; - - [allKeysofDbStructure addObject:table_id]; - [allKeysofDbStructure addObject:field_id]; - - if(![[structure valueForKey:connectionID] valueForKey:db_id] || [[[structure valueForKey:connectionID] valueForKey:db_id] isKindOfClass:[NSString class]] ) - [[structure valueForKey:connectionID] setObject:[NSMutableDictionary dictionary] forKey:db_id]; - - if(![[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id]) - [[[structure valueForKey:connectionID] valueForKey:db_id] setObject:[NSMutableDictionary dictionary] forKey:table_id]; - - [[[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id] setObject: - [NSArray arrayWithObjects:dtd, access, det, security_type, definer, [NSNumber numberWithUnsignedLongLong:uniqueCounter], nil] forKey:field_id]; - [[[[structure valueForKey:connectionID] valueForKey:db_id] valueForKey:table_id] setObject:type forKey:@" struct_type "]; - uniqueCounter++; - } - mysql_free_result(theResult); - } - } + // 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 retain], @"structure", [queriedStructureKeys retain], @"keys", nil] waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(unlockQuerying) withObject:nil waitUntilDone:YES]; -// NSLog(@"queryDbStructureWithUserInfo done"); - // Notify that the structure querying has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + mysql_close(structConnection); - mysql_close(structConnection); - } + // Notify that the structure querying has been performed + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; + + [self performSelectorOnMainThread:@selector(decrementQueryingDbStructure) withObject:nil waitUntilDone:YES]; } - } else { - // Notify that the structure querying has been performed - [[NSNotificationCenter defaultCenter] postNotificationName:@"SPDBStructureWasUpdated" object:delegate]; } - - isQueryingDbStructure = NO; + [queryPool release]; } +/* + * Update global variables on main thread to avoid accessing from different threads + */ +- (void)updateGlobalVariablesWith:(NSDictionary*)object +{ + NSString *connectionID = [[self delegate] connectionID]; + if([connectionID length] < 2) return; + if(![structure valueForKey:connectionID]) + [structure setObject:[NSMutableDictionary dictionary] forKey:connectionID]; + [structure setObject:[[object objectForKey:@"structure"] retain] forKey:connectionID]; + [allKeysofDbStructure setArray:[[object objectForKey:@"keys"] retain]]; + usleep(100); +} + +- (void)incrementQueryingDbStructure +{ + isQueryingDbStructure++; +} + +- (void)decrementQueryingDbStructure +{ + isQueryingDbStructure--; + if(isQueryingDbStructure < 0) isQueryingDbStructure = 0; +} + +- (BOOL)isQueryingDatabaseStructure +{ + return (isQueryingDbStructure > 0) ? YES : NO; +} + +- (void)lockQuerying +{ + lockQuerying = YES; +} + +- (void)unlockQuerying +{ + lockQuerying = NO; + usleep(50000); +} /** * Returns a dict containing the structure of all available databases */ - (NSDictionary *)getDbStructure { - return [[structure copy] autorelease]; + if(lockQuerying) return nil; + NSDictionary *d = [NSDictionary dictionaryWithDictionary:structure]; + return d; } /** @@ -2227,22 +2279,8 @@ void performThreadedKeepAlive(void *ptr) */ - (NSArray *)getAllKeysOfDbStructure { - NSArray *r = nil; - @try - { - r = [NSArray arrayWithArray:allKeysofDbStructure]; - } - @catch(id ae) - { - @try - { - r = [NSArray arrayWithArray:allKeysofDbStructure]; - } - @catch(id ae) - { - return nil; - } - } + if(lockQuerying) return nil; + NSArray *r = [NSArray arrayWithArray:allKeysofDbStructure]; return r; } diff --git a/Source/CMTextView.m b/Source/CMTextView.m index 2b9879b6..91be7257 100644 --- a/Source/CMTextView.m +++ b/Source/CMTextView.m @@ -309,13 +309,12 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) else connectionID = @"_"; - // NSDictionary *dbs = [NSDictionary dictionaryWithDictionary:[[mySQLConnection getDbStructure] objectForKey:connectionID]]; + // Try to get structure data NSDictionary *dbs = [NSDictionary dictionaryWithDictionary:[[SPNavigatorController sharedNavigatorController] dbStructureForConnection:connectionID]]; if(dbs != nil && [dbs isKindOfClass:[NSDictionary class]] && [dbs count]) { NSMutableArray *allDbs = [NSMutableArray array]; - @try { [allDbs addObjectsFromArray:[dbs allKeys]]; } - @catch(id ae) { ; } + [allDbs addObjectsFromArray:[dbs allKeys]]; NSSortDescriptor *desc = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES selector:@selector(localizedCompare:)]; NSMutableArray *sortedDbs = [NSMutableArray array]; @@ -520,11 +519,17 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) - (void) doAutoCompletion { + // Cancel autocompletion trigger + if([prefs boolForKey:SPCustomQueryAutoComplete]) + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doAutoCompletion) + object:nil]; + if(completionIsOpen) return; NSRange r = [self selectedRange]; - if(![[self delegate] isKindOfClass:[CustomQuery class]] || r.length || snippetControlCounter > -1) return; + if(![self delegate] || ![[self delegate] isKindOfClass:[CustomQuery class]] || r.length || snippetControlCounter > -1) return; if(r.location) { if([[[self textStorage] attribute:kQuote atIndex:r.location-1 effectiveRange:nil] isEqualToString:kQuoteValue]) @@ -539,14 +544,16 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) - (void) doCompletionByUsingSpellChecker:(BOOL)isDictMode fuzzyMode:(BOOL)fuzzySearch autoCompleteMode:(BOOL)autoCompleteMode { - if(![self isEditable] || (completionIsOpen && !completionWasReinvokedAutomatically)) return; - // Cancel autocompletion trigger if([prefs boolForKey:SPCustomQueryAutoComplete]) [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(doAutoCompletion) object:nil]; + if(![self isEditable] || (completionIsOpen && !completionWasReinvokedAutomatically)) { + return; + } + [self breakUndoCoalescing]; NSUInteger caretPos = NSMaxRange([self selectedRange]); @@ -728,6 +735,12 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) completionIsOpen = YES; + // Cancel autocompletion trigger again if user typed something in while parsing + if([prefs boolForKey:SPCustomQueryAutoComplete]) + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doAutoCompletion) + object:nil]; + SPNarrowDownCompletion* completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:[self suggestionsForSQLCompletionWith:currentWord dictMode:isDictMode browseMode:dbBrowseMode withTableName:tableName withDbName:dbName] alreadyTyped:filter staticPrefix:prefix @@ -746,7 +759,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) selectedDb:currentDb caretMovedLeft:caretMovedLeft autoComplete:autoCompleteMode - oneColumn:isDictMode]; + oneColumn:isDictMode + isQueryingDBStructure:[mySQLConnection isQueryingDatabaseStructure]]; completionParseRangeLocation = parseRange.location; @@ -1275,6 +1289,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) return; } + completionIsOpen = YES; + SPNarrowDownCompletion* completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions alreadyTyped:@"" staticPrefix:@"" @@ -1293,7 +1309,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) selectedDb:@"" caretMovedLeft:NO autoComplete:NO - oneColumn:NO]; + oneColumn:NO + isQueryingDBStructure:NO]; //Get the NSPoint of the first character of the current word NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(aRange.location,0) actualCharacterRange:NULL]; @@ -1303,7 +1320,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) // Adjust list location to be under the current word or insertion point pos.y -= [[self font] pointSize]*1.25; [completionPopUp setCaretPos:pos]; - completionIsOpen = YES; [completionPopUp orderFront:self]; } @@ -1438,7 +1454,9 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) NSMutableArray *possibleCompletions = [[[NSMutableArray alloc] initWithCapacity:[list count]] autorelease]; for(id w in list) [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"dummy-small", @"image", nil]]; - + + completionIsOpen = YES; + completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions alreadyTyped:@"" staticPrefix:@"" @@ -1457,7 +1475,8 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) selectedDb:@"" caretMovedLeft:NO autoComplete:NO - oneColumn:YES]; + oneColumn:YES + isQueryingDBStructure:NO]; //Get the NSPoint of the first character of the current word NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(r2.location,0) actualCharacterRange:NULL]; NSRect boundingRect = [[self layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[self textContainer]]; @@ -1466,7 +1485,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) // Adjust list location to be under the current word or insertion point pos.y -= [[self font] pointSize]*1.25; [completionPopUp setCaretPos:pos]; - completionIsOpen = YES; [completionPopUp orderFront:self]; } } @@ -3252,6 +3270,17 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) - (void) dealloc { + if([prefs boolForKey:SPCustomQueryUpdateAutoHelp]) + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(autoHelp) + object:nil]; + + if([prefs boolForKey:SPCustomQueryAutoComplete]) + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doAutoCompletion) + object:nil]; + + // Remove observers [[NSNotificationCenter defaultCenter] removeObserver:self]; [prefs removeObserver:self forKeyPath:SPCustomQueryEditorFont]; diff --git a/Source/SPNarrowDownCompletion.h b/Source/SPNarrowDownCompletion.h index e2aaed5e..fc8bd942 100644 --- a/Source/SPNarrowDownCompletion.h +++ b/Source/SPNarrowDownCompletion.h @@ -49,12 +49,19 @@ BOOL commaInsertionMode; BOOL autoCompletionMode; BOOL oneColumnMode; + BOOL isQueryingDatabaseStructure; NSInteger backtickMode; NSFont *tableFont; NSRange theCharRange; NSRange theParseRange; NSString *theDbName; + NSTimer *stateTimer; + NSArray *syncArrowImages; + NSInteger currentSyncImage; + + NSUInteger timeCounter; + id theView; NSInteger maxWindowWidth; @@ -71,10 +78,15 @@ charRange:(NSRange)initRange parseRange:(NSRange)parseRange inView:(id)aView dictMode:(BOOL)mode dbMode:(BOOL)theDbMode tabTriggerMode:(BOOL)tabTriggerMode fuzzySearch:(BOOL)fuzzySearch backtickMode:(NSInteger)theBackTickMode withDbName:(NSString*)dbName withTableName:(NSString*)tableName - selectedDb:(NSString*)selectedDb caretMovedLeft:(BOOL)caretMovedLeft autoComplete:(BOOL)autoComplete oneColumn:(BOOL)oneColumn; + selectedDb:(NSString*)selectedDb caretMovedLeft:(BOOL)caretMovedLeft autoComplete:(BOOL)autoComplete oneColumn:(BOOL)oneColumn + isQueryingDBStructure:(BOOL)isQueryingDBStructure; - (void)setCaretPos:(NSPoint)aPos; - (void)insert_text:(NSString* )aString; - (void)insertCommonPrefix; - (void)adjustWorkingRangeByDelta:(NSInteger)delta; +- (void)updateSyncArrowStatus; +- (void)reInvokeCompletion; + + @end diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index cf5abb49..287fe98d 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -39,6 +39,7 @@ #import "CMTextView.h" #import "SPConstants.h" + @interface NSTableView (MovingSelectedRow) - (BOOL)SP_NarrowDownCompletion_canHandleEvent:(NSEvent*)anEvent; @@ -113,17 +114,32 @@ caseSensitive = YES; filtered = nil; spaceCounter = 0; - + currentSyncImage = 0; prefs = [NSUserDefaults standardUserDefaults]; tableFont = [NSUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] dataForKey:SPCustomQueryEditorFont]]; [self setupInterface]; + + syncArrowImages = [[NSArray alloc] initWithObjects: + [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_01" ofType:@"tiff"]], + [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_02" ofType:@"tiff"]], + [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_03" ofType:@"tiff"]], + [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_04" ofType:@"tiff"]], + [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_05" ofType:@"tiff"]], + [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sync_arrows_06" ofType:@"tiff"]], + nil]; + } return self; } - (void)dealloc { + if(stateTimer != nil) { + [stateTimer invalidate]; + [stateTimer release]; + } + stateTimer = nil; [staticPrefix release]; [mutablePrefix release]; [textualInputCharacters release]; @@ -135,12 +151,45 @@ [super dealloc]; } +- (void)updateSyncArrowStatus +{ + currentSyncImage++; + timeCounter++; + if(currentSyncImage >= [syncArrowImages count]) currentSyncImage = 0; + if(timeCounter > 20) { + timeCounter = 0; + if(![[theView valueForKeyPath:@"mySQLConnection"] isQueryingDatabaseStructure]) { + isQueryingDatabaseStructure = NO; + if(stateTimer) { + [stateTimer invalidate]; + [stateTimer release]; + stateTimer = nil; + if(syncArrowImages) [syncArrowImages release]; + usleep(1000); + [self performSelectorOnMainThread:@selector(reInvokeCompletion) withObject:nil waitUntilDone:NO]; + return; + } + } + } + + [theTableView setNeedsDisplay:YES]; + +} + +- (void)reInvokeCompletion +{ + [theView setCompletionWasReinvokedAutomatically:YES]; + closeMe = YES; + [theView doCompletionByUsingSpellChecker:dictMode fuzzyMode:fuzzyMode autoCompleteMode:NO]; +} + - (id)initWithItems:(NSArray*)someSuggestions alreadyTyped:(NSString*)aUserString staticPrefix:(NSString*)aStaticPrefix additionalWordCharacters:(NSString*)someAdditionalWordCharacters caseSensitive:(BOOL)isCaseSensitive charRange:(NSRange)initRange parseRange:(NSRange)parseRange inView:(id)aView dictMode:(BOOL)mode dbMode:(BOOL)theDbMode tabTriggerMode:(BOOL)tabTriggerMode fuzzySearch:(BOOL)fuzzySearch backtickMode:(NSInteger)theBackTickMode withDbName:(NSString*)dbName withTableName:(NSString*)tableName selectedDb:(NSString*)selectedDb caretMovedLeft:(BOOL)caretMovedLeft autoComplete:(BOOL)autoComplete oneColumn:(BOOL)oneColumn + isQueryingDBStructure:(BOOL)isQueryingDBStructure { if(self = [self init]) { @@ -151,6 +200,10 @@ autoCompletionMode = autoComplete; oneColumnMode = oneColumn; + isQueryingDatabaseStructure = isQueryingDBStructure; + + if(isQueryingDatabaseStructure) + stateTimer = [[NSTimer scheduledTimerWithTimeInterval:0.07f target:self selector:@selector(updateSyncArrowStatus) userInfo:nil repeats:YES] retain]; fuzzyMode = fuzzySearch; if(fuzzyMode) @@ -258,8 +311,8 @@ [theTableView setDelegate:self]; NSTableColumn *column0 = [[[NSTableColumn alloc] initWithIdentifier:@"image"] autorelease]; - [column0 setDataCell:[[ImageAndTextCell new] autorelease]]; - [column0 setEditable:NO]; + [column0 setDataCell:[NSImageCell new]]; + // [column0 setEditable:NO]; [theTableView addTableColumn:column0]; [column0 setMinWidth:0]; [column0 setWidth:20]; @@ -301,7 +354,10 @@ - (NSString *)tableView:(NSTableView *)aTableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex mouseLocation:(NSPoint)mouseLocation { + if(isQueryingDatabaseStructure) rowIndex--; + if([[aTableColumn identifier] isEqualToString:@"image"]) { + if(rowIndex == -1) return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress"); if(!dictMode) { NSString *imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"]; if([imageName hasPrefix:@"dummy"]) @@ -321,8 +377,10 @@ } return @""; } else if([[aTableColumn identifier] isEqualToString:@"name"]) { + if(rowIndex == -1) return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress"); return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"]; } else if ([[aTableColumn identifier] isEqualToString:@"list"] || [[aTableColumn identifier] isEqualToString:@"type"]) { + if(rowIndex == -1) return NSLocalizedString(@"fetching database structure data in progress", @"fetching database structure data in progress"); if(dictMode) { return @""; } else { @@ -341,6 +399,7 @@ } } else if ([[aTableColumn identifier] isEqualToString:@"path"]) { + if(rowIndex == -1) return NSLocalizedString(@"fetching database structure in progress", @"fetching database structure in progress"); if(dictMode) { return @""; } else { @@ -360,26 +419,43 @@ return @""; } +- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +{ + if(rowIndex == 0 && isQueryingDatabaseStructure) + return NO; + + return YES; +} + + - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { NSImage* image = nil; NSString* imageName = nil; + if(isQueryingDatabaseStructure) rowIndex--; + if([[aTableColumn identifier] isEqualToString:@"image"]) { if(!dictMode) { - imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"]; - if(imageName) - image = [NSImage imageNamed:imageName]; - [[aTableColumn dataCell] setImage:image]; + if(rowIndex == -1) { + return [syncArrowImages objectAtIndex:currentSyncImage]; + } else { + imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"]; + if(imageName) + image = [NSImage imageNamed:imageName]; + return image; + } } return @""; } else if([[aTableColumn identifier] isEqualToString:@"name"]) { - // NSTextFieldCell *b = [[[NSTextFieldCell new] initTextCell:[[filtered objectAtIndex:rowIndex] objectForKey:@"display"]] autorelease]; + if(rowIndex == -1) return @"fetching structure…"; + [[aTableColumn dataCell] setFont:[NSFont systemFontOfSize:12]]; return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"]; } else if ([[aTableColumn identifier] isEqualToString:@"list"]) { + if(rowIndex == -1) return @""; if(dictMode) { return @""; } else { @@ -407,11 +483,10 @@ } } else if([[aTableColumn identifier] isEqualToString:@"type"]) { + if(rowIndex == -1) return @""; if(dictMode) { return @""; } else { - // [[aTableColumn dataCell] setTextColor:([aTableView selectedRow] == rowIndex)?[NSColor whiteColor]:[NSColor darkGrayColor]]; - // return ([[filtered objectAtIndex:rowIndex] objectForKey:@"type"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"type"] : @""; NSTokenFieldCell *b = [[[NSTokenFieldCell alloc] initTextCell:([[filtered objectAtIndex:rowIndex] objectForKey:@"type"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"type"] : @""] autorelease]; [b setEditable:NO]; [b setAlignment:NSRightTextAlignment]; @@ -421,6 +496,7 @@ } } else if ([[aTableColumn identifier] isEqualToString:@"path"]) { + if(rowIndex == -1) return @""; if(dictMode) { return @""; } else { @@ -461,7 +537,7 @@ if(autoCompletionMode) return; if(spaceCounter < 1) for(id w in filtered){ - if([[w objectForKey:@"match"] ?: [w objectForKey:@"display"] rangeOfString:@" "].length) { + if([[w objectForKey:@"match"] ?: [w objectForKey:@"display"] rangeOfString:@" "].length && ![w objectForKey:@"noCompletion"]) { [textualInputCharacters addCharactersInString:@" "]; break; } @@ -615,6 +691,9 @@ - (void)watchUserEvents { + + [theView setCompletionIsOpen:YES]; + closeMe = NO; while(!closeMe) { @@ -797,9 +876,22 @@ { if([theTableView selectedRow] == -1) return; - NSDictionary* selectedItem = [filtered objectAtIndex:[theTableView selectedRow]]; + NSDictionary* selectedItem; + NSInteger selectedRowNumber = [theTableView selectedRow]; + if(isQueryingDatabaseStructure) { + if(selectedRowNumber < 1) { + closeMe = YES; + return; + } + selectedItem = [filtered objectAtIndex:selectedRowNumber-1]; + } else { + selectedItem = [filtered objectAtIndex:selectedRowNumber]; + } - if([selectedItem objectForKey:@"noCompletion"]) return; + if([selectedItem objectForKey:@"noCompletion"]) { + closeMe = YES; + return; + } if(dictMode){ [self insert_text:[selectedItem objectForKey:@"match"] ?: [selectedItem objectForKey:@"display"]]; diff --git a/Source/SPNavigatorController.h b/Source/SPNavigatorController.h index 6c31b013..2833cd99 100644 --- a/Source/SPNavigatorController.h +++ b/Source/SPNavigatorController.h @@ -45,7 +45,7 @@ NSMutableDictionary *schemaDataFiltered; NSMutableDictionary *allSchemaKeys; NSMutableArray *infoArray; - NSMutableSet *updatingConnections; + NSMutableArray *updatingConnections; NSMutableDictionary *expandStatus1; NSMutableDictionary *expandStatus2; @@ -80,13 +80,13 @@ - (NSString*)tableInfoLabelForIndex:(NSInteger)index ofType:(NSInteger)type; - (void)updateNavigator:(NSNotification *)aNotification; -- (void)isUpdatingNavigator:(NSNotification *)aNotification; - (NSDictionary *)dbStructureForConnection:(NSString*)connectionID; - (NSArray *)allSchemaKeysForConnection:(NSString*)connectionID; - (NSArray *)getUniqueDbIdentifierFor:(NSString*)term andConnection:(NSString*)connectionID; - (BOOL)isUpdatingConnection:(NSString*)connectionID; +- (BOOL)isUpdating; - (void)restoreSelectedItems; - (void)setIgnoreUpdate:(BOOL)flag; diff --git a/Source/SPNavigatorController.m b/Source/SPNavigatorController.m index 439b4d5c..0bb723ba 100644 --- a/Source/SPNavigatorController.m +++ b/Source/SPNavigatorController.m @@ -70,7 +70,7 @@ static SPNavigatorController *sharedNavigatorController = nil; expandStatus1 = [[NSMutableDictionary alloc] init]; expandStatus2 = [[NSMutableDictionary alloc] init]; infoArray = [[NSMutableArray alloc] init]; - updatingConnections = [[NSMutableSet alloc] initWithCapacity:1]; + updatingConnections = [[NSMutableArray alloc] init]; selectedKey1 = @""; selectedKey2 = @""; ignoreUpdate = NO; @@ -143,9 +143,6 @@ static SPNavigatorController *sharedNavigatorController = nil; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateNavigator:) name:@"SPDBStructureWasUpdated" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(isUpdatingNavigator:) - name:@"SPDBStructureIsUpdating" object:nil]; - } - (NSString *)windowFrameAutosaveName @@ -349,12 +346,14 @@ static SPNavigatorController *sharedNavigatorController = nil; [schemaData removeObjectForKey:connectionID]; if(allSchemaKeys) [allSchemaKeys removeObjectForKey:connectionID]; - - [outlineSchema1 reloadData]; - [outlineSchema2 reloadData]; - [self restoreSelectedItems]; - if(isFiltered) - [self filterTree:self]; + + if([[self window] isVisible]) { + [outlineSchema1 reloadData]; + [outlineSchema2 reloadData]; + [self restoreSelectedItems]; + if(isFiltered) + [self filterTree:self]; + } } } @@ -418,15 +417,6 @@ static SPNavigatorController *sharedNavigatorController = nil; [self performSelectorOnMainThread:@selector(updateEntriesForConnection:) withObject:nil waitUntilDone:YES]; } -- (void)isUpdatingNavigator:(NSNotification *)aNotification -{ - // id object = [aNotification object]; - // - // if([object isKindOfClass:[TableDocument class]]) - // [updatingConnections addObject:[object connectionID]]; - -} - - (void)updateEntriesForConnection:(NSString*)connectionID { @@ -435,13 +425,16 @@ static SPNavigatorController *sharedNavigatorController = nil; return; } - [self saveSelectedItems]; + if([[self window] isVisible]) { + [self saveSelectedItems]; + [infoArray removeAllObjects]; + } - [infoArray removeAllObjects]; + id doc = nil; if ([[[NSDocumentController sharedDocumentController] documents] count]) { - id doc = [[NSDocumentController sharedDocumentController] currentDocument]; + doc = [[NSDocumentController sharedDocumentController] currentDocument]; id theConnection = [doc valueForKeyPath:@"mySQLConnection"]; if(!theConnection || ![theConnection isConnected]) return; @@ -449,7 +442,6 @@ static SPNavigatorController *sharedNavigatorController = nil; NSString *connectionName = [doc connectionID]; if(!connectionName || [connectionName isEqualToString:@"_"] || (connectionID && ![connectionName isEqualToString:connectionID]) ) { -// NSLog(@"navigator update skipped %@", connectionName); return; } @@ -481,21 +473,27 @@ static SPNavigatorController *sharedNavigatorController = nil; [allSchemaKeys setObject:[NSArray array] forKey:connectionName]; } - [outlineSchema1 reloadData]; - [outlineSchema2 reloadData]; + [updatingConnections removeObject:connectionName]; - [self restoreExpandStatus]; - [self restoreSelectedItems]; + if([[self window] isVisible]) { + [outlineSchema1 reloadData]; + [outlineSchema2 reloadData]; - [updatingConnections removeObject:connectionName]; + [self restoreExpandStatus]; + [self restoreSelectedItems]; + } } - [self syncButtonAction:self]; + if([[self window] isVisible]) + [self syncButtonAction:self]; if(isFiltered && [[self window] isVisible]) [self filterTree:self]; - + + [[NSNotificationCenter defaultCenter] postNotificationName:@"SPNavigatorStructureWasUpdated" object:doc]; + + } - (BOOL)schemaPathExistsForConnection:(NSString*)connectionID andDatabase:(NSString*)dbname @@ -575,6 +573,11 @@ static SPNavigatorController *sharedNavigatorController = nil; return ([updatingConnections containsObject:connectionID]) ? YES : NO; } +- (BOOL)isUpdating +{ + return ([updatingConnections count]) ? YES : NO; +} + #pragma mark - #pragma mark IBActions @@ -824,6 +827,7 @@ static SPNavigatorController *sharedNavigatorController = nil; return [item objectAtIndex:index]; } return nil; + } - (BOOL)outlineView:(id)outlineView isItemExpandable:(id)item diff --git a/Source/TableDocument.m b/Source/TableDocument.m index 4ef781e3..b2da29af 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -3690,9 +3690,8 @@ */ - (void)windowWillClose:(NSNotification *)aNotification { - if ([[[SPNavigatorController sharedNavigatorController] window] isVisible]) { - [[SPNavigatorController sharedNavigatorController] removeConnection:[self connectionID]]; - } + + [[SPNavigatorController sharedNavigatorController] removeConnection:[self connectionID]]; [mySQLConnection setDelegate:nil]; if (_isConnected) [self closeConnection]; |