From 072574b4c7deeb8a6e630e5b29483ec459826404 Mon Sep 17 00:00:00 2001 From: hvlad Date: Thu, 3 Nov 2022 19:23:47 +0200 Subject: [PATCH] Fixed ref constraints handling: dest tables are ordered according to the master-details relationships - independent tables first then tables that depends on them only and so on. Loops created by foreign keys are detected and corresponding FK's are disabled before data pump and enabled after. DDL statement to enable (create) FK is fixed for the case when master table is distinct from detail one. Table nodes in dest treeview are correctly marked as loop parts. Also such nodes are expanded and nodes of 'to be disabled' FK's are underlined. It worked for self referenced tables only, now loops of any number of participant tables is detected correctly. --- Sources/IBDataPump/ibpMain.pas | 311 ++++++++++++++++++++++++--------- 1 file changed, 233 insertions(+), 78 deletions(-) diff --git a/Sources/IBDataPump/ibpMain.pas b/Sources/IBDataPump/ibpMain.pas index edf165b..31a5feb 100644 --- a/Sources/IBDataPump/ibpMain.pas +++ b/Sources/IBDataPump/ibpMain.pas @@ -457,7 +457,7 @@ TibpMain = class(TForm) public {$IFDEF CCNEWS} FCCNews: TclDownLoader; -{$ENDIF} +{$ENDIF} // ccCompos tvSource: TccTreeView; @@ -543,7 +543,7 @@ TibpMain = class(TForm) arRequired: array[boolean] of string = ('', 'Required'); PumpMsgDlgType: array[TMsgDlgType] of string = ('Warning', 'Error', 'Information', 'Confirmation', 'Custom'); - AppVersion = '3.5s3'; + AppVersion = '3.5s4'; AppTitle = 'Interbase DataPump v '; AppHome = 'www.CleverComponents.com'; AppEmail = 'info@CleverComponents.com'; @@ -1177,7 +1177,8 @@ procedure TibpMain.FormCreate(Sender: TObject); eSourceDatabase:= TccButtonEdit.Create(Self); with eSourceDatabase do begin - Parent:= tsDatabases; Left:= 92; + Parent:= tsDatabases; + Left:= 92; Top:= 24; Width:= 426; Height:= 21; @@ -1200,7 +1201,8 @@ procedure TibpMain.FormCreate(Sender: TObject); upErrCnt:= TccSpinEdit.Create(Self); with upErrCnt do begin - Parent:= tsPumpProp; Left:= 94; + Parent:= tsPumpProp; + Left:= 94; Top:= 105; Width:= 81; Height:= 21; @@ -1350,7 +1352,8 @@ procedure TibpMain.FormCreate(Sender: TObject); ccSaveReport:= TccButtonEdit.Create(Self); with ccSaveReport do begin - Parent:= pStepThreeBottom; Left:= 85; + Parent:= pStepThreeBottom; + Left:= 85; Top:= 3; Width:= 480; Height:= 21; @@ -2441,104 +2444,243 @@ procedure TibpMain.DelDep(const AName: string); end; procedure TibpMain.FillDestDef; + +type + TTabCounts = record + FDetail : Integer; + FMaster : Integer; + end; + var lst, rd, lcmp: TStringList; nd, nrc, tmp: TccTreeNode; i, j, k: integer; loop, loopinf: TStringList; - qryFree: TIBQuery; + disabledFK : TStringList; + tabCounts : array of TTabCounts; + found : Boolean; + fldDepDetail, fldDepMaster, fldDepFK : TField; + + // recursive, return index of loop start or -1 + function walkTable(i : integer; var unmark : boolean) : integer; + const + mark : Pointer = Pointer(1); + + var + bm : TBookmark; + n : Integer; + fk : String; + + begin + Result := -1; + + // mark as walked + loop.Objects[i] := mark; + if not qryDep.Locate(fldDepDetail.FieldName, loop[i], []) + then Exit; + + while Result = -1 do + begin + // check master table + n := loop.IndexOf(Trim(fldDepMaster.AsString)); + if n < 0 + then Exit; + + // put fk name + fk := Trim(fldDepFK.AsString); + loopinf.Add(fk); + + if Assigned(loop.Objects[n]) + then begin + if i = n + then unmark := true; + + Result := n; + Exit; + end; + + bm := qryDep.GetBookmark; + Result := walkTable(n, unmark); + + if Result >= 0 + then begin + if i = Result // loop started here + then unmark := true + else if unmark // unmark tables that is not a part of the loop + then begin + loop.Objects[i] := nil; + loopinf.Delete(loopinf.IndexOf(fk)); + end; + + qryDep.FreeBookmark(bm); + Exit; + end; + + loopinf.Delete(loopinf.Count - 1); + qryDep.GotoBookmark(bm); + qryDep.FreeBookmark(bm); + + if not qryDep.LocateNext(fldDepDetail.FieldName, loop[i], []) + then Exit; + end; + end; + + function findLoops : boolean; + var + u : Boolean; + i : Integer; + begin + Result := false; + + // put all tables that makes a loops into 'loop' list + for i := 0 to lst.Count - 1 do + if (tabCounts[i].FDetail > 0) and (tabCounts[i].FMaster > 0) + then loop.Add(lst[i]); + + if loop.Count = 0 + then Exit; + + u := false; + Result := (walkTable(0, u) >= 0); + + // delete not marked items + i := 0; + while i < loop.Count do + if not Assigned(loop.Objects[i]) + then loop.Delete(i) + else Inc(i); + end; + begin FDM.DBDest.Connected := True; try FDM.DBDest.DefaultTransaction.StartTransaction; + lst := TStringList.Create; rd := TStringList.Create; lcmp := TStringList.Create; loop := TStringList.Create; loopinf := TStringList.Create; + disabledFK := TStringList.Create; + tvDest.Items.BeginUpdate; try FDM.DBDest.GetTableNames(lst); + lst.Sort; + SetLength(tabCounts, lst.Count); + // get counts as detail\master for all relations qryDep.Open; - qryFree:= FDM.GetIBQuery(FDM.DBDest, - 'select rdb$relation_name as name ' - + 'from rdb$relations ' - + 'where rdb$view_blr is null ' - + 'and (rdb$system_flag is null or rdb$system_flag = 0) ' - + 'order by RDB$RELATION_NAME'); - qryFree.Open; - try - qryFree.First; - while not qryFree.EOF do - begin - rd.Add(TrimRight(qryFree.Fields[0].AsString)); - i := lst.IndexOf(rd[rd.Count-1]); - lst.Delete(i); - DelDep(rd[rd.Count-1]); - qryFree.Next; + // fields are: detail, fk, master, pk + fldDepDetail := qryDep.Fields[0]; + fldDepFK := qryDep.Fields[1]; + fldDepMaster := qryDep.Fields[2]; + + qryDep.First; + while not qryDep.Eof do + begin + if cbLoop.Checked and (Trim(fldDepDetail.AsString) = Trim(fldDepMaster.AsString)) + then begin + disabledFK.Add(Trim(fldDepFK.AsString)); + qryDep.Delete; + end + else begin + i := lst.IndexOf(Trim(fldDepDetail.AsString)); + if i >= 0 + then Inc(tabCounts[i].FDetail); + + i := lst.IndexOf(Trim(fldDepMaster.AsString)); + if i >= 0 + then Inc(tabCounts[i].FMaster); + + qryDep.Next; end; - finally - qryFree.Close; - qryFree.Free; end; - rd.Sort; - lst.Sort; + // add tables into 'rd' list in the correct order + + // first add tables with no FK relationships + for i := 0 to lst.Count - 1 do + if (tabCounts[i].FDetail = 0) and (tabCounts[i].FMaster = 0) + then rd.Add(lst[i]); - loop.Clear; - loopinf.Clear; - while lst.Count <> 0 do + // then add other tables, independent first + while True do begin - // Check for loop - if FixLocate(qryDep, 'DEP', lst[0]) then - begin - if loop.IndexOf(Trim(qryDep.FindField('DEP').AsString)) >= 0 then - begin - PumpDlg('Can not continue - Loop found! Tables in loop: ' + loop.CommaText + '. Ref Constraints: ' + loopinf.CommaText + - '. To resolve loop you need to alter or temporary delete one of this ref constraints. ' + - 'After data pumping finished you can restore it again. ' + - 'Please read help to get more info.'); - Abort; - end; + found := false; + for i := 0 to lst.Count - 1 do + if (tabCounts[i].FDetail = 0) and (tabCounts[i].FMaster <> 0) + then begin + found := true; + rd.Add(lst[i]); + + while qryDep.Locate(fldDepMaster.FieldName, lst[i], []) do + begin + Dec(tabCounts[i].FMaster); - i := lst.IndexOf(TrimRight(qryDep.FindField('SOURCE').AsString)); + j := lst.IndexOf(Trim(fldDepDetail.AsString)); + if j >= 0 + then begin + Dec(tabCounts[j].FDetail); - if i <> 0 then - begin - loop.Add(TrimRight(qryDep.FindField('DEP').AsString)); - loopinf.Add(TrimRight(qryDep.FindField('RDB$CONSTRAINT_NAME').AsString)); - end; + if (tabCounts[j].FDetail = 0) and (tabCounts[j].FMaster = 0) + then rd.Add(lst[j]); + end; - if i = -1 then - begin - // mistake in algorithm found - PumpDlg(lst[0] +'-' + Trim(qryDep.FindField('SOURCE').AsString)); - end; - if i = 0 then - begin - // loop here - delete this link - qryDep.Delete; - end - else - begin - // link found - swap it - lst.Move(0, lst.Count-1); + qryDep.Delete; + end; end; - end - else - begin - // 0 element have no constraints now - rd.Add(TrimRight(lst[0])); - DelDep(lst[0]); - lst.Delete(0); - loop.Clear; - loopinf.Clear; + if rd.Count = lst.Count + then Break; + + if found + then Continue; + + // find one loop details + if not findLoops + then begin + PumpDlg('Loop is expected but not foung, bug ?'); + Abort; + end; + + if not cbLoop.Checked + then begin + PumpDlg('Can not continue - Loop found!'#13 + + ' Tables in loop: ' + loop.CommaText + '.'#13 + + ' Ref Constraints: ' + loopinf.CommaText + '.'#13#13 + + 'To resolve loop you need to alter or temporary delete one of this ref constraints. '#13 + + 'After data pumping finished you can restore it again. '#13 + + 'Please read help to get more info.'); + Abort; end; + + // Disable FK to break a loop and repeat + disabledFK.Add(loopinf[0]); + + if qryDep.Locate(fldDepFK.FieldName, loopinf[0], []) + then begin + i := lst.IndexOf(Trim(fldDepDetail.AsString)); + if (i >= 0) + then Dec(tabCounts[i].FDetail); + + i := lst.IndexOf(Trim(fldDepMaster.AsString)); + if (i >= 0) + then Dec(tabCounts[i].FMaster); + + if (tabCounts[i].FDetail = 0) and (tabCounts[i].FMaster = 0) + then rd.Add(lst[i]); + + qryDep.Delete; + end; + + loop.Clear; + loopinf.Clear; end; + disabledFK.Sort; + tvDest.Items.Clear; for i := 0 to rd.Count-1 do begin @@ -2609,7 +2751,16 @@ procedure TibpMain.FillDestDef; tmp := tvDest.Items.AddChild(nrc, TrimRight(qryDep.FindField('RDB$CONSTRAINT_NAME').AsString)); tmp.InfoText := TrimRight(qryDep.FindField('SOURCE').AsString); tmp.ImageIndex := Integer(picRefConst); - if tmp.InfoText = nd.TheText then nd.ImageIndex := Integer(picTableLoop); + + if disabledFK.IndexOf(Trim(qryDep.FindField('RDB$CONSTRAINT_NAME').AsString)) >= 0 + then begin + nd.ImageIndex := Integer(picTableLoop); + tmp.Tag := 1; // mark FK node as disabled + + nrc.Expanded := True; + nd.Expanded := True; + end; + inc(j); FillFK(tmp); qryDep.Next; @@ -2647,8 +2798,10 @@ procedure TibpMain.FillDestDef; lcmp.Free; loop.Free; loopinf.Free; + disabledFK.Free; tvDest.Items.EndUpdate; qryDep.Close; + SetLength(tabCounts, 0); end; finally FDM.DBDest.Connected := False; @@ -3141,11 +3294,14 @@ procedure TibpMain.tvDestCustomDraw(Sender: TObject; TreeNode: TccTreeNode; if (TreeNode.SelectedIndex in [Integer(picDestField), Integer(picSourceField)]) and (TreeNode.Data <> nil) then AFont.Style := [fsUnderline]; + + // To be disabled FK if (TreeNode.SelectedIndex = Integer(picRefConst)) and - (TreeNode.Parent.Parent.TheText = TreeNode.InfoText) then + (TreeNode.Tag = 1) then begin AFont.Style := AFont.Style + [fsUnderline]; end; + if Pos(#0, TreeNode.Text) > 0 then begin AFont.Style := AFont.Style + [fsBold]; @@ -3673,7 +3829,7 @@ function TibpMain.GetNameLst(tn: TccTreeNode; AType: TPumpDatabaseType; ASQLDial procedure TibpMain.AlterConst(lOn: boolean); var nd, rn, cons, fk, rk, opt: TccTreeNode; - ASQLCons, ASQLFields, ASQLFieldsRel, ASQLTable: string; + ASQLCons, ASQLFields, ASQLFieldsRel, ASQLTable, AMaster: string; begin if not cbLoop.Checked then Exit; if lOn @@ -3688,10 +3844,11 @@ procedure TibpMain.AlterConst(lOn: boolean); cons := rn.GetFirstChild; while cons <> nil do begin - if cons.InfoText = nd.TheText then + if cons.Tag = 1 then // disabled FK found begin ASQLTable := GetSQLName(nd.TheText, pdtIB, DestDialect); - ASQLCons := GetSQLName(cons.TheText, pdtIB, DestDialect); + AMaster := GetSQLName(cons.InfoText, pdtIB, DestDialect); + ASQLCons := GetSQLName(cons.TheText, pdtIB, DestDialect); qryDest.Close; qryDest.SQL.Clear; if lOn then @@ -3704,7 +3861,7 @@ procedure TibpMain.AlterConst(lOn: boolean); qryDest.SQL.Add(Format('ALTER TABLE %s', [ASQLTable])); qryDest.SQL.Add(Format(' ADD CONSTRAINT %s', [ASQLCons])); qryDest.SQL.Add(Format(' FOREIGN KEY (%s)', [ASQLFields])); - qryDest.SQL.Add(Format(' REFERENCES %s (%s) %s', [ASQLTable, ASQLFieldsRel, opt.InfoText])); + qryDest.SQL.Add(Format(' REFERENCES %s (%s) %s', [AMaster, ASQLFieldsRel, opt.InfoText])); end else begin @@ -5397,5 +5554,3 @@ finalization end; end. - -