{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvDBMove.PAS, released on 2002-07-04. The Initial Developers of the Original Code are: Andrei Prygounkov Copyright (c) 1999, 2002 Andrei Prygounkov All Rights Reserved. Contributor(s): You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Description: database batchmove History: 1.23 - added suport for table names with extensions; Note: All referenced fields MUST be Integer Example : Source = dbChildCompany Destination = dbCompany Tables = ( Employee Children ); References = ( Children.Employee = Employee.Uni ); TempTable = '_RATMP1_.DB'; BeforePost = user defined unique generation procedure; Known Issues: -----------------------------------------------------------------------------} // $Id: JvBDEMove.pas 10612 2006-05-19 19:04:09Z jfudickar $ unit JvBDEMove; {$I jvcl.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} Classes, DB, DBTables, JvComponentBase; type TJvDBMove = class; TMoveAction = (maMove, maMap, maIgnore); TMoveEvent = procedure(Sender: TJvDBMove; Table: TTable; var Action: TMoveAction) of object; TJvDBMove = class(TJvComponent) private FSource: string; FDestination: string; FSTable: TTable; FDTable: TTable; FTempTable: string; FRTable: TTable; { temporary table } FTables: TStringList; FReferences: TStringList; FMappings: TStringList; FFieldRefs: TList; FProgress: Boolean; FRecordCount: Integer; FCurrentRecord: Integer; FErrorCount: Integer; FErrorBlobCount: Integer; FMaxPass: Integer; FOnMoveRecord: TMoveEvent; FOnPostError: TDataSetErrorEvent; procedure DoMove; function GetTables: TStrings; function GetReferences: TStrings; function GetMappings: TStrings; procedure SetTables(Value: TStrings); procedure SetReferences(Value: TStrings); procedure SetMappings(Value: TStrings); procedure CreateTmpTable; procedure CompileReferences; function Map(const TableName, FieldName: string): string; procedure CompatTables; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Execute; property RecordCount: Integer read FRecordCount; property CurrentRecord: Integer read FCurrentRecord; property ErrorCount: Integer read FErrorCount; property ErrorBlobCount: Integer read FErrorBlobCount; published property Source: string read FSource write FSource; property Destination: string read FDestination write FDestination; property Tables: TStrings read GetTables write SetTables; property TempTable: string read FTempTable write FTempTable; property References: TStrings read GetReferences write SetReferences; property Mappings: TStrings read GetMappings write SetMappings; property OnMoveRecord: TMoveEvent read FOnMoveRecord write FOnMoveRecord; property OnPostError: TDataSetErrorEvent read FOnPostError write FOnPostError; property Progress: Boolean read FProgress write FProgress default False; end; EJvDBMoveError = class(EDatabaseError); {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvBDEMove.pas $'; Revision: '$Revision: 10612 $'; Date: '$Date: 2006-05-19 21:04:09 +0200 (ven., 19 mai 2006) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} implementation uses {$IFDEF HAS_UNIT_VARIANTS} Variants, {$ENDIF HAS_UNIT_VARIANTS} SysUtils, BDE, JvBDEUtils, JvResources; const cTable = 'Table'; cField = 'Field'; cOldValue = 'OldValue'; cNewValue = 'NewValue'; type TFieldRef = class(TObject) private STableName: string; SFieldName: string; STableIndex: Integer; SFieldIndex: Integer; DTFieldIndex: Integer; MasterRef: Boolean; DTableName: string; DFieldName: string; DTableIndex: Integer; DFieldIndex: Integer; end; function CmdString(S: string): Boolean; begin S := Trim(S); Result := (S <> '') and (S[1] <> ';'); end; constructor TJvDBMove.Create(AOwner: TComponent); begin inherited Create(AOwner); FTables := TStringList.Create; FReferences := TStringList.Create; FMappings := TStringList.Create; FFieldRefs := TList.Create; FTempTable := '_RATMP1_'; FMaxPass := 1; end; destructor TJvDBMove.Destroy; begin FTables.Free; FReferences.Free; FMappings.Free; FFieldRefs.Free; inherited Destroy; end; function TJvDBMove.GetTables: TStrings; begin Result := FTables; end; procedure TJvDBMove.SetTables(Value: TStrings); begin FTables.Assign(Value); CompatTables; end; procedure TJvDBMove.CompatTables; var I: Integer; begin { make compatible with previous version of TJvDBMove } for I := 0 to FTables.Count - 1 do if FTables[I] <> '' then FTables[I] := Trim(SubStr(FTables[I], 0, '=')); end; function TJvDBMove.GetReferences: TStrings; begin Result := FReferences; end; procedure TJvDBMove.SetReferences(Value: TStrings); begin FReferences.Assign(Value); end; function TJvDBMove.GetMappings: TStrings; begin Result := FMappings; end; procedure TJvDBMove.SetMappings(Value: TStrings); begin FMappings.Assign(Value); end; function TJvDBMove.Map(const TableName, FieldName: string): string; begin if FieldName = '' then begin Result := FMappings.Values[TableName]; if Result = '' then Result := TableName; end else begin Result := SubStrEnd(FMappings.Values[ChangeFileExt(TableName, '') + '.' + FieldName], 0, '.'); if Result = '' then Result := FieldName; end; end; procedure TJvDBMove.CreateTmpTable; begin with FRTable do begin Active := False; { The Table component must not be active } { First, describe the type of table and give it a name } DatabaseName := FDestination; TableType := ttDefault; TableName := FTempTable; { Next, describe the fields in the table } with FieldDefs do begin Clear; Add(cTable, ftInteger, 0, True); Add(cField, ftInteger, 0, True); Add(cOldValue, ftInteger, 0, True); Add(cNewValue, ftInteger, 0, True); end; { Next, describe any indexes } { with IndexDefs do begin Clear; Add('', cTable + ';' + cField + ';' + cOldValue, [ixPrimary, ixUnique]); end; }{ Now that we have specified what we want, create the table } CreateTable; end; end; procedure TJvDBMove.Execute; procedure CalcRecords; var I: Integer; begin FRecordCount := 0; FCurrentRecord := 0; for I := 0 to FTables.Count - 1 do if CmdString(FTables[I]) then begin FSTable.Close; FSTable.TableName := FTables[I]; FSTable.Open; Inc(FRecordCount, FSTable.RecordCount); end; end; begin CompatTables; FSTable := TTable.Create(Self); FDTable := TTable.Create(Self); FRTable := TTable.Create(Self); try FSTable.DatabaseName := FSource; FDTable.DatabaseName := FDestination; FRecordCount := -1; if FProgress then CalcRecords; CreateTmpTable; try FRTable.Open; CompileReferences; FDTable.OnPostError := FOnPostError; DoMove; finally FRTable.Close; FRTable.DeleteTable; end; finally FSTable.Free; FDTable.Free; FRTable.Free; end; end; procedure TJvDBMove.CompileReferences; var I, J: Integer; S: string; Master, Detail: string; FieldRef: TFieldRef; begin FFieldRefs.Clear; for I := 0 to FReferences.Count - 1 do begin S := FReferences[I]; if CmdString(S) then begin Detail := SubStr(S, 0, '='); Master := SubStr(S, 1, '='); if (Detail = '') or (Pos('.', Detail) = 0) or (Master = '') or (Pos('.', Master) = 0) then raise EJvDBMoveError.CreateRes(@RsEInvalidReferenceDescriptor); FieldRef := TFieldRef.Create; FieldRef.STableName := Trim(SubStr(Master, 0, '.')); FieldRef.SFieldName := Trim(SubStr(Master, 1, '.')); FieldRef.DTableName := Trim(SubStr(Detail, 0, '.')); FieldRef.DFieldName := Trim(SubStr(Detail, 1, '.')); FieldRef.STableIndex := -1; FieldRef.STableIndex := -1; FieldRef.SFieldIndex := -1; FieldRef.DFieldIndex := -1; FieldRef.DTFieldIndex := -1; FieldRef.MasterRef := True; for J := 0 to FFieldRefs.Count - 1 do with TFieldRef(FFieldRefs[J]) do if Cmp(STableName, FieldRef.STableName) and Cmp(SFieldName, FieldRef.SFieldName) then begin FieldRef.MasterRef := False; Break; end; FFieldRefs.Add(FieldRef); end; end; end; procedure TJvDBMove.DoMove; type TRef = record IsRef: Boolean; Value: Integer; HasRef: Boolean; end; var MasterFields: array [0..1023] of TRef; // Max_Columns HasMaster, HasDetail: Boolean; AllFixups: Boolean; I, TableIndex: Integer; // Er : Integer; procedure UpdateRefList(ATableIndex: Integer); var I, F: Integer; begin FillChar(MasterFields, SizeOf(MasterFields), 0); for I := 0 to FFieldRefs.Count - 1 do with TFieldRef(FFieldRefs[I]) do begin if Cmp(STableName, ChangeFileExt(FSTable.TableName, '')) then begin STableIndex := ATableIndex; for F := 0 to FSTable.FieldCount - 1 do if Cmp(SFieldName, FSTable.Fields[F].FieldName) then begin SFieldIndex := F; DTFieldIndex := FDTable.FieldByName( Map(FSTable.TableName, FSTable.Fields[SFieldIndex].FieldName)).Index; MasterFields[F].IsRef := True; HasMaster := True; end; end; if Cmp(Map(DTableName, ''), ChangeFileExt(FDTable.TableName, '')) then begin DTableIndex := ATableIndex; for F := 0 to FDTable.FieldCount - 1 do if Cmp(Map(DTableName, DFieldName), FDTable.Fields[F].FieldName) then begin DFieldIndex := F; MasterFields[F].HasRef := True; HasDetail := True; end; end; end; end; procedure AppendRef(TableIndex: Integer); var I: Integer; begin for I := 0 to FFieldRefs.Count - 1 do with TFieldRef(FFieldRefs[I]) do if MasterRef and (STableIndex = TableIndex) then try FRTable.AppendRecord([TableIndex + 1, SFieldIndex + 1, MasterFields[SFieldIndex].Value, FDTable.Fields[DTFieldIndex].AsVariant]); except; end; end; function FixupRef(TableIndex: Integer): Boolean; var I: Integer; begin for I := 0 to FFieldRefs.Count - 1 do with TFieldRef(FFieldRefs[I]) do if (DTableIndex = TableIndex) and (DFieldIndex <> -1) and (FDTable.Fields[DFieldIndex].AsVariant <> Null) then begin { FDTable.Fields[DFieldIndex].AsVariant := FRTable.Lookup(cTable + ';' + cField + ';' + cOldValue, VarArrayOf([ STableIndex + 1, SFieldIndex + 1, FDTable.Fields[DFieldIndex].AsVariant]), cNewValue); } if FRTable.Locate(cTable + ';' + cField + ';' + cOldValue, VarArrayOf([STableIndex + 1, SFieldIndex + 1, FDTable.Fields[DFieldIndex].AsVariant]), []) then FDTable.Fields[DFieldIndex].AsVariant := FRTable[cNewValue] else begin // record not found, may be in second pass AllFixups := False; Result := False; Inc(FErrorCount); Exit; end; end; Result := True; end; procedure MoveRecord(TableIndex: Integer); var F: Integer; Action: TMoveAction; procedure MoveField(FieldIndex: Integer); begin try FDTable.FieldByName(Map(FSTable.TableName, FSTable.Fields[FieldIndex].FieldName)).AsVariant := FSTable.Fields[FieldIndex].AsVariant; except on E: EDBEngineError do if E.Errors[0].ErrorCode = DBIERR_BLOBMODIFIED then begin Inc(FErrorCount); Inc(FErrorBlobCount); end else raise; end; end; begin FDTable.Append; try for F := 0 to FSTable.FieldCount - 1 do if FDTable.FindField(Map(FSTable.TableName, FSTable.Fields[F].FieldName)) <> nil then begin MoveField(F); if MasterFields[F].IsRef then MasterFields[F].Value := FSTable.Fields[F].AsInteger; end; Action := maMove; if HasDetail and not FixupRef(TableIndex) then Action := maIgnore; if (Action = maMove) and Assigned(FOnMoveRecord) then FOnMoveRecord(Self, FDTable, Action); if HasMaster and (Action in [maMove, maMap]) then AppendRef(TableIndex); if Action = maMove then try FDTable.Post except on E: EAbort do begin FDTable.Cancel; Inc(FErrorCount); end; end else FDTable.Cancel; except on E: EAbort do raise else if FDTable.State = dsInsert then FDTable.Cancel; // raise; end; end; procedure MoveTable(TableIndex: Integer); begin FSTable.Close; FDTable.Close; FSTable.TableName := FTables[TableIndex]; FDTable.TableName := Map(FTables[TableIndex], ''); FSTable.Open; FDTable.Open; UpdateRefList(TableIndex); while not FSTable.Eof do begin try Inc(FCurrentRecord); MoveRecord(TableIndex); except // raise; end; FSTable.Next; end; end; begin FCurrentRecord := 0; FErrorCount := 0; FErrorBlobCount := 0; for I := 0 to FTables.Count - 1 do if CmdString(FTables[I]) then begin { in Tables list one table can be appear more than once, but we must use one TableIndex for all appearance } TableIndex := FTables.IndexOf(FTables[I]); // if (TableIndex = I) or not AllFixups then begin AllFixups := True; MoveTable(TableIndex); end; { if TableIndex = I then Er := FErrorCount else FErrorCount := Er; } end; end; {$IFDEF UNITVERSIONING} initialization RegisterUnitVersion(HInstance, UnitVersioning); finalization UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.