Skip to content

Commit

Permalink
Merge pull request #808 from gduh/FK_otherDB
Browse files Browse the repository at this point in the history
Allow creating foreign keys referencing other databases #added
  • Loading branch information
Jason-Morcos committed Jan 24, 2021
2 parents 3083682 + 8dd1887 commit 85d0d6b
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 167 deletions.
172 changes: 88 additions & 84 deletions Interfaces/DBView.xib

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Source/Controllers/DataControllers/SPTableData.h
Expand Up @@ -73,7 +73,7 @@
- (void) resetStatusData;
- (void) resetColumnData;
- (BOOL) updateInformationForCurrentTable;
- (NSDictionary *) informationForTable:(NSString *)tableName;
- (NSDictionary *) informationForTable:(NSString *)tableName fromDatabase:(NSString *)database;
- (BOOL) updateInformationForCurrentView;
- (NSDictionary *) informationForView:(NSString *)viewName;
- (BOOL) updateStatusInformationForCurrentTable;
Expand Down
132 changes: 69 additions & 63 deletions Source/Controllers/DataControllers/SPTableData.m
Expand Up @@ -375,7 +375,7 @@ - (BOOL) updateInformationForCurrentTable
[primaryKeyColumns removeAllObjects];

if( [tableListInstance tableType] == SPTableTypeTable || [tableListInstance tableType] == SPTableTypeView ) {
tableData = [self informationForTable:[tableListInstance tableName]];
tableData = [self informationForTable:[tableListInstance tableName] fromDatabase:[tableListInstance selectedDatabase]];
}

// If nil is returned, return failure.
Expand Down Expand Up @@ -442,77 +442,83 @@ - (BOOL) updateInformationForCurrentView
/**
* Retrieve the CREATE statement for a table/view and return extracted table
* structure information.
* @param tableName tablename from current database or depending the second param if not nil.
* @param database database name owning the tablename, can be nil.
* @attention This method will interact with the UI on errors/connection loss!
*/
- (NSDictionary *) informationForTable:(NSString *)tableName
- (NSDictionary *) informationForTable:(NSString *)tableName fromDatabase:(NSString *)database
{
BOOL changeEncoding = ![[mySQLConnection encoding] hasPrefix:@"utf8"];

// Catch unselected tables and return nil
if ([tableName isEqualToString:@""] || !tableName) return nil;

// Ensure the encoding is set to UTF8
if (changeEncoding) {
[mySQLConnection storeEncodingForRestoration];
[mySQLConnection setEncoding:@"utf8mb4"];
}
BOOL changeEncoding = ![[mySQLConnection encoding] hasPrefix:@"utf8"];

// In cases where this method is called directly instead of via -updateInformationForCurrentTable
// (for example, from the exporters) clear the list of constraints to prevent the previous call's table
// constraints being included in the table information (issue 1206).
[constraints removeAllObjects];
// Catch unselected tables and return nil
if ([tableName isEqualToString:@""] || !tableName) return nil;

// Retrieve the CREATE TABLE syntax for the table
SPMySQLResult *theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]];
[theResult setReturnDataAsStrings:YES];

// Check for any errors, but only display them if a connection still exists
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
NSString *errorMessage = [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"),
tableName, [mySQLConnection lastErrorMessage]];

// If the current table doesn't exist anymore reload table list
if ([mySQLConnection lastErrorID] == 1146) {

// Release the table loading lock to allow reselection/reloading to requery the database.
pthread_mutex_unlock(&dataProcessingLock);

[tableListInstance deselectAllTables];
[tableListInstance updateTables:self];
}
SPMainQSync(^{
[NSAlert createWarningAlertWithTitle:NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message") message:errorMessage callback:nil];
});
if (changeEncoding) [mySQLConnection restoreStoredEncoding];
}

return nil;
}
// Ensure the encoding is set to UTF8
if (changeEncoding) {
[mySQLConnection storeEncodingForRestoration];
[mySQLConnection setEncoding:@"utf8mb4"];
}

// Retrieve the table syntax string
NSArray *syntaxResult = [theResult getRowAsArray];
NSArray *resultFieldNames = [theResult fieldNames];
// In cases where this method is called directly instead of via -updateInformationForCurrentTable
// (for example, from the exporters) clear the list of constraints to prevent the previous call's table
// constraints being included in the table information (issue 1206).
[constraints removeAllObjects];

// Retrieve the CREATE TABLE syntax for the table
SPMySQLResult *theResult;
if (database)
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@.%@", [database backtickQuotedString], [tableName backtickQuotedString]]];
else
theResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]];
[theResult setReturnDataAsStrings:YES];

