Реалізація off-line реплікації в Interbase (Firebird)

Часто в задачі автоматизації управління підприємствами входить завдання
синхронізації стану інформації баз даних у головному офісі та регіональних
офісах. Прикладами можуть служити управління розподіленим складом або філіями
банку.

У системі без виділеного головного офісу завдання може полягати в передачі
змін заданих таблиць БД всім або виділеного списку територіальних
управлінь (ТУ), що працюють з БД тієї ж структури.

Для вирішення цього завдання в СУБД FireBird можна поступати таким чином.

Для всіх таблиць БД використовувати сурогатні первинні ключі представляють
счетікі, що задаються генераторами, а обмеження унікальності поєднань полів
задавати додатково. Для кожного ТУ задавати такі значення генераторів,
щоб гарантувати відсутність перетинів множин значень генераторів різних
ТУ, наприклад всі генератори ТУ1 мають значення у відрізку цілих чисел [1, N],
для ТУ2 [N +1, 2N] і т.д.

Для відстеження змін в базі даних скористаємося менеджером протоколів
даних IBExpress. На Рис 1. показано яким чином можна поставити на
логування таблиці бази даних ROffice.gdb.



Рис 1.

Облік змін ведеться в чотирьох системних таблицях зображених на Рис 2.



Рис 2.

Наприклад, при внесенні змін до таблиці CITY, в таблицях IBE $ Log_Tables,
IBE $ Log_Keys, IBE $ Log_Fields, IBE $ Log_Blob_Fields з'являться записи за рахунок
спрацьовування тригерів логування. На Рис. 3 показані дані таблиці
IBE $ Log_Tables, де видно, що 09.09.2005 виконувалися операції INSERT і UPDATE з
таблицею CITY. Відповідні цих операціях запису будуть присутні і в
таблицях IBE $ Log_Keys, IBE $ Log_Fields, IBE $ Log_Blob_Fields.



Рис 3.

Інформацію про змінені даних однієї БД за певний проміжок часу
для заданого набору таблиць будемо формувати у файл даних off-line реплікації,
який будемо називати D – файл.

Імена файлів D мають структуру:


При прийомі файлу D відбувається перевірка синтаксису файлу, номер сеансу і дати
в імені файлу, номер сеансів повинні задовольняти своїй послідовності від
кожного ТУ (див. формування імені файлу D). За результатом контороля формується
файл квитанції S.

Ім'я файлу квитанції формується з імені файлу додаванням зліва букви S. Для
обміну D і S файли створюються каталоги.

.. ROOT IN
.. ROOT OUT, де ROOT – коренева директорія

Для управління сеансами реплікації потрібно створити БД, що містить по крайней
мірою три таблиці, подібних зображеним на Рис. 4.



Рис 4.

і таблиці Project і Tables, що містять інформацію про бази і таблицях,
підлягають реплікації (Рис 5).



Рис 5.

Тут AliasName ім'я файлу бази даних. Replication = 1 означає, що об'єкт
підлягає реплікації а Replication = 0 немає.

Черговий файл реплікації D може бути побудований тільки при наявності
сквитованих без помилок попереднього файлу D, або якщо він є першим.

При успішному формуванні файла він поміщається в Connections.Files, каталог
".. ROOT" і копіюється в ".. ROOT OUT" для поштової програми. У
Connections.Errors пишеться протокол. При цьому виконуються перетворення в
таблиці сеансів Connections.

При виявленні помилки при прийомі D файлу формується квитанція з кодом
помилки, а Connections.State: = stateReceived, Connections.Errid: = <> 0.

При виявленні помилки при прийомі S, квитанція бракується Connections.State: =
stateReceived , Connections.Errid := <> 0.

При нормальному прийомі S Connections.State: = stateTICKED (сквитованих) для
файлу D і stateRECEIVE (прийнята) для квитанції і Connections.Errid: = 0 для
обох.

Якщо для прийнятого файлу існує запис Connections.NameFile =
NameFile, Connections.State = stateReceive, Connections.Errid = 0, файл
віддаляється як повторний.

Файл D містить наступні дані.


