git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.jvcl@12 7f62d464-2af8-f54e-996c-e91b33f51cbe
575 lines
15 KiB
ObjectPascal
575 lines
15 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
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 <a dott prygounkov att gmx dott de>
|
|
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/branches/JVCL3_36_PREPARATION/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.
|
|
|