// Check for any errors, but only display them if a connection still exists
if ([mySQLConnection queryErrored]) {
if ([mySQLConnection isConnected]) {
NSString *errorMessage = [NSString stringWithFormat:NSLocalizedString(@"An error occurred while retrieving the information for table '%@'. Please try again.\n\nMySQL said: %@", @"error retrieving table information informative message"),
tableName, [mySQLConnection lastErrorMessage]];

// If the current table doesn't exist anymore reload table list
if ([mySQLConnection lastErrorID] == 1146) {

// Release the table loading lock to allow reselection/reloading to requery the database.
pthread_mutex_unlock(&dataProcessingLock);

[tableListInstance deselectAllTables];
[tableListInstance updateTables:self];
}
SPMainQSync(^{
[NSAlert createWarningAlertWithTitle:NSLocalizedString(@"Error retrieving table information", @"error retrieving table information message") message:errorMessage callback:nil];
});
if (changeEncoding) [mySQLConnection restoreStoredEncoding];
}

return nil;
}

// Only continue if syntaxResult is not nil. This accommodates causes where the above query caused the
// connection reconnect dialog to appear and the user chose to close the connection.
if (!syntaxResult) return nil;

// A NULL value indicates that the user does not have permission to view the syntax
if ([[syntaxResult safeObjectAtIndex:1] isNSNull] || [syntaxResult safeObjectAtIndex:1] == nil) {
[NSAlert createWarningAlertWithTitle:NSLocalizedString(@"Permission Denied", @"Permission Denied") message:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail") callback:nil];

if (changeEncoding) [mySQLConnection restoreStoredEncoding];
return nil;
}
// Retrieve the table syntax string
NSArray *syntaxResult = [theResult getRowAsArray];
NSArray *resultFieldNames = [theResult fieldNames];

// Only continue if syntaxResult is not nil. This accommodates causes where the above query caused the
// connection reconnect dialog to appear and the user chose to close the connection.
if (!syntaxResult) return nil;

// A NULL value indicates that the user does not have permission to view the syntax
if ([[syntaxResult safeObjectAtIndex:1] isNSNull] || [syntaxResult safeObjectAtIndex:1] == nil) {
[NSAlert createWarningAlertWithTitle:NSLocalizedString(@"Permission Denied", @"Permission Denied") message:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail") callback:nil];

if (changeEncoding) [mySQLConnection restoreStoredEncoding];
return nil;
}

tableCreateSyntax = [[NSString alloc] initWithString:[syntaxResult objectAtIndex:1]];
NSDictionary *tableData = [self parseCreateStatement:tableCreateSyntax ofType:[resultFieldNames objectAtIndex:0]];
if (changeEncoding) [mySQLConnection restoreStoredEncoding];
tableCreateSyntax = [[NSString alloc] initWithString:[syntaxResult objectAtIndex:1]];
NSDictionary *tableData = [self parseCreateStatement:tableCreateSyntax ofType:[resultFieldNames objectAtIndex:0]];
if (changeEncoding) [mySQLConnection restoreStoredEncoding];

return tableData;
return tableData;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion Source/Controllers/DataExport/Exporters/SPCSVExporter.m
Expand Up @@ -134,7 +134,7 @@ - (void)exportOperation
if ([queryResult numberOfRows]) {
id object = [[queryResult getRowAsDictionary] objectForKey:@"Create View"];

tableDetails = [[NSDictionary alloc] initWithDictionary:(object) ? [[self csvTableData] informationForView:[self csvTableName]] : [[self csvTableData] informationForTable:[self csvTableName]]];
tableDetails = [[NSDictionary alloc] initWithDictionary:(object) ? [[self csvTableData] informationForView:[self csvTableName]] : [[self csvTableData] informationForTable:[self csvTableName] fromDatabase:nil]];
}

// Retrieve the table details via the data class, and use it to build an array containing column numeric status
Expand Down
2 changes: 1 addition & 1 deletion Source/Controllers/DataExport/Exporters/SPDotExporter.m
Expand Up @@ -127,7 +127,7 @@ - (void)exportOperation

NSString *tableName = [[self dotExportTables] safeObjectAtIndex:i];
NSString *tableLinkName = [self dotForceLowerTableNames] ? [tableName lowercaseString] : tableName;
NSDictionary *tableInfo = [[self dotTableData] informationForTable:tableName];
NSDictionary *tableInfo = [[self dotTableData] informationForTable:tableName fromDatabase:nil];

// Set the current table
[self setDotExportCurrentTable:tableName];
Expand Down
2 changes: 1 addition & 1 deletion Source/Controllers/DataExport/Exporters/SPSQLExporter.m
Expand Up @@ -344,7 +344,7 @@ - (void)exportOperation
// Add the table content if required
if (sqlOutputIncludeContent && (tableType == SPTableTypeTable)) {
// Retrieve the table details via the data class, and use it to build an array containing column numeric status
NSDictionary *tableDetails = [NSDictionary dictionaryWithDictionary:[sqlTableDataInstance informationForTable:tableName]];
NSDictionary *tableDetails = [NSDictionary dictionaryWithDictionary:[sqlTableDataInstance informationForTable:tableName fromDatabase:nil]];

NSUInteger colCount = [[tableDetails objectForKey:@"columns"] count];
NSMutableArray *rawColumnNames = [NSMutableArray arrayWithCapacity:colCount];
Expand Down
2 changes: 1 addition & 1 deletion Source/Controllers/DataImport/SPDataImport.m
Expand Up @@ -1308,7 +1308,7 @@ - (BOOL) buildFieldMappingArrayWithData:(NSArray *)importData isPreview:(BOOL)da
// Store target table definitions
SPTableData *selectedTableData = [[SPTableData alloc] init];
[selectedTableData setConnection:mySQLConnection];
NSDictionary *targetTableDetails = [selectedTableData informationForTable:selectedTableTarget];
NSDictionary *targetTableDetails = [selectedTableData informationForTable:selectedTableTarget fromDatabase:nil];

// Store all field names which are of typegrouping 'geometry' and 'bit', and check if
// numeric columns can hold NULL values to map empty strings to.
Expand Down
2 changes: 1 addition & 1 deletion Source/Controllers/DataImport/SPFieldMapperController.m
Expand Up @@ -524,7 +524,7 @@ - (IBAction)changeTableTarget:(id)sender
// Retrieve the information for the newly selected table using a SPTableData instance
SPTableData *selectedTableData = [[SPTableData alloc] init];
[selectedTableData setConnection:mySQLConnection];
NSDictionary *tableDetails = [selectedTableData informationForTable:[tableTargetPopup titleOfSelectedItem]];
NSDictionary *tableDetails = [selectedTableData informationForTable:[tableTargetPopup titleOfSelectedItem] fromDatabase:nil];
targetTableHasPrimaryKey = NO;
BOOL isReplacePossible = NO;

Expand Down
Expand Up @@ -51,7 +51,8 @@
IBOutlet NSTextField *constraintName;
IBOutlet NSBox *addRelationTableBox;
IBOutlet NSPopUpButton *columnPopUpButton;
IBOutlet NSPopUpButton *refTablePopUpButton;
IBOutlet NSPopUpButton *refDatabasePopUpButton;
IBOutlet NSPopUpButton *refTablePopUpButton;
IBOutlet NSPopUpButton *refColumnPopUpButton;
IBOutlet NSPopUpButton *onUpdatePopUpButton;
IBOutlet NSPopUpButton *onDeletePopUpButton;
Expand Down Expand Up @@ -79,6 +80,7 @@
- (IBAction)closeRelationSheet:(id)sender;
- (IBAction)confirmAddRelation:(id)sender;
- (IBAction)selectTableColumn:(id)sender;
- (IBAction)selectReferenceDatabase:(id)sender;
- (IBAction)selectReferenceTable:(id)sender;
- (IBAction)refreshRelations:(id)sender;

Expand Down
Expand Up @@ -140,6 +140,7 @@ - (IBAction)confirmAddRelation:(id)sender

NSString *thisTable = [tablesListInstance tableName];
NSString *thisColumn = [columnPopUpButton titleOfSelectedItem];
NSString *thatDatabase = [refDatabasePopUpButton titleOfSelectedItem];
NSString *thatTable = [refTablePopUpButton titleOfSelectedItem];
NSString *thatColumn = [refColumnPopUpButton titleOfSelectedItem];

Expand All @@ -150,11 +151,12 @@ - (IBAction)confirmAddRelation:(id)sender
query = [query stringByAppendingString:[NSString stringWithFormat:@"CONSTRAINT %@ ", [[constraintName stringValue] backtickQuotedString]]];
}

query = [query stringByAppendingString:[NSString stringWithFormat:@"FOREIGN KEY (%@) REFERENCES %@ (%@)",
[thisColumn backtickQuotedString],
[thatTable backtickQuotedString],
[thatColumn backtickQuotedString]]];

query = [query stringByAppendingString:[NSString stringWithFormat:@"FOREIGN KEY (%@) REFERENCES %@.%@ (%@)",
[thisColumn backtickQuotedString],
[thatDatabase backtickQuotedString],
[thatTable backtickQuotedString],
[thatColumn backtickQuotedString]]];

NSArray *onActions = @[@"RESTRICT", @"CASCADE", @"SET NULL", @"NO ACTION"];

// If required add ON DELETE
Expand Down Expand Up @@ -221,6 +223,16 @@ - (IBAction)selectTableColumn:(id)sender
[self _updateAvailableTableColumns];
}

/**
* Updates the available tables and columns when the user selects a database.
*/
- (IBAction)selectReferenceDatabase:(id)sender {
// Update available tables...
[self _updateAvailableTables];
// ...then update available table columns
[self _updateAvailableTableColumns];
}

/**
* Updates the available columns when the user selects a table.
*/
Expand All @@ -244,6 +256,7 @@ - (IBAction)addRelation:(id)sender
NSArray *columnTitles = ([prefs boolForKey:SPAlphabeticalTableSorting])? [[tableDataInstance columnNames] sortedArrayUsingSelector:@selector(compare:)] : [tableDataInstance columnNames];
[columnPopUpButton addItemsWithTitles:columnTitles];

[refDatabasePopUpButton removeAllItems];
[refTablePopUpButton removeAllItems];

BOOL changeEncoding = ![[connection encoding] hasPrefix:@"utf8"];
Expand All @@ -254,13 +267,13 @@ - (IBAction)addRelation:(id)sender
[connection setEncoding:@"utf8mb4"];
}