Далі аналогічно містяться дані, що відповідають запитам SIBKUI, SIBKD,
SIBFU, SIBBU з тим же роздільником між ними.

Формат передачі даних ClientDataset використовується для зручності передачі
значень BLOB полів чого не дозволяє найбільш популярний для цих цілей XML
формат.

/ / Функція побудови файлу D для заданого відрізку часу
/ / ProjectID – номер проекту
/ / NKeyMin – мінімальне значення ключів БД відправника
/ / NKeyMax – максимальне значення ключів БД відправника
/ / TablesList – список таблиць, розділених комою, що підлягають реплікації

function TfmSkladTun.CreateContentDViacds (DateB, DateE: TDateTime;
NameDataBase: string; ProjectID, NKeyMin, NKeyMax: integer;
TablesList: string ): string;
Const / / для послідовності операцій
SIBT =
"Select IBT.table_name, IBT.ID, IBT.OPERATION from ibe $ log_tables" +
"IBT inner join ibe $ log_keys IBK on IBT.ID = IBK.log_tables_id" +
” where DATE_TIME >= :D1 and DATE_TIME < :D2 “+
” and Cast(IBK.KEY_VALUE as numeric) >= %d “+
” and Cast(IBK.KEY_VALUE as numeric) < %d”;

SIBKUI = / / для віддалених записів
"Select IBT.table_name, IBK.KEY_FIELD, IBK.KEY_VALUE, IBT.ID" +
"From ibe $ log_keys IBK, ibe $ log_tables IBT where log_tables_id in" +
"(Select id from ibe $ log_tables where DATE_TIME> =: D1 and" +
” DATE_TIME < :D2 “+
” and OPERATION <> “D” ) “+
” and IBK.log_tables_id = IBT.id “+
” and Cast(IBK.KEY_VALUE as numeric) >= %d “+
” and Cast(IBK.KEY_VALUE as numeric) < %d”;

SIBKD = / / для віддалених записів
"Select IBT.table_name, IBK.KEY_FIELD, IBK.KEY_VALUE, IBT.ID" +
"From ibe $ log_keys IBK, ibe $ log_tables IBT where log_tables_id in" +
"(Select id from ibe $ log_tables where DATE_TIME> =: D1 and" +
” DATE_TIME < :D2 “+
” and OPERATION = “D” ) “+
” and IBK.log_tables_id = IBT.id “+
” and Cast(IBK.KEY_VALUE as numeric) >= %d “+
” and Cast(IBK.KEY_VALUE as numeric) < %d”;

/ / Для змінених значень полів operation <> "D"
/ / Для вставлених значень полів

SIBFU =
"Select IBT.table_name, IBF.FIELD_NAME, IBF.OLD_VALUE, IBF.NEW_VALUE," +
; IBK.KEY_FIELD, IBK.KEY_VALUE, IBT.ID from ibe $ log_tables IBT, "+
” ibe$log_fields IBF, ibe$log_keys IBK “+
” where IBF.log_tables_id in “+
"(Select id from ibe $ log_tables where DATE_TIME> =: D1 and" +
” DATE_TIME < :D2 “+
” and OPERATION <> “D” ) “+
"And IBT.ID = IBK.log_tables_id and IBT.ID = IBF.log_tables_id" +
” and Cast(IBK.KEY_VALUE as numeric) >= %d “+
” and Cast(IBK.KEY_VALUE as numeric) < %d”;

