Skip to content
Permalink
Browse files

Separate join in TDBConnection.GetTableForeignKeys into two separate …

…and faster queries. Closes #852
  • Loading branch information
ansgarbecker committed Jan 14, 2020
1 parent ca87fc0 commit 22b00d4316e9ce6be98c5ce326cfe24e54186ef8
Showing with 42 additions and 18 deletions.
  1. +42 −18 source/dbconnection.pas
@@ -361,6 +361,7 @@ TDBConnection = class(TComponent)
FKeepAliveTimer: TTimer;
FFavorites: TStringList;
FPrefetchResults: TDBQueryList;
FForeignKeyQueriesFailed: Boolean;
procedure SetActive(Value: Boolean); virtual; abstract;
procedure DoBeforeConnect; virtual;
procedure DoAfterConnect; virtual;
@@ -1683,6 +1684,7 @@ constructor TDBConnection.Create(AOwner: TComponent);
FCurrentUserHostCombination := '';
FKeepAliveTimer := TTimer.Create(Self);
FFavorites := TStringList.Create;
FForeignKeyQueriesFailed := False;
end;


@@ -4891,42 +4893,64 @@ function TSQLiteConnection.GetTableKeys(Table: TDBObject): TTableKeyList;

function TDBConnection.GetTableForeignKeys(Table: TDBObject): TForeignKeyList;
var
ForeignQuery: TDBQuery;
ForeignQuery, ColQuery: TDBQuery;
ForeignKey: TForeignKey;
begin
// Generic: query table foreign keys from IS.?
Result := TForeignKeyList.Create(True);
if FForeignKeyQueriesFailed then begin
Log(lcDebug, 'Avoid foreign key retrieval with queries which failed before');
Exit;
end;
try
ForeignQuery := GetResults('SELECT k.*, r.UPDATE_RULE, r.DELETE_RULE'+
' FROM information_schema.KEY_COLUMN_USAGE AS k, information_schema.REFERENTIAL_CONSTRAINTS AS r'+
// Combine two IS tables by hand, not by JOIN, as this is too slow. See #852
ForeignQuery := GetResults('SELECT *'+
' FROM information_schema.REFERENTIAL_CONSTRAINTS'+
' WHERE'+
' k.CONSTRAINT_SCHEMA='+EscapeString(Table.Database)+
' AND k.TABLE_NAME='+EscapeString(Table.Name)+
' AND k.REFERENCED_TABLE_NAME IS NOT NULL'+
' AND k.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA'+
' AND k.TABLE_NAME=r.TABLE_NAME'+
' AND k.CONSTRAINT_NAME=r.CONSTRAINT_NAME'
' CONSTRAINT_SCHEMA='+EscapeString(Table.Database)+
' AND TABLE_NAME='+EscapeString(Table.Name)+
' AND REFERENCED_TABLE_NAME IS NOT NULL'
);
ForeignKey := nil;
while not ForeignQuery.Eof do begin
if (not Assigned(ForeignKey)) or (ForeignKey.KeyName <> ForeignQuery.Col('CONSTRAINT_NAME')) then begin
ColQuery := GetResults('SELECT *'+
' FROM information_schema.KEY_COLUMN_USAGE'+
' WHERE'+
' CONSTRAINT_SCHEMA='+EscapeString(Table.Database)+
' AND TABLE_NAME='+EscapeString(Table.Name)+
' AND REFERENCED_TABLE_NAME IS NOT NULL'
);
try
while not ForeignQuery.Eof do begin
ForeignKey := TForeignKey.Create(Self);
Result.Add(ForeignKey);
ForeignKey.KeyName := ForeignQuery.Col('CONSTRAINT_NAME');
ForeignKey.OldKeyName := ForeignKey.KeyName;
ForeignKey.ReferenceTable := ForeignQuery.Col('REFERENCED_TABLE_SCHEMA') +
ForeignKey.ReferenceTable := ForeignQuery.Col('UNIQUE_CONSTRAINT_SCHEMA') +
'.' + ForeignQuery.Col('REFERENCED_TABLE_NAME');
ForeignKey.OnUpdate := ForeignQuery.Col('UPDATE_RULE');
ForeignKey.OnDelete := ForeignQuery.Col('DELETE_RULE');
while not ColQuery.Eof do begin
if ColQuery.Col('CONSTRAINT_NAME') = ForeignQuery.Col('CONSTRAINT_NAME') then begin
ForeignKey.Columns.Add(ColQuery.Col('COLUMN_NAME'));
ForeignKey.ForeignColumns.Add(ColQuery.Col('REFERENCED_COLUMN_NAME'));
end;
ColQuery.Next;
end;
ColQuery.First;
ForeignQuery.Next;
end;
ForeignKey.Columns.Add(ForeignQuery.Col('COLUMN_NAME'));
ForeignKey.ForeignColumns.Add(ForeignQuery.Col('REFERENCED_COLUMN_NAME'));
ForeignQuery.Next;
ForeignQuery.Free;
ColQuery.Free;
except
// Don't silence errors here:
on E:EDbError do
Log(lcError, E.Message);
end;
ForeignQuery.Free;
except
// Silently ignore non existent IS tables and/or columns
on E:EDbError do;
// And remember to not fire such queries again here
on E:EDbError do begin
FForeignKeyQueriesFailed := True;
end;
end;
end;

0 comments on commit 22b00d4

Please sign in to comment.
You can’t perform that action at this time.