// Set all databases exxcept system database
[refDatabasePopUpButton addItemsWithTitles:[tableDocumentInstance allDatabaseNames]];
// Set selected item to the current database
[refDatabasePopUpButton selectItemWithTitle:[tableDocumentInstance database]];

// Get all InnoDB tables in the current database
SPMySQLResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = %@ ORDER BY table_name ASC", [[tableDocumentInstance database] tickQuotedString]]];
[result setDefaultRowReturnType:SPMySQLResultRowAsArray];
[result setReturnDataAsStrings:YES]; // TODO: Workaround for #2699/#2700
for (NSArray *eachRow in result) {
[refTablePopUpButton addItemWithTitle:[eachRow objectAtIndex:0]];
}
[self _updateAvailableTables];

// Reset other fields
[constraintName setStringValue:@""];
Expand Down Expand Up @@ -564,15 +577,47 @@ - (void)_refreshRelationDataForcingCacheRefresh:(BOOL)clearAllCaches
[relationsTableView reloadData];
}


/**
* Updates the available table columns that the reference is pointing to. Available columns are those that are
* within the selected table and are of the same data type as the column the reference is from.
*/
- (void)_updateAvailableTables
{
// Get selected database
NSString *database = [refDatabasePopUpButton titleOfSelectedItem];

[refTablePopUpButton setEnabled:NO];
[refColumnPopUpButton setEnabled:NO];
[confirmAddRelationButton setEnabled:NO];

// Get all InnoDB tables in the current database
[refTablePopUpButton removeAllItems];
if (database != nil && database.length != 0) {
SPMySQLResult *result = [connection queryString:[NSString stringWithFormat:@"SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine = 'InnoDB' AND table_schema = %@ ORDER BY table_name ASC", [database tickQuotedString]]];
[result setDefaultRowReturnType:SPMySQLResultRowAsArray];
[result setReturnDataAsStrings:YES]; // TODO: Workaround for #2699/#2700
for (NSArray *eachRow in result) {
[refTablePopUpButton addItemWithTitle:[eachRow objectAtIndex:0]];
}
}

[refTablePopUpButton setEnabled:YES];
[refColumnPopUpButton setEnabled:YES];
[confirmAddRelationButton setEnabled:YES];

}

/**
* Updates the available table columns that the reference is pointing to. Available columns are those that are
* within the selected database and table and are of the same data type as the column the reference is from.
*/
- (void)_updateAvailableTableColumns
{
NSString *column = [columnPopUpButton titleOfSelectedItem];
NSString *table = [refTablePopUpButton titleOfSelectedItem];

NSString *database = [refDatabasePopUpButton titleOfSelectedItem];

[tableDataInstance resetAllData];
[tableDataInstance updateInformationForCurrentTable];

Expand All @@ -584,7 +629,7 @@ - (void)_updateAvailableTableColumns
[refColumnPopUpButton removeAllItems];

[tableDataInstance resetAllData];
NSDictionary *tableInfo = [tableDataInstance informationForTable:table];
NSDictionary *tableInfo = [tableDataInstance informationForTable:table fromDatabase:database];

NSArray *columns = [tableInfo objectForKey:@"columns"];

Expand Down

0 comments on commit 85d0d6b

Please sign in to comment.