/ / Для змінених значень Blob полів operation <> "D"
/ / Для вставлених значень Blob полів
SIBBU =
” select IBT.table_name, IBB.FIELD_NAME, IBB.OLD_CHAR_VALUE, “+
” IBB.NEW_CHAR_VALUE,”+
” IBB.OLD_BLOB_VALUE, IBB.NEW_BLOB_VALUE,”+
"IBK.KEY_FIELD, IBK.KEY_VALUE, IBT.ID from ibe $ log_tables IBT," +
” ibe$log_blob_fields IBB, ibe$log_keys IBK “+
” where IBB.log_tables_id in (select id from ibe$log_tables “+
"Where DATE_TIME> =: D1 and DATE_TIME <: D2 and OPERATION <>" D ")" +
"And IBT.ID = IBK.log_tables_id and IBT.ID = IBB.log_tables_id" +
” and Cast(IBK.KEY_VALUE as numeric) >= %d “+
” and Cast(IBK.KEY_VALUE as numeric) < %d”;

var
i,j: integer;
StrTmpAll: string;
ListSql: TStringList;
TableList: TStringList;
DBNameSaved: string;
FDelimiter, FDelimiter1: string;
guid: TGUID;
StrStream: TStringStream;
sTemp: string;
begin
Result := “;
try
try
/ / Номер реплицируемой проекту
StrTmpAll := Padl(Trim(IntToStr(ProjectID)), 10)+#13#10+
FormatDateTime(“DD.MM.YYYY HH:NN:SS”,DateB)+#13#10+
FormatDateTime(“DD.MM.YYYY HH:NN:SS”,DateE)+#13#10+
IntToStr(DeltaGen) +#13#10;
/ / – DeltaGen негативне чи позитивне ціле число,
/ / Представляє різниця значень генераторів в БД відправника
/ / І БД одержувача.
if CreateGuid(guid) = S_OK then
FDelimiter := GuidToString(guid)
else
begin
MessageDlg ("Не можу отримати рядок роздільника", mtError, [mbOk], []);
Exit;
end; //
StrTmpAll := StrTmpAll + FDelimiter+#13#10;
TableList := TStringList.Create();
TableList.CommaText := TablesList;
TableList.CaseSensitive := False;
ListSql := TStringList.Create();

ListSql.Add(Format(SIBT, [ NKeyMin, NKeyMax]));
ListSql.Add(Format(SIBKUI, [ NKeyMin, NKeyMax]));
ListSql.Add(Format(SIBKD, [ NKeyMin, NKeyMax]) );
ListSql.Add(Format(SIBFU, [ NKeyMin, NKeyMax]));
ListSql.Add(Format(SIBBU, [ NKeyMin, NKeyMax]));

if dmReplConn.fbConstr.Connected then
dmReplConn.fbConstr.Connected := False;

DBNameSaved := dmReplConn.fbConstr.DBName;
dmReplConn.fbConstr.DBName := NameDataBase;
dmReplConn.fbConstr.Connected := True;
for j := 0 to ListSql.Count-1 do
begin
sTemp := “;
StrStream := TStringStream.Create(sTemp);
Createcds(j, cdsReplik);
if dmReplConn.fdCommon.Active then
dmReplConn.fdCommon.Close;
dmReplConn.fdCommon.SelectSQL.Clear;
dmReplConn.fdCommon.SelectSQL.Add(ListSql[j]);
dmReplConn.fdCommon.OpenWP([DateB, DateE]);
with (dmReplConn.fdCommon) Do
begin
First;
while not Eof Do
begin
cdsReplik.Append;

/ / Включаємо тільки
/ / Таблиці підлягають реплікації
if TableList.IndexOf (Fields [0]. AsString) <> -1 then
For i:=0 to Fields.Count-1 Do
Begin
cdsReplik.Fields[i].AsString := Fields[i].AsString;
End;
Next;
end;
end;
cdsReplik.SaveToStream(StrStream, dfBinary);
cdsReplik.Close;
StrTmpAll: = StrTmpAll + StrStream.DataString + FDelimiter + # 13 # 10;
StrStream.Free;
/ / Кінець DEL частини
end;
Result := StrTmpAll;
except
on e: exception do
ErrorDlg(e);
end;
finally
ListSql.Free;
TableList.Free;
if dmReplConn.fdCommon.Active then
dmReplConn.fdCommon.Close;
if dmReplConn.fbConstr.Connected then
dmReplConn.fbConstr.Connected := False;
dmReplConn.fbConstr.DBName := DBNameSaved;
end;
end;

/ / Побудова ClientDataSet для форматування даних
procedure TfmSkladTun.Createcds (TipLog: smallint; cds: TClientDataSet);
var
i: integer;
begin
with cds do
begin
if Active then
Close;
FieldDefs.Clear;
case TipLog of
0: / / для ID
begin
FieldDefs.Add( “TABLE_NAME”, ftString, 70 );
FieldDefs.Add( “OPERATION”, ftString, 1 );
FieldDefs.Add( “ID”, ftLargeint);
end;

1, 2: / / для insert, update і delete ключів

begin
FieldDefs.Add( “TABLE_NAME”, ftString, 70 );
FieldDefs.Add( “KEY_FIELD”, ftString, 70 );
FieldDefs.Add( “KEY_VALUE”, ftString, 255);
FieldDefs.Add( “ID”, ftLargeint);
end;

3: / / для insert і Update полів

begin
FieldDefs.Add( “TABLE_NAME”, ftString, 70 );
FieldDefs.Add( “FIELD_NAME”, ftString, 70 );
FieldDefs.Add( “OLD_VALUE”, ftMemo);
FieldDefs.Add( “NEW_VALUE”, ftMemo);
FieldDefs.Add( “KEY_FIELD”, ftString, 70 );
FieldDefs.Add( “KEY_VALUE”, ftString, 255);
FieldDefs.Add( “ID”, ftLargeint);
end;

4: / / для insert і Update Blob полів

begin
FieldDefs.Add( “TABLE_NAME”, ftString, 70 );
FieldDefs.Add( “FIELD_NAME”, ftString, 70 );
FieldDefs.Add( “OLD_CHAR_VALUE”, ftMemo);
FieldDefs.Add( “NEW_CHAR_VALUE”, ftMemo);
FieldDefs.Add( “OLD_BLOB_VALUE”, ftBlob);
FieldDefs.Add( “NEW_BLOB_VALUE”, ftBlob);
FieldDefs.Add( “KEY_FIELD”, ftString, 70 );
FieldDefs.Add( “KEY_VALUE”, ftString, 255);
FieldDefs.Add( “ID”, ftLargeint);
end;
end;
CreateDataSet;
IndexFieldNames := “ID”;
end;
end;


Файл квитанції S містить наступні дані


Черговий файл D, крім першого, може бути прийнятий тільки при наявності
сквитованих без помилок попереднього прийнятого файлу D.

Функція реалізації змін при прийомі файлу D
AllUpdateDatabaseGetContentD () генерує і виконує необхідні SQL команди в
БД одержувача, в одній транзакції, і в точно такій же послідовності, як
вони виконувалися в базі ТУ відправника.

Процедура контролю файлу D, яка виконує перевірку імені та вмісту
файлу, послідовності сеансів і т.п. (У статті не наведено) отримує
змінні наведені нижче з рядка вмісту файлу D FileString.

ProjectID := StrToInt(Trim(Copy(FileString, 1, 10)));

function TFileHandling.AllUpdateDatabaseGetContentD(): boolean;
var
Sds: TSqlDataSet;
TD: TTransactionDesc;
OldDatabase,NameDatabase: string;
cdsID: TClientDataSet;
sTemp: string;
StrStream: TStringStream;
ProjectID: integer;
begin
Result := true;
/ / Отримуємо ім'я файлу бази даних одержувача за номером проекту
NameDataBase := fmSkladTun.GetNameDatabase(ProjectID);
try
try
with dmReplConn do
begin
Sds := TSqlDataSet.Create(dmReplConn);
if cnRep.Connected then
cnRep.Close;
OldDatabase := cnRep.Params.Values[“DataBase”];
cnRep.Params.Values[“DataBase”] := NameDatabase;
if not cnRep.Connected then
cnRep.Open;
While cnRep.InTransaction do
Continue;
TD.TransactionID := 1;
TD.IsolationLevel := xilREADCOMMITTED;
cnRep.StartTransaction(TD);
Sds.SqlConnection := cnRep;
Sds.CommandType := ctQuery;
cdsID := TClientdataset.Create(fmSkladTun);
fmSkladTun.Createcds(0, cdsID);
sTemp := StringID;
StrStream := TStringStream.Create(sTemp);
cdsID.LoadFromStream(StrStream);
FreeAndNil(StrStream);
cdsID.First;
While not cdsID.Eof do
begin
UpdateDatabaseGetContentD (cdsID.Fields [0]. AsInteger, cdsID.Fields [2]. AsString [1], Sds);
cdsID.Next;
end;
end;
except
on E: Exception do
begin
if dmReplConn.cnRep.InTransaction then
dmReplConn.cnRep.Rollback(TD);
result := false;
ErrorDlg(E);
exit;
end;
end;
dmReplConn.cnRep.Commit(TD);
finally
if dmReplConn.cnRep.Connected then
dmReplConn.cnRep.Close;
dmReplConn.cnRep.Params.Values[“DataBase”] := OldDatabase;
Screen.Cursor := crDefault;
if Sds.Active then
Sds.Close;
if Assigned(Sds) then
FreeAndNil(Sds);
if Assigned(cdsID) then
FreeAndNil(cdsID);
end;
end;

procedure TFileHandling.UpdateDatabaseGetContent (IDCUR: integer;
Operation: char; Sds: TSqlDataSet);
var
i: integer;
NBLOBVALUE: string;
LOG_TABLES_ID: integer;
TABLE_NAME,KEY_FIELD,KEY_VALUE: string;
FIELD_NAME, OLD_VALUE, NEW_VALUE: string;
OLD_CHAR_VALUE, NEW_CHAR_VALUE, OLD_BLOB_VALUE, NEW_BLOB_VALUE: string;
pos1, pos2: integer;
sTemp, sTemp1: string;
FieldType: TFieldType;
StrStream, StrStream1: TStringStream;
StrValues: string;
TN: string;
cdsKeys, cdsFieldInsert, cdsBlobInsert: TClientDataset;
TempParams: TParams;
sTempNames, sTempValues, sTempNamesB, sTempValuesB: string;
sTempNamesValues, sTempNamesValuesB: string;

function GetPartInsertStatement (ForBlob: boolean; Id: integer; Tip: smallint): string;
var
sPart: string;
i: integer;
TABLE_NAME,FIELD_NAME,NEW_VALUE, KEY_FIELD,KEY_VALUE: string;
begin
Result:= “;
sPart := “;
if ForBlob then
with cdsBlobInsert do
begin
TempParams := TParams.Create;
Sds.Params.Clear;
First;
while not Eof do
begin
if Id = Fields[8].AsInteger then
if Tip = 0 then
begin / / отримати рядок для значень вставляються BLOB полів
Sds.Params.Add;
if sPart = ” then
sPart := sPart+” :p”+Trim(IntToStr(Sds.Params.Count))
else
sPart := sPart+”, :p”+Trim(IntToStr(Sds.Params.Count));
if Fields[3].AsString <> ” then
NBLOBVALUE := Fields[3].AsString
else
NBLOBVALUE := Fields[5].AsString;
Sds.Params[Sds.Params.Count-1].AsBlob := NBLOBVALUE;
end
else / / отримати рядок для імен вставляються BLOB полів
if TIP = 1 then
if sPart = ” then
sPart := sPart+ Fields[1].AsString
else
sPart := sPart+”,”+ Fields[1].AsString
else / / для update
begin
Sds.Params.Add;
if Trim(sPart) = ” then
sPart: = sPart + Fields [1]. AsString + "=: p" + Trim (IntToStr (Sds.Params.Count-1))
else
sPart: = sPart +","+ Fields [1]. AsString + "=: p" + Trim (IntToStr (Sds.Params.Count));
if Fields[3].AsString <> ” then
NBLOBVALUE := Fields[3].AsString
else
NBLOBVALUE := Fields[5].AsString;
Sds.Params[Sds.Params.Count-1].AsBlob := NBLOBVALUE;
end;
Next;
end;
end
else / / для звичайних полів
with cdsFieldInsert do
begin
First;
while not Eof do
begin
if (Id = Fields[6].AsInteger) then
if Tip = 0 then
/ / Отримати рядок для значень вставляються полів
begin
TABLE_NAME := Fields[0].AsString;
FIELD_NAME := Fields[1].AsString;
NEW_VALUE := Fields[3].AsString;
If FIELD_NAME = Fields [4]. AsString then / / якщо це ключове поле
NEW_VALUE := IntToStr(Fields[3].AsInteger + DeltaGen);

if not GetFieldType(TABLE_NAME, FIELD_NAME, FieldType) then
raise Exception.Create (Format ("Неможливо отримати тип поля% s для таблиці% s",
[FIELD_NAME, TABLE_NAME]));
if FieldType in [ftString, ftWideString, ftDate, ftDateTime, ftTimeStamp] then
NEW_VALUE := QuotedStr(NEW_VALUE);
if sPart = ” then
sPart := sPart + NEW_VALUE
else
sPart := sPart +”,”+ NEW_VALUE;
end
else / / отримати рядок для імен вставляються полів
if TIP = 1 then
if sPart = ” then
sPart := sPart+ Fields[1].AsString
else
sPart := sPart+”,”+ Fields[1].AsString
else / / Tip = 2 для update
begin
TABLE_NAME := Fields[0].AsString;
FIELD_NAME := Fields[1].AsString;
NEW_VALUE := Fields[3].AsString;
if not GetFieldType(TABLE_NAME, FIELD_NAME, FieldType) then
raise Exception.Create (Format ("Неможливо отримати тип поля% s для таблиці% s",
[FIELD_NAME, TABLE_NAME]));
if FieldType in [ftString, ftWideString, ftDate, ftDateTime, ftTimeStamp] then
NEW_VALUE := QuotedStr(NEW_VALUE);
if sPart = ” then
sPart := sPart + Fields[1].AsString+” = “+ NEW_VALUE
else
sPart := sPart+”,”+ Fields[1].AsString+” = “+ NEW_VALUE;
end;
Next;
end;
end;
for i := 0 to Sds.Params.Count-1 do
TempParams.AddParam (Sds.Params[i]);
Result := sPart;
end;
begin
try
/ / Отримання та виконання запитів на вставку і
/ / Зміна записів для звичайних і BLOB полів
cdsKeys := TClientdataset.Create(fmSkladTun);
cdsFieldInsert:= TClientdataset.Create(fmSkladTun);
cdsBlobInsert := TClientdataset.Create(fmSkladTun);

sTemp := StringForInsUpd;
StrStream := TStringStream.Create(sTemp);
fmSkladTun.Createcds(1, cdsKeys);
cdsKeys.LoadFromStream(StrStream);
FreeAndNil(StrStream);
sTemp := StringForField;
sTemp1 := StringForBlob;
StrStream := TStringStream.Create(sTemp);
StrStream1 := TStringStream.Create(sTemp1);
fmSkladTun.Createcds(3, cdsFieldInsert);
cdsFieldInsert.LoadFromStream(StrStream);
fmSkladTun.Createcds (4, cdsBlobInsert); / / для BLOBOV
cdsBlobInsert.LoadFromStream(StrStream1);
cdsKeys.First;
with cdsKeys do
while not Eof do
begin
if Fields[3].AsInteger = IDCUR then
begin
TABLE_NAME := Fields[0].AsString;
KEY_FIELD := Fields[1].AsString;
KEY_VALUE := Fields[2].AsString;
LOG_TABLES_ID := Fields[3].AsInteger;
case Operation of
“I”:
begin
sTempNames := GetPartInsertStatement(false, LOG_TABLES_ID, 1 );
sTempValues: = GetPartInsertStatement (false, LOG_TABLES_ID, 0);
sTempNamesB := GetPartInsertStatement(true, LOG_TABLES_ID, 1 );
sTempValuesB: = GetPartInsertStatement (true, LOG_TABLES_ID, 0);

Sds.CommandText: = "Insert INTO" + TABLE_NAME + "(" + sTempNames +
IIf_Str(sTempNames=”,”,IIf_Str(sTempNamesB=”,”,”,”))+
{Рядок найменувань вставляються Чи не BLOB полів}
sTempNamesB + {рядок найменувань вставляються BLOB полів}
“) VALUES (“+ sTempValues+
IIf_Str(sTempValues=”,”,IIf_Str(sTempValuesB=”,”,”,”))+
{Рядок значень вставляються Чи не BLOB полів}
sTempValuesB + {рядок значень вставляються BLOB полів}
“)”;
end;
“U”:
begin
sTempNamesValues: = GetPartInsertStatement (false, LOG_TABLES_ID, 2);
sTempNamesValuesB: = GetPartInsertStatement (true, LOG_TABLES_ID, 2);
Sds.CommandText: = "UPDATE" + TABLE_NAME + "SET" + sTempNamesValues +
IIf_Str (sTempNamesValues =",", IIf_Str (sTempNamesValuesB =",",","))+
{Рядок найменувань = значення змінюваних Чи не BLOB полів}
sTempNamesValuesB + {рядок найменувань = значення змінюваних BLOB полів}
"WHERE" + Fields [1]. AsString + "=" + IntToStr (Fields [2]. AsInteger + DeltaGen);
end;
end;
for i := 0 to Sds.Params.Count-1 do
Sds.Params[i]:= TempParams[i];
Sds.ExecSql;
if Assigned(TempParams) then
TempParams.Free;
end;
Next;
end;
FreeAndNil(StrStream);
FreeAndNil(StrStream1);

/ / Отримання запитів на видалення записів
FreeAndNil(cdsKeys);
cdsKeys := TClientdataset.Create(fmSkladTun);
sTemp := StringForDel;
StrStream := TStringStream.Create(sTemp);
fmSkladTun.Createcds(0, cdsKeys);
cdsKeys.LoadFromStream(StrStream);
cdsKeys.First;
with cdsKeys do
// if FindKey([IDCUR]) then
while not Eof do
begin
if Fields[3].AsInteger = IDCUR then
begin
TABLE_NAME := Fields[0].AsString;
KEY_FIELD := Fields[1].AsString;
KEY_VALUE := Fields[2].AsString;
LOG_TABLES_ID := Fields[3].AsInteger;
Sds.CommandText: = "delete from" + TABLE_NAME + "where" + KEY_FIELD + "=" + KEY_VALUE;
Sds.ExecSql;
end;
Next;
end;
FreeAndNil(StrStream);
finally
if Assigned(cdsKeys) then
FreeAndNil(cdsKeys);
if Assigned(cdsFieldInsert) then
FreeAndNil(cdsFieldInsert);
if Assigned(cdsBlobInsert) then
FreeAndNil(cdsBlobInsert);
end;
end;


Сервер off – line реплікації має можливість виконувати настройки режимів
реплікації, вибирати списки ТУ, на які виконується розсилка TU.Replication =
True, списки проектів в кожному ТУ, і списки таблиць баз даних проектів,
підлягають реплікації. При виконанні пункту меню "Створення файлу реплікації"
виконується побудова і відправка файлу D за період від введеного часу до
поточного. При запуску автоматичного режиму виконуються цикли прийому і передачі
даних і квитанцій з періодичністю TU. PeriodConnect. У будь-який момент можна
зупинити сервер off – line реплікації.

На рис. 6. зображено стан після відправки на Склад 2 чергового файлу D.
Файл сформувався після зміни даних в одній з таблиць проекту 3,
поставлених на реплікацію. На Рис. 7. показано, що наступний файл не
формується до приходу квитанції S від складу 2. На Рис. 8. показано, що всі
таблиці проекту 3 поставлені на реплікацію з передачі для складу 1. Після
прийому файлу D на стороні складу 2 та формування квитанції Рис. 9. сервер
складу 1 готовий знову формувати сеанс D (Мал. 10.), при виникненні
змін до даних однієї з таблиць, поставлених на реплікацію. У
відповідної БД і таблицях на стороні складу 2 виконається та ж транзакція,
але зі значеннями ключів, що відрізняються на DeltaGen.



Рис 6.


Рис 7.


Рис 8.


Рис 9.


Рис 10.

Приклад програми, що реалізує даний механізм Ви можете знайти на сайті: http://nerusoft.com

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*