воскресенье, 12 апреля 2026 г.

m3AttrIndexDumperJoin

{$IfDef FPC}{$CodePage cp1251}{$EndIf FPC}

unit m3AttrIndexDumperJoin;

// --------------------------------------------------------------------------
// Родители: "Attributes" <> MUID: (667D35D300BC) :: "m3" <> MUID: (548712F60101) :: "Shared Delphi Low Level" <> MUID: (4ABCC25A0322)
// --------------------------------------------------------------------------
// Модуль: "w:\common\components\rtl\Garant\m3\m3AttrIndexDumperJoin.pas" GeneratorVersion: 1.0.0.883901
// Стереотип: "<>"
// Элемент модели: "Tm3AttrIndexDumperJoin" MUID: (67175F5B013B)
// --------------------------------------------------------------------------

//#UC START# *67175F5B013BbeforeDefines*
//#UC END# *67175F5B013BbeforeDefines*
{$Include m3Define.inc}

interface

uses
 l3IntfUses
 , l3CProtoObject
 , m3SearcherInterfaces
 , l3Ranges
 , l3CoreInterfaces
 , SysUtils
 , m3AttrDumperEnum
 , Im3AttrIndexDumperList
 , m3IndexElements
;

type
 Tm3AttrIndexDumperJoinProxy = class(Tl3CProtoObject, Im3AttrIndexDumper)
  {* [RequestLink:902333387] }
  private
   f_Last: Im3AttrIndexDumper;
    {* Последний в цепочке }
   f_OtherJoin: Im3AttrIndexDumper;
   f_Files: Im3FilesEnumerable;
   f_TimeStamp: TDateTime;
   f_IsDirect: Boolean;
   f_Modified: Il3RangeEnumerable;
   f_Count: Integer;
   f_MinKeyID: Integer;
   f_MaxKeyID: Integer;
   f_Others: TIm3AttrIndexDumperList;
   f_SearchMode: Tm3SearchMode;
   f_OthersForSeq: TIm3AttrIndexDumperList;
    {* "Другие" для последовательного перебора }
  private
   procedure CheckOther;
  protected
   function pm_GetFirst: Il3RangeEnumerable;
   function pm_GetLast: Il3RangeEnumerable;
   function pm_GetCount: Integer;
   function ModifiedDocuments: Il3RangeEnumerable;
   function IndexedVersions: Im3IndexedVersions;
   function Get_Range: Tm3IDRange;
   function ValuesByKey(aKey: Integer): Il3RangeEnumerable;
   function TimeStamp: TDateTime;
   function Get_MaxKeyID: Integer;
   function Get_Operation: Tm3GroupOperation;
   function Get_MinKeyID: Integer;
   procedure Set_SearchMode(aValue: Tm3SearchMode);
   function Get_Sequential: Boolean;
   function ToTheLeftOf(const anOther: Im3AttrIndexDumper): Boolean;
   procedure Cleanup; override;
    {* Функция очистки полей объекта. }
   procedure ClearFields; override;
  public
   constructor Create(const aFiles: Im3FilesEnumerable;
    aIsDirect: Boolean); reintroduce;
   class function Make(const aFiles: Im3FilesEnumerable;
    aIsDirect: Boolean): Im3AttrIndexDumper; reintroduce;
   {$If NOT Defined(l3NoSRT)}
   function SetRefTo(var thePlace: Tm3AttrIndexDumperJoinProxy): Boolean; overload; {$If Defined(l3HasInl)}inline;{$IfEnd}
   {$IfEnd} // NOT Defined(l3NoSRT)
   function GetEnumerator: Im3AttrIndexElementEnumerator;
    {* Получает Enumerator для перебора элементов "контейнера".

Вся  эта конструкция совместима с "синтаксическим сахаром" вида:

for Item in Container do Process(Item);

Который поддерживается в "новых" Delphi.

См. http://mdp.garant.ru/pages/viewpage.action?pageId=152961307 }
  public
   property First: Il3RangeEnumerable
    read pm_GetFirst;
    {* Первый элемент. }
   property Last: Il3RangeEnumerable
    read pm_GetLast;
    {* Последний элемент. }
   property Count: Integer
    read pm_GetCount;
    {* Число элементов. }
 end;//Tm3AttrIndexDumperJoinProxy

 Tm3AttrIndexDumperJoin = class(Tl3CProtoObject, Im3AttrIndexDumper)
  private
   f_Main: Im3AttrIndexDumper;
   f_Delta: Im3AttrIndexDumper;
   f_IsDirect: Boolean;
   f_Modified: Il3RangeEnumerable;
   f_Versions: Im3IndexedVersions;
   f_Count: Integer;
  protected
   function pm_GetFirst: Il3RangeEnumerable;
   function pm_GetLast: Il3RangeEnumerable;
   function pm_GetCount: Integer;
   function ModifiedDocuments: Il3RangeEnumerable;
   function IndexedVersions: Im3IndexedVersions;
   function Get_Range: Tm3IDRange;
   function ValuesByKey(aKey: Integer): Il3RangeEnumerable;
   function TimeStamp: TDateTime;
   function Get_MaxKeyID: Integer;
   function Get_Operation: Tm3GroupOperation;
   function Get_MinKeyID: Integer;
   procedure Set_SearchMode(aValue: Tm3SearchMode);
   function Get_Sequential: Boolean;
   function ToTheLeftOf(const anOther: Im3AttrIndexDumper): Boolean;
   procedure Cleanup; override;
    {* Функция очистки полей объекта. }
   procedure ClearFields; override;
  public
   constructor Create(const aMain: Im3AttrIndexDumper;
    const aDelta: Im3AttrIndexDumper;
    aIsDirect: Boolean); reintroduce;
   class function MakePrim(const aMain: Im3AttrIndexDumper;
    const aDelta: Im3AttrIndexDumper;
    aIsDirect: Boolean): Im3AttrIndexDumper; reintroduce;
   class function Make(const aMain: Im3AttrIndexDumper;
    const aDelta: Im3AttrIndexDumper;
    aIsDirect: Boolean): Im3AttrIndexDumper;
   class function MakeFromMask(const aMask: TFileName;
    aIsDirect: Boolean): Im3AttrIndexDumper;
   class function MakeFromFilesEnum(const aFiles: Im3FilesEnumerator;
    aIsDirect: Boolean): Im3AttrIndexDumper;
   class function MakeFromFiles(const aFiles: Im3FilesEnumerable;
    aIsDirect: Boolean): Im3AttrIndexDumper;
   class function MakeFromList(const aList: Im3AttrIndexDumperEnumerable;
    aIsDirect: Boolean): Im3AttrIndexDumper;
   class function MakeForAttribute(const aBaseName: TFileName;
    anAttrID: Integer;
    aIsDirect: Boolean): Im3AttrIndexDumper; overload;
   class function MakeForAttribute(const aBaseName: TFileName;
    const anAttrName: AnsiString;
    aIsDirect: Boolean): Im3AttrIndexDumper; overload;
   class function MakeForAttribute(const aBaseName: TFileName;
    const anAttrID: array of Integer;
    aIsDirect: Boolean): Im3AttrIndexDumper; overload;
   class function MakeNameForAttr(const aName: Il3CString;
    const anAttrID: AnsiString;
    aIsDirect: Boolean): Il3CString;
   class function MakeFilesForAttr(const aFiles: Im3FilesEnumerable;
    const anAttrID: AnsiString;
    aIsDirect: Boolean): Im3FilesEnumerable;
   {$If NOT Defined(l3NoSRT)}
   function SetRefTo(var thePlace: Tm3AttrIndexDumperJoin): Boolean; overload; {$If Defined(l3HasInl)}inline;{$IfEnd}
   {$IfEnd} // NOT Defined(l3NoSRT)
   function GetEnumerator: Im3AttrIndexElementEnumerator;
    {* Получает Enumerator для перебора элементов "контейнера".

Вся  эта конструкция совместима с "синтаксическим сахаром" вида:

for Item in Container do Process(Item);

Который поддерживается в "новых" Delphi.

См. http://mdp.garant.ru/pages/viewpage.action?pageId=152961307 }
  public
   property First: Il3RangeEnumerable
    read pm_GetFirst;
    {* Первый элемент. }
   property Last: Il3RangeEnumerable
    read pm_GetLast;
    {* Последний элемент. }
   property Count: Integer
    read pm_GetCount;
    {* Число элементов. }
 end;//Tm3AttrIndexDumperJoin

 Tm3AttrIndexDumperJoinProxy_IndexElement = class(Tl3CProtoObject, Im3AttrIndexElement)
  private
   f_ID: Integer;
   f_Values: Il3RangeEnumerable;
  protected
   function Get_ID: Integer;
   function Get_Values: Il3RangeEnumerable;
   procedure Cleanup; override;
    {* Функция очистки полей объекта. }
   procedure ClearFields; override;
  public
   constructor Create(anID: Integer;
    const aValues: Il3RangeEnumerable); reintroduce;
   class function Make(anID: Integer;
    const aValues: Il3RangeEnumerable): Im3AttrIndexElement; reintroduce;
   {$If NOT Defined(l3NoSRT)}
   function SetRefTo(var thePlace: Tm3AttrIndexDumperJoinProxy_IndexElement): Boolean; overload; {$If Defined(l3HasInl)}inline;{$IfEnd}
   {$IfEnd} // NOT Defined(l3NoSRT)
 end;//Tm3AttrIndexDumperJoinProxy_IndexElement

 Tm3AttrIndexDumperJoinProxy_Enumerator = class(Tl3CProtoObject, Im3AttrIndexElementEnumerator)
  private
   f_Proxy: Tm3AttrIndexDumperJoinProxy;
   f_Current: Im3AttrIndexElement;
   f_Min: Integer;
   f_Max: Integer;
   f_Key: Integer;
   f_Values: Il3RangeEnumerable;
  protected
   function Get_Current: Im3AttrIndexElement;
   procedure Cleanup; override;
    {* Функция очистки полей объекта. }
   procedure ClearFields; override;
  public
   constructor Create(aProxy: Tm3AttrIndexDumperJoinProxy); reintroduce;
   class function Make(aProxy: Tm3AttrIndexDumperJoinProxy): Im3AttrIndexElementEnumerator; reintroduce;
   {$If NOT Defined(l3NoSRT)}
   function SetRefTo(var thePlace: Tm3AttrIndexDumperJoinProxy_Enumerator): Boolean; overload; {$If Defined(l3HasInl)}inline;{$IfEnd}
   {$IfEnd} // NOT Defined(l3NoSRT)
   function MoveNext: Boolean;
    {* Перемещается на следующий элемент контейнера. 

Возвращает true если элемент валидный. }
   function pCurrent: Im3AttrIndexElementEnumerator_PCurrentItemType;
    {* Указатель на текущий элемент. Он "закеширован" }
   function CanMoveNext: Boolean;
    {* Определяет - можно ли переместиться на следующий элемент контейнера. }
  public
   property Current: Im3AttrIndexElement
    read Get_Current;
    {* Текущий элемент. Он "закеширован" }
 end;//Tm3AttrIndexDumperJoinProxy_Enumerator

implementation

uses
 l3ImplUses
 , m3AttrIndexDumper
 , m3IndexedVersionsSortedJoin
 , Im3AttrIndexDumperByTimeStampList
 , Im3AttrIndexElementEnumeratorWithFilteredModified
 , Im3AttrIndexElementEnumeratorSortedJoin
 , Im3DirectAttrIndexElementEnumeratorSortedJoin
 , DateUtils
 , Math
 //#UC START# *67175F5B013Bimpl_uses*
 , Types
 {$IfDef Linux}
 {$IfDef AppClientSide}
 , ddClientBaseEngine
 {$EndIf AppClientSide}
 {$EndIf Linux}
 //, SysUtils
 , l3MinMax
 , l3Base
 , l3String
 , l3Stubs
 , l3FileUtils
 , l3Date
 , l3StringList
 , l3PrimString
 , l3SharedConstString
 , k2Attributes
 //, Im3DirectAttrIndexElementEnumeratorWithFilteredModified
 //, Im3DirectAttrIndexElementEnumeratorSortedJoin
 , m3DBHelper
 , m3StorageHolderList
 , l3Enumerators
 //#UC END# *67175F5B013Bimpl_uses*
;

type
 TIm3DirectAttrIndexElementEnumeratorWithFilteredModified = class(Tl3CProtoObject, Im3AttrIndexElementEnumerator)
  private
   f_Other: Im3AttrIndexElementEnumerator;
   f_Modified: Il3RangeEnumerable;
   f_ModifiedIt: Il3RangeEnumerator;
  protected
   function Get_Current: Im3AttrIndexElement;
   procedure ClearFields; override;
  public
   constructor Create(const anOther: Im3AttrIndexElementEnumerator;
    const aModified: Il3RangeEnumerable); reintroduce;
   class function Make(const anOther: Im3AttrIndexElementEnumerator;
    const aModified: Il3RangeEnumerable): Im3AttrIndexElementEnumerator; reintroduce;
   function MoveNext: Boolean;
    {* Перемещается на следующий элемент контейнера. 

Возвращает true если элемент валидный. }
   function pCurrent: Im3AttrIndexElementEnumerator_PCurrentItemType;
    {* Указатель на текущий элемент. Он "закеширован" }
   function CanMoveNext: Boolean;
    {* Определяет - можно ли переместиться на следующий элемент контейнера. }
  public
   property Current: Im3AttrIndexElement
    read Get_Current;
    {* Текущий элемент. Он "закеширован" }
 end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified

constructor Tm3AttrIndexDumperJoinProxy_IndexElement.Create(anID: Integer;
 const aValues: Il3RangeEnumerable);
//#UC START# *69BB4759017E_69BB46D900B8_var*
//#UC END# *69BB4759017E_69BB46D900B8_var*
begin
//#UC START# *69BB4759017E_69BB46D900B8_impl*
 inherited Create;
 f_ID := anID;
 f_Values := aValues;
//#UC END# *69BB4759017E_69BB46D900B8_impl*
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.Create

class function Tm3AttrIndexDumperJoinProxy_IndexElement.Make(anID: Integer;
 const aValues: Il3RangeEnumerable): Im3AttrIndexElement;
var
 l_Inst : Tm3AttrIndexDumperJoinProxy_IndexElement;
begin
 l_Inst := Create(anID, aValues);
 try
  Result := l_Inst;
 finally
  l_Inst.Free;
 end;//try..finally
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.Make

{$If NOT Defined(l3NoSRT)}
function Tm3AttrIndexDumperJoinProxy_IndexElement.SetRefTo(var thePlace: Tm3AttrIndexDumperJoinProxy_IndexElement): Boolean;
begin
 if (thePlace = Self) then
  Result := false
 else
 begin
  Result := true;
  thePlace.Free;
  thePlace := Self.Use;
 end;//thePlace = Self
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.SetRefTo
{$IfEnd} // NOT Defined(l3NoSRT)

function Tm3AttrIndexDumperJoinProxy_IndexElement.Get_ID: Integer;
//#UC START# *666AD8510295_69BB46D900B8get_var*
//#UC END# *666AD8510295_69BB46D900B8get_var*
begin
//#UC START# *666AD8510295_69BB46D900B8get_impl*
 Result := f_ID;
//#UC END# *666AD8510295_69BB46D900B8get_impl*
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.Get_ID

function Tm3AttrIndexDumperJoinProxy_IndexElement.Get_Values: Il3RangeEnumerable;
//#UC START# *666ADD3000B4_69BB46D900B8get_var*
//#UC END# *666ADD3000B4_69BB46D900B8get_var*
begin
//#UC START# *666ADD3000B4_69BB46D900B8get_impl*
 Result := f_Values;
//#UC END# *666ADD3000B4_69BB46D900B8get_impl*
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.Get_Values

procedure Tm3AttrIndexDumperJoinProxy_IndexElement.Cleanup;
 {* Функция очистки полей объекта. }
//#UC START# *479731C50290_69BB46D900B8_var*
//#UC END# *479731C50290_69BB46D900B8_var*
begin
//#UC START# *479731C50290_69BB46D900B8_impl*
 f_Values := nil;
 inherited;
//#UC END# *479731C50290_69BB46D900B8_impl*
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.Cleanup

procedure Tm3AttrIndexDumperJoinProxy_IndexElement.ClearFields;
begin
 f_Values := nil;
 inherited;
end;//Tm3AttrIndexDumperJoinProxy_IndexElement.ClearFields

constructor Tm3AttrIndexDumperJoinProxy_Enumerator.Create(aProxy: Tm3AttrIndexDumperJoinProxy);
//#UC START# *69BB238C0112_69BB232003AC_var*
//#UC END# *69BB238C0112_69BB232003AC_var*
begin
//#UC START# *69BB238C0112_69BB232003AC_impl*
 Assert(aProxy <> nil);
 inherited Create;
 aProxy.SetRefTo(f_Proxy);
 f_Proxy.Set_SearchMode(m3_smSeq);
 f_Min := f_Proxy.Get_MinKeyID;
 f_Max := f_Proxy.Get_MaxKeyID;
 Assert(f_Min > Low(f_Min));
 f_Key := f_Min - 1;
 f_Proxy.CheckOther;
//#UC END# *69BB238C0112_69BB232003AC_impl*
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.Create

class function Tm3AttrIndexDumperJoinProxy_Enumerator.Make(aProxy: Tm3AttrIndexDumperJoinProxy): Im3AttrIndexElementEnumerator;
var
 l_Inst : Tm3AttrIndexDumperJoinProxy_Enumerator;
begin
 l_Inst := Create(aProxy);
 try
  Result := l_Inst;
 finally
  l_Inst.Free;
 end;//try..finally
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.Make

{$If NOT Defined(l3NoSRT)}
function Tm3AttrIndexDumperJoinProxy_Enumerator.SetRefTo(var thePlace: Tm3AttrIndexDumperJoinProxy_Enumerator): Boolean;
begin
 if (thePlace = Self) then
  Result := false
 else
 begin
  Result := true;
  thePlace.Free;
  thePlace := Self.Use;
 end;//thePlace = Self
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.SetRefTo
{$IfEnd} // NOT Defined(l3NoSRT)

function Tm3AttrIndexDumperJoinProxy_Enumerator.MoveNext: Boolean;
 {* Перемещается на следующий элемент контейнера. 

Возвращает true если элемент валидный. }
//#UC START# *5ACB6780016E_69BB232003AC_var*
var
 l_Values : Il3RangeEnumerable;
//#UC END# *5ACB6780016E_69BB232003AC_var*
begin
//#UC START# *5ACB6780016E_69BB232003AC_impl*
 Result := false;
 while CanMoveNext do
 begin
  Inc(f_Key);
  if (f_Key <= f_Max) then
  begin
   l_Values := f_Proxy.ValuesByKey(f_Key);
   f_Values := l_Values;
   // - себе на всякий случай запомним
   if (l_Values <> nil) then
   // - ключу соответствует непустое значение
   begin
    f_Current := Tm3AttrIndexDumperJoinProxy_IndexElement.Make(f_Key, l_Values);
    Result := true;
    // - элемент найден
    Exit;
    // - выходим
   end;//l_Values <> nil
  end//f_Key <= f_Max
  else
  // - ушли за конец
   break;
   // - отваливаем
 end;//while CanMoveNext
 if not Result then
 begin
  f_Values := nil;
  f_Current := nil;
  if (f_Proxy <> nil) then
   f_Proxy.Set_SearchMode(m3_smRandom);
 end;//not Result
//#UC END# *5ACB6780016E_69BB232003AC_impl*
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.MoveNext

function Tm3AttrIndexDumperJoinProxy_Enumerator.pCurrent: Im3AttrIndexElementEnumerator_PCurrentItemType;
 {* Указатель на текущий элемент. Он "закеширован" }
//#UC START# *5C4EFC140038_69BB232003AC_var*
//#UC END# *5C4EFC140038_69BB232003AC_var*
begin
//#UC START# *5C4EFC140038_69BB232003AC_impl*
 Result := @f_Current;
//#UC END# *5C4EFC140038_69BB232003AC_impl*
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.pCurrent

function Tm3AttrIndexDumperJoinProxy_Enumerator.CanMoveNext: Boolean;
 {* Определяет - можно ли переместиться на следующий элемент контейнера. }
//#UC START# *5E60D4B30164_69BB232003AC_var*
//#UC END# *5E60D4B30164_69BB232003AC_var*
begin
//#UC START# *5E60D4B30164_69BB232003AC_impl*
 Result := (f_Key < f_Max);
//#UC END# *5E60D4B30164_69BB232003AC_impl*
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.CanMoveNext

function Tm3AttrIndexDumperJoinProxy_Enumerator.Get_Current: Im3AttrIndexElement;
//#UC START# *6739C0470374_69BB232003ACget_var*
//#UC END# *6739C0470374_69BB232003ACget_var*
begin
//#UC START# *6739C0470374_69BB232003ACget_impl*
 Result := f_Current;
//#UC END# *6739C0470374_69BB232003ACget_impl*
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.Get_Current

procedure Tm3AttrIndexDumperJoinProxy_Enumerator.Cleanup;
 {* Функция очистки полей объекта. }
//#UC START# *479731C50290_69BB232003AC_var*
//#UC END# *479731C50290_69BB232003AC_var*
begin
//#UC START# *479731C50290_69BB232003AC_impl*
 f_Values := nil;
 if (f_Proxy <> nil) then
  f_Proxy.Set_SearchMode(m3_smRandom);
 FreeAndNil(f_Proxy);
 inherited;
//#UC END# *479731C50290_69BB232003AC_impl*
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.Cleanup

procedure Tm3AttrIndexDumperJoinProxy_Enumerator.ClearFields;
begin
 f_Current := nil;
 f_Values := nil;
 inherited;
end;//Tm3AttrIndexDumperJoinProxy_Enumerator.ClearFields

constructor Tm3AttrIndexDumperJoinProxy.Create(const aFiles: Im3FilesEnumerable;
 aIsDirect: Boolean);
//#UC START# *69B94B46031D_69B94A1A036F_var*
//#UC END# *69B94B46031D_69B94A1A036F_var*
begin
//#UC START# *69B94B46031D_69B94A1A036F_impl*
 inherited Create;
 f_Files := aFiles;
 f_TimeStamp := BadDateTime;
 f_IsDirect := aIsDirect;
 f_Count := -1;
 f_MinKeyID := High(f_MinKeyID);
 f_MaxKeyID := Low(f_MaxKeyID);
//#UC END# *69B94B46031D_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.Create

class function Tm3AttrIndexDumperJoinProxy.Make(const aFiles: Im3FilesEnumerable;
 aIsDirect: Boolean): Im3AttrIndexDumper;
var
 l_Inst : Tm3AttrIndexDumperJoinProxy;
begin
 l_Inst := Create(aFiles, aIsDirect);
 try
  Result := l_Inst;
 finally
  l_Inst.Free;
 end;//try..finally
end;//Tm3AttrIndexDumperJoinProxy.Make

procedure Tm3AttrIndexDumperJoinProxy.CheckOther;
//#UC START# *69B94FF003CF_69B94A1A036F_var*
var
 l_It : Im3FilesEnumerator;
 l_Count : Integer;
 l_Other : Im3AttrIndexDumper;
//#UC END# *69B94FF003CF_69B94A1A036F_var*
begin
//#UC START# *69B94FF003CF_69B94A1A036F_impl*
 if (f_OtherJoin = nil) then
 begin
  Assert(Self.f_Files <> nil);
  l_It := Self.f_Files.GetEnumerator;
  if (l_It <> nil) then
  begin
   if (f_Last <> nil) then
   begin
    l_Count := Self.f_Files.Count - 1;
    while (l_Count > 0)
          AND l_It.MoveNext
          do
    begin
     Dec(l_Count);
     if (f_Others = nil) then
      f_Others := TIm3AttrIndexDumperList.Create;
     l_Other := Tm3AttrIndexDumper.Make(l_It.Current.AsString);
     l_Other.SearchMode := Self.f_SearchMode;
     f_Others.Add(l_Other);
    end;//l_It.MoveNext
    if (f_Others = nil) then
     f_Others := TIm3AttrIndexDumperList.Create;
    f_Last.SearchMode := Self.f_SearchMode;
    f_Others.Add(f_Last);
   end//f_Last <> nil
   else
   begin
    while l_It.MoveNext do
    begin
     if (f_Others = nil) then
      f_Others := TIm3AttrIndexDumperList.Create;
     l_Other := Tm3AttrIndexDumper.Make(l_It.Current.AsString);
     l_Other.SearchMode := Self.f_SearchMode;
     f_Others.Add(l_Other);
    end;//l_It.MoveNext
   end;//f_Last <> nil
  end;//l_It <> nil
  if (f_Others <> nil) then
   f_OtherJoin := Tm3AttrIndexDumperJoin.MakeFromList(f_Others.AsEnumerable, f_IsDirect)
  else
   f_OtherJoin := f_Last;
  //f_OtherJoin := Tm3AttrIndexDumperJoin.MakeFromFilesEnum(Self.f_Files.GetEnumerator, Self.f_IsDirect);
  if (f_OtherJoin = nil) then
  // - тут делаем NULL-объект
   f_OtherJoin := Tm3AttrIndexDumper.Make('');
 end;//f_OtherJoin = nil
//#UC END# *69B94FF003CF_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.CheckOther

{$If NOT Defined(l3NoSRT)}
function Tm3AttrIndexDumperJoinProxy.SetRefTo(var thePlace: Tm3AttrIndexDumperJoinProxy): Boolean;
begin
 if (thePlace = Self) then
  Result := false
 else
 begin
  Result := true;
  thePlace.Free;
  thePlace := Self.Use;
 end;//thePlace = Self
end;//Tm3AttrIndexDumperJoinProxy.SetRefTo
{$IfEnd} // NOT Defined(l3NoSRT)

function Tm3AttrIndexDumperJoinProxy.pm_GetFirst: Il3RangeEnumerable;
//#UC START# *47D8233603DD_69B94A1A036Fget_var*
//#UC END# *47D8233603DD_69B94A1A036Fget_var*
begin
//#UC START# *47D8233603DD_69B94A1A036Fget_impl*
 Result := nil;
 l3NI;
//#UC END# *47D8233603DD_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.pm_GetFirst

function Tm3AttrIndexDumperJoinProxy.pm_GetLast: Il3RangeEnumerable;
//#UC START# *47D823570315_69B94A1A036Fget_var*
//#UC END# *47D823570315_69B94A1A036Fget_var*
begin
//#UC START# *47D823570315_69B94A1A036Fget_impl*
 Result := nil;
 l3NI;
//#UC END# *47D823570315_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.pm_GetLast

function Tm3AttrIndexDumperJoinProxy.pm_GetCount: Integer;
//#UC START# *4BB08B8902F2_69B94A1A036Fget_var*
{$IfDef nsTest}
var
 l_CountForCompare : Integer;
{$EndIf nsTest}
var
 {$IfDef nsTest}
 l_Total : Integer;
 l_PartCount : Integer;
 {$EndIf nsTest}
 l_Index : Integer;
 l_Modified : Il3RangeEnumerable;
 l_IE : Il3IntegerEnumerable;
//#UC END# *4BB08B8902F2_69B94A1A036Fget_var*
begin
//#UC START# *4BB08B8902F2_69B94A1A036Fget_impl*
 if (f_Count < 0) then
 begin
  CheckOther;
  if f_IsDirect then
  begin
   {$IfDef nsTest}
   l_CountForCompare := f_OtherJoin.Count;
   {$EndIf nsTest}
   if (f_Others = nil) then
   begin
    f_Count := f_OtherJoin.Count;
   end//f_Others = nil
   else
   begin
    if (f_Modified = nil) then
    begin
     {$IfDef nsTest}
     l_Total := 0;
     {$EndIf nsTest}
     for l_Index := 0 to f_Others.Count - 1 do
     begin
      l_Modified := f_Others[l_Index].ModifiedDocuments;
      {$IfDef nsTest}
      l_PartCount := 0;
      if (l_Modified <> nil) then
      begin
       l_IE := Il3RangeEnumerable_ToIntegerEnumerable(l_Modified);
       if (l_IE = nil) then
        l_PartCount := 0
       else
        l_PartCount := l_IE.Count;
       //l_PartCount := l_Modified.Count;
       Inc(l_Total, l_PartCount);
      end;//l_Modified <> nil
      {$EndIf nsTest}
      f_Modified := Il3RangeEnumerable_JoinWithOther(f_Modified, l_Modified);
     end;//for l_Index
     if (f_Modified = nil) then
      f_Count := 0
     else
     begin
      l_IE := Il3RangeEnumerable_ToIntegerEnumerable(f_Modified);
      if (l_IE = nil) then
       f_Count := 0
      else
       f_Count := l_IE.Count;
      //f_Count := f_Modified.Count;
     end;//f_Modified = nil
    end;//f_Modified = nil
    {$IfDef nsTest}
    if (l_CountForCompare <> f_Count) then
    begin
     Assert(l_CountForCompare <= f_Count);
     l3System.Msg2Log(Self.f_Files.First.AsString
                      + ' CountForCompare: '
                      + IntToStr(l_CountForCompare)
                      + ' Count: '
                      + IntToStr(f_Count)
                      + ' Total: '
                      + IntToStr(l_Total)
                      );
     {
      Понятно почему тут "рассинхронизация".
      Потому что в Builder'ах ЕДИНЫЕ modified и range
      для всех индексов.
     }
    end;//l_CountForCompare <> f_Count
    {$EndIf nsTest}
   end;//f_Others = nil
  end//f_IsDirect
  else
   f_Count := f_OtherJoin.Count;
 end;//f_Count < 0
 Result := f_Count;
//#UC END# *4BB08B8902F2_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.pm_GetCount

function Tm3AttrIndexDumperJoinProxy.GetEnumerator: Im3AttrIndexElementEnumerator;
 {* Получает Enumerator для перебора элементов "контейнера".

Вся  эта конструкция совместима с "синтаксическим сахаром" вида:

for Item in Container do Process(Item);

Который поддерживается в "новых" Delphi.

См. http://mdp.garant.ru/pages/viewpage.action?pageId=152961307 }
//#UC START# *5AB8C6B0003A_69B94A1A036F_var*

{$If Defined(nsTest) OR Defined(FPC)}
 {$Define use_Proxy_Enumerator}
{$IfEnd} // {$If Defined(nsTest) OR Defined(FPC)}

var
 l_PartsCount : Integer;
 l_D : Integer;
 l_Min : Integer;
 l_Max : Integer;
 l_Count : Integer;
 l_Div : Integer;
//#UC END# *5AB8C6B0003A_69B94A1A036F_var*
begin
//#UC START# *5AB8C6B0003A_69B94A1A036F_impl*
 CheckOther;
 if f_IsDirect then
 begin
  {$IfDef use_Proxy_Enumerator}
  //Result := f_OtherJoin.GetEnumerator;
  if (f_Others = nil) then
   l_PartsCount := 0
  else
   l_PartsCount := f_Others.Count;

  if (l_PartsCount > 200) then
  // - типа частей до хера
  begin
   Result := Tm3AttrIndexDumperJoinProxy_Enumerator.Make(Self);
   Exit;
  end;//l_PartsCount > 200

  l_Min := Self.Get_MinKeyID;
  l_Max := Self.Get_MaxKeyID;
  l_D := l_Max - l_Min;
  if (l_D > 10000) then
  // - дельта типа до хера большая
  begin
   l_Count := Self.Count;
   // - попробуем оценить максимальную дельту
   if (l_Count >= l_D) then
   // - дельта совсем сравнима с числом элементов
    Result := Tm3AttrIndexDumperJoinProxy_Enumerator.Make(Self)
   else
   begin
    if (l_Count = 0) then
    // - вроде как документов нет
     Result := f_OtherJoin.GetEnumerator
     // - давайте попробуем работат по-старому
    else
    begin
     l_Div := l_D div l_Count;
     // - считаем пропорцию
     if (l_Div > 5) then
      Result := f_OtherJoin.GetEnumerator
      // - давайте попробуем работат по-старому
     else
      Result := Tm3AttrIndexDumperJoinProxy_Enumerator.Make(Self);
      // - тут работаем по-новому
    end;//l_Count = 0
   end;//l_Count >= l_D
  end//l_D > 10000
  else
   Result := Tm3AttrIndexDumperJoinProxy_Enumerator.Make(Self);
  {$Else  use_Proxy_Enumerator}
  Result := f_OtherJoin.GetEnumerator;
  {$EndIf use_Proxy_Enumerator}
 end//f_IsDirect
 else
  Result := f_OtherJoin.GetEnumerator;
//#UC END# *5AB8C6B0003A_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.GetEnumerator

function Tm3AttrIndexDumperJoinProxy.ModifiedDocuments: Il3RangeEnumerable;
//#UC START# *5B2B66730195_69B94A1A036F_var*
//#UC END# *5B2B66730195_69B94A1A036F_var*
begin
//#UC START# *5B2B66730195_69B94A1A036F_impl*
 if (f_Modified <> nil) then
  Result := f_Modified
 else
 begin
  CheckOther;
  Result := f_OtherJoin.ModifiedDocuments;
 end;//f_Modified <> nil
//#UC END# *5B2B66730195_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.ModifiedDocuments

function Tm3AttrIndexDumperJoinProxy.IndexedVersions: Im3IndexedVersions;
//#UC START# *5B2E194003AE_69B94A1A036F_var*
//#UC END# *5B2E194003AE_69B94A1A036F_var*
begin
//#UC START# *5B2E194003AE_69B94A1A036F_impl*
 CheckOther;
 Result := f_OtherJoin.IndexedVersions;
//#UC END# *5B2E194003AE_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.IndexedVersions

function Tm3AttrIndexDumperJoinProxy.Get_Range: Tm3IDRange;
//#UC START# *5DA9C0270207_69B94A1A036Fget_var*
//#UC END# *5DA9C0270207_69B94A1A036Fget_var*
begin
//#UC START# *5DA9C0270207_69B94A1A036Fget_impl*
 CheckOther;
 Result := f_OtherJoin.Range;
//#UC END# *5DA9C0270207_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.Get_Range

function Tm3AttrIndexDumperJoinProxy.ValuesByKey(aKey: Integer): Il3RangeEnumerable;
//#UC START# *66B4B9740318_69B94A1A036F_var*
var
 l_Others : TIm3AttrIndexDumperList;
 l_Index : Integer;
 l_Range : Tm3IDRange;
 l_Modified : Il3RangeEnumerable;
//#UC END# *66B4B9740318_69B94A1A036F_var*
begin
//#UC START# *66B4B9740318_69B94A1A036F_impl*
 CheckOther;
(* if (f_OtherJoin = nil) then
 // - это ловушка для отладки
  CheckOther;*)
 if f_IsDirect then
 // https://mdp.garant.ru/pages/viewpage.action?pageId=902334979
 begin
  l_Others := f_Others;
  if (l_Others = nil) then
  // - хм... у нас нет Others, ну ладно, бывает
  // тогда "попробуем как при бабушке" (С)
{
  «Теперь всё будет, как при бабушке» — это выражение, означающее возвращение к прежнему, более привычному порядку.

  Возникновение фразы связано с восшествием на престол Александра I после убийства его отца Павла I в 1801 году. Александр хотел восстановить порядки, существовавшие при его бабушке, Екатерине II. Эти слова стали выражением надежды на возвращение к стабильности, справедливости и просвещённому абсолютизму, который был характерен для екатерининской эпохи.

  Хотя фраза сохранилась в культурной памяти, она не получила широкого распространения, но используется в разговорной речи в значении «всё будет, как прежде».
  }
   Result := f_OtherJoin.ValuesByKey(aKey)
  else
  begin
   // - тут попрбуем по-новому
   Assert(l_Others <> nil);
   // - возможно тут ещё надо оценить l_Others.Count да и не дёргаться
   if true then
   // - тут пробуем по-новому
   begin
    // Тут "списывать будем" из Tm3AttrIndexDumperJoin.ValuesByKey
    Result := nil;
    // - пока не нашли
    for l_Index := l_Others.Count - 1 downto 0 do
    // - перебираем с конца, ибо дельты так сортированы
    begin
     l_Range := l_Others.ItemSlot(l_Index).Range;
     if not l_Range.Has(aKey) then
     // - если не попали в диапазон, то значит это не в нашем куске
      continue
      // - на другой виток цикла
     else
     begin
      l_Modified := l_Others.ItemSlot(l_Index).ModifiedDocuments;
      // - наши документы
      if (l_Modified = nil) then
      // - нет наших документов
       continue
       // - на другой виток цикла
      else
      if not l_Modified.Has(Tl3Range_C(aKey)) then
      // - aKey не в наших документах
       continue
       // - на другой виток цикла
      else
      begin
       Result := l_Others.ItemSlot(l_Index).ValuesByKey(aKey);
       // - а вот тут берём наши значения
       Exit;
       // - ну нам точно тут пора отваливать
      end;//else
     end;//not l_Range.Has(aKey)
    end;//for l_Index
   end//true
   else
    Result := f_OtherJoin.ValuesByKey(aKey);
  end;//l_Others = nil
 end//f_IsDirect
 else
  Result := f_OtherJoin.ValuesByKey(aKey);
//#UC END# *66B4B9740318_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.ValuesByKey

function Tm3AttrIndexDumperJoinProxy.TimeStamp: TDateTime;
//#UC START# *66C46E6801D3_69B94A1A036F_var*
//#UC END# *66C46E6801D3_69B94A1A036F_var*
begin
//#UC START# *66C46E6801D3_69B94A1A036F_impl*
 if not SameValue(f_TimeStamp, BadDateTime) then
  Result := f_TimeStamp
 else
 begin
  if (f_OtherJoin <> nil) then
   Result := f_OtherJoin.TimeStamp
   // -- берём timestamp "от всего объединения", раз уж оно создано
  else
  begin
   if (f_Last = nil) then
   begin
    Assert(Self.f_Files <> nil);
    f_Last := Tm3AttrIndexDumper.Make(f_Files.Last.AsString);
    f_Last.SearchMode := Self.f_SearchMode;
   end;//f_Last = nil
   Result := f_Last.TimeStamp;
   // -- берём timestamp от самого "крайнего справа".
  end;//f_OtherJoin <> nil
  f_TimeStamp := Result;
 end;//not SameValue(f_TimeStamp, 0)
//#UC END# *66C46E6801D3_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.TimeStamp

function Tm3AttrIndexDumperJoinProxy.Get_MaxKeyID: Integer;
//#UC START# *671D18210090_69B94A1A036Fget_var*
//#UC END# *671D18210090_69B94A1A036Fget_var*
begin
//#UC START# *671D18210090_69B94A1A036Fget_impl*
 if (f_MaxKeyID = Low(f_MaxKeyID)) then
 begin
  CheckOther;
  f_MaxKeyID := f_OtherJoin.MaxKeyID;
 end;//f_MaxKeyID = Low(f_MaxKeyID)
 Result := f_MaxKeyID;
//#UC END# *671D18210090_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.Get_MaxKeyID

function Tm3AttrIndexDumperJoinProxy.Get_Operation: Tm3GroupOperation;
//#UC START# *68E135160346_69B94A1A036Fget_var*
//#UC END# *68E135160346_69B94A1A036Fget_var*
begin
//#UC START# *68E135160346_69B94A1A036Fget_impl*
 CheckOther;
 Result := f_OtherJoin.Operation;
//#UC END# *68E135160346_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.Get_Operation

function Tm3AttrIndexDumperJoinProxy.Get_MinKeyID: Integer;
//#UC START# *69B14AAE0155_69B94A1A036Fget_var*
//#UC END# *69B14AAE0155_69B94A1A036Fget_var*
begin
//#UC START# *69B14AAE0155_69B94A1A036Fget_impl*
 if (f_MinKeyID = High(f_MinKeyID)) then
 begin
  CheckOther;
  f_MinKeyID := f_OtherJoin.MinKeyID;
 end;//f_MinKeyID = High(f_MinKeyID)
 Result := f_MinKeyID;
//#UC END# *69B14AAE0155_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.Get_MinKeyID

procedure Tm3AttrIndexDumperJoinProxy.Set_SearchMode(aValue: Tm3SearchMode);
//#UC START# *69BBB8A101BA_69B94A1A036Fset_var*
var
 l_Index : Integer;
//#UC END# *69BBB8A101BA_69B94A1A036Fset_var*
begin
//#UC START# *69BBB8A101BA_69B94A1A036Fset_impl*
 Self.f_SearchMode := aValue;
 if f_IsDirect then
 begin
  Case Self.f_SearchMode of
   m3_smSeq:
   begin
    CheckOther;
    if (f_Others <> nil) then
    begin
     for l_Index := 0 to Pred(f_Others.Count) do
     begin
      f_Others[l_Index].SearchMode := Self.f_SearchMode;
     end;//for l_Index
    end;//f_Others <> nil
   end;//m3_smSeq
   m3_smRandom:
   begin
    if (f_Others <> nil) then
    begin
     for l_Index := 0 to Pred(f_Others.Count) do
     begin
      f_Others[l_Index].SearchMode := Self.f_SearchMode;
     end;//for l_Index
    end;//f_Others <> nil
   end;//m3_smRandom
  end;//Case aValue
 end;//f_IsDirect
//#UC END# *69BBB8A101BA_69B94A1A036Fset_impl*
end;//Tm3AttrIndexDumperJoinProxy.Set_SearchMode

function Tm3AttrIndexDumperJoinProxy.Get_Sequential: Boolean;
//#UC START# *69C1D45B01E5_69B94A1A036Fget_var*
//#UC END# *69C1D45B01E5_69B94A1A036Fget_var*
begin
//#UC START# *69C1D45B01E5_69B94A1A036Fget_impl*
 CheckOther;
 Result := f_OtherJoin.Sequential;
//#UC END# *69C1D45B01E5_69B94A1A036Fget_impl*
end;//Tm3AttrIndexDumperJoinProxy.Get_Sequential

function Tm3AttrIndexDumperJoinProxy.ToTheLeftOf(const anOther: Im3AttrIndexDumper): Boolean;
//#UC START# *69C1DE0300B2_69B94A1A036F_var*
//#UC END# *69C1DE0300B2_69B94A1A036F_var*
begin
//#UC START# *69C1DE0300B2_69B94A1A036F_impl*
 CheckOther;
 Result := f_OtherJoin.ToTheLeftOf(anOther);
//#UC END# *69C1DE0300B2_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.ToTheLeftOf

procedure Tm3AttrIndexDumperJoinProxy.Cleanup;
 {* Функция очистки полей объекта. }
//#UC START# *479731C50290_69B94A1A036F_var*
//#UC END# *479731C50290_69B94A1A036F_var*
begin
//#UC START# *479731C50290_69B94A1A036F_impl*
 f_Last := nil;
 f_OtherJoin := nil;
 f_Files := nil;
 f_TimeStamp := 0;
 f_IsDirect := false;
 FreeAndNil(f_OthersForSeq);
 FreeAndNil(f_Others);
 inherited;
//#UC END# *479731C50290_69B94A1A036F_impl*
end;//Tm3AttrIndexDumperJoinProxy.Cleanup

procedure Tm3AttrIndexDumperJoinProxy.ClearFields;
begin
 f_Last := nil;
 f_OtherJoin := nil;
 f_Files := nil;
 f_Modified := nil;
 inherited;
end;//Tm3AttrIndexDumperJoinProxy.ClearFields

constructor Tm3AttrIndexDumperJoin.Create(const aMain: Im3AttrIndexDumper;
 const aDelta: Im3AttrIndexDumper;
 aIsDirect: Boolean);
//#UC START# *67175FC70110_67175F5B013B_var*
//#UC END# *67175FC70110_67175F5B013B_var*
begin
//#UC START# *67175FC70110_67175F5B013B_impl*
 f_Count := -1;
 Assert(aMain <> nil);
 Assert(aDelta <> nil);
 f_Main := aMain;
 f_Delta := aDelta;
 f_IsDirect := aIsDirect;
 if false then
 // - пока отключаем, ибо падает на D:\SuperBase\garant\bserv001#ExternalHandle_(d)#
  if CompareDateTime(aMain.TimeStamp, aDelta.TimeStamp) = GreaterThanValue then
   raise Exception.Create(l3ForceUTF8FPC('Ошибка: Основной индекс новее дельты'));
 inherited Create;
//#UC END# *67175FC70110_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.Create

class function Tm3AttrIndexDumperJoin.MakePrim(const aMain: Im3AttrIndexDumper;
 const aDelta: Im3AttrIndexDumper;
 aIsDirect: Boolean): Im3AttrIndexDumper;
var
 l_Inst : Tm3AttrIndexDumperJoin;
begin
 l_Inst := Create(aMain, aDelta, aIsDirect);
 try
  Result := l_Inst;
 finally
  l_Inst.Free;
 end;//try..finally
end;//Tm3AttrIndexDumperJoin.MakePrim

class function Tm3AttrIndexDumperJoin.Make(const aMain: Im3AttrIndexDumper;
 const aDelta: Im3AttrIndexDumper;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *6717604D0301_67175F5B013B_var*
//#UC END# *6717604D0301_67175F5B013B_var*
begin
//#UC START# *6717604D0301_67175F5B013B_impl*
 if (aMain = nil) then
  Result := aDelta
 else
 if (aDelta = nil) then
  Result := aMain
 else
 begin
  Result := MakePrim(aMain, aDelta, aIsDirect);
 end;//aDelta = nil
//#UC END# *6717604D0301_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.Make

class function Tm3AttrIndexDumperJoin.MakeFromMask(const aMask: TFileName;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *671768C9027E_67175F5B013B_var*

var
 l_IsEtalon : Boolean;
var
 l_L : TIm3AttrIndexDumperByTimeStampList;
 l_Purge : Im3FilesEnumerable;

 function DoFile(const aFileName: string): Boolean;
 var
  l_Dumper : Im3AttrIndexDumper;
  l_Ext : TFileName;
  l_S : Tl3PrimString;
 begin//DoFile
  Result := true;
  if FileExists(aFileName) then
  begin
   l_Ext := ExtractFileExt(aFileName);
   if (l_Ext <> '.idxdeltas') then
   begin
    if l_IsEtalon then
    begin
     if not (Pos('.etalon.', ExtractFileName(aFileName)) > 0) then
      Exit;
    end//l_IsEtalon
    else
    begin
     if (Pos('.etalon.', ExtractFileName(aFileName)) > 0) then
      Exit;
    end;//l_IsEtalon
    if (l_Purge <> nil) then
    begin
     if not l_Purge.Empty then
     begin
      l_S := Tl3SharedConstString.CreateShared(aFileName);
      try
       if l_Purge.Has(l_S) then
        Exit;
      finally
       FreeAndNil(l_S);
      end;//try..finally
     end;//not l_Purge.Empty
    end;//l_Purge <> nil
    l_Dumper := nil;
    try
     l_Dumper := Tm3AttrIndexDumper.Make(aFileName);
    except
     l_Dumper := nil;
    end;//try..finally
    if (l_Dumper <> nil) then
    begin
     l_L.Add(l_Dumper);
    end;//l_Dumper <> nil
   end;//l_Ext <> '.idxdeltas'
  end;//FileExists(aFileName)
 end;//DoFile

(*var
l_Dumper : Im3AttrIndexDumper;
l_NewDumper : Im3AttrIndexDumper;

 function DoFile(const aFileName: string): Boolean;
 begin//DoFile
  Result := true;
  if FileExists(aFileName) then
  begin
   l_NewDumper := Tm3AttrIndexDumper.Make(aFileName);
   if (l_NewDumper <> nil) then
   begin
    if (l_Dumper = nil) then
     l_Dumper := l_NewDumper
    else
     l_Dumper := Self.Make(l_Dumper, l_NewDumper, aIsDirect);
   end;//l_NewDumper <> nil
  end;//FileExists(aFileName)
 end;//DoFile*)

var
 l_Folder : TFileName;
 l_Mask : TFileName;
 l_FPStub : Pointer;
 l_Helper : Im3DBHelper;
 l_BaseName : TFileName;
// l_Index : Integer;
//#UC END# *671768C9027E_67175F5B013B_var*
begin
//#UC START# *671768C9027E_67175F5B013B_impl*
 Result := nil;
 //l_Dumper := nil;
 //l_NewDumper := nil;
 l_BaseName := ChangeFileExt(aMask, '');
 l_Purge := nil;
 try
  l_Helper := Tm3DBHelper.Make(l_BaseName);
  try
   l_Purge := l_Helper.GetIndexForPurgeFiles;
  finally
   l_Helper := nil;
  end;//try..finally
  l_Folder := ExtractFilePath(aMask);
  l_Mask := ExtractFileName(aMask);
  l_IsEtalon := Pos('.etalon.', l_Mask) > 0;
  l_L := TIm3AttrIndexDumperByTimeStampList.CreateSorted;
  try
   l_FPStub := l3LocalStub(@DoFile);
   try
    ProcessFilesWithMask(l_Folder, l_Mask, TFileProcessingFunc(l_FPStub));
   finally
    l3FreeLocalStub(l_FPStub);
   end;//try..finally
   Result := Self.MakeFromList(l_L.AsEnumerable, aIsDirect);
 (*  for l_Index := 0 to Pred(l_L.Count) do
   begin
    if (Result = nil) then
     Result := l_L.Items[l_Index]
    else
     Result := Self.Make(Result, l_L.Items[l_Index], aIsDirect);
   end;//for l_Index*)
  finally
   FreeAndNil(l_L);
  end;//try..finally
  //Result := l_Dumper;
 finally
  l_Purge := nil;
 end;//try..finallt
//#UC END# *671768C9027E_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeFromMask

class function Tm3AttrIndexDumperJoin.MakeFromFilesEnum(const aFiles: Im3FilesEnumerator;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *6717CE9E016D_67175F5B013B_var*
var
 l_L : TIm3AttrIndexDumperByTimeStampList;
var
 //l_Dumper : Im3AttrIndexDumper;
 l_NewDumper : Im3AttrIndexDumper;
//#UC END# *6717CE9E016D_67175F5B013B_var*
begin
//#UC START# *6717CE9E016D_67175F5B013B_impl*
 Result := nil;
 if (aFiles <> nil) then
 begin
  //l_Dumper := nil;
  l_L := TIm3AttrIndexDumperByTimeStampList.CreateSorted;
  try
   while aFiles.MoveNext do
   begin
    {$If not Defined(Archi) OR not Defined(Linux)}
    {$If not Defined(Linux) OR not Defined(nsTest)}
    {$If not Defined(NanoServer)}
    {$If not Defined(m3DBCheck)}
    l3System.Msg2Log('Tm3AttrIndexDumperJoin.MakeFromFilesEnum: ' + aFiles.Current.AsString);
    {$IfEnd}
    {$IfEnd}
    {$IfEnd}
    {$IfEnd}
    l_NewDumper := Tm3AttrIndexDumper.Make(aFiles.Current.AsString);
    if (l_NewDumper <> nil) then
    begin
     l_L.Add(l_NewDumper);
(*     if (l_Dumper = nil) then
      l_Dumper := l_NewDumper
     else
      l_Dumper := Make(l_Dumper, l_NewDumper, aIsDirect);*)
    end;//l_NewDumper <> nil
   end;//while aFiles.MoveNext
   //Result := l_Dumper;
   Result := Self.MakeFromList(l_L.AsEnumerable, aIsDirect);
  finally
   FreeAndNil(l_L);
  end;//try..finally
 end;//aFiles <> nil
//#UC END# *6717CE9E016D_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeFromFilesEnum

class function Tm3AttrIndexDumperJoin.MakeFromFiles(const aFiles: Im3FilesEnumerable;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *6717CECE007E_67175F5B013B_var*
//#UC END# *6717CECE007E_67175F5B013B_var*
begin
//#UC START# *6717CECE007E_67175F5B013B_impl*
(* if (aFiles = nil) then
  Result := nil
 else*)
 begin
  if (aFiles = nil)
     OR aFiles.Empty
     then
   Result := Tm3AttrIndexDumper.Make('')
  else
  if (aFiles.Count = 1) then
   Result := Tm3AttrIndexDumper.Make(aFiles.First.AsString)
  else
   Result := Tm3AttrIndexDumperJoinProxy.Make(aFiles, aIsDirect);
  //Result := MakeFromFilesEnum(aFiles.GetEnumerator, aIsDirect);
 end;//aFiles = nil
//#UC END# *6717CECE007E_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeFromFiles

class function Tm3AttrIndexDumperJoin.MakeFromList(const aList: Im3AttrIndexDumperEnumerable;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *6717D4380255_67175F5B013B_var*
var
 //l_Index : Integer;
 l_It : Im3AttrIndexDumperEnumerator;
//#UC END# *6717D4380255_67175F5B013B_var*
begin
//#UC START# *6717D4380255_67175F5B013B_impl*
 Result := nil;
 if (aList <> nil) then
 begin
  l_It := aList.GetEnumerator;
  if (l_It <> nil) then
   while l_It.MoveNext do
   begin
    if (Result = nil) then
     Result := l_It.Current
    else
     Result := Self.Make(Result, l_It.Current, aIsDirect);
   end;//while l_It.MoveNext
 end;//aList <> nil
(* for l_Index := 0 to Pred(aList.Count) do
 begin
  if (Result = nil) then
   Result := aList.Items[l_Index]
  else
   Result := Self.Make(Result, aList.Items[l_Index], aIsDirect);
 end;//for l_Index*)
//#UC END# *6717D4380255_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeFromList

class function Tm3AttrIndexDumperJoin.MakeForAttribute(const aBaseName: TFileName;
 anAttrID: Integer;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *6717EC2E03D3_67175F5B013B_var*
(*var
 l_Name : TFileName;*)
//#UC END# *6717EC2E03D3_67175F5B013B_var*
begin
//#UC START# *6717EC2E03D3_67175F5B013B_impl*
(* l_Name := aBaseName;
 l_Name := l_Name + '#' + Tk2Attributes.Instance.NameByID(anAttrID);
 if aIsDirect then
  l_Name := l_Name + '_' + '(d)';
 l_Name := l_Name + '#';
 l_Name := l_Name + '.idx';
 l_Name := l_Name + '*';
 Result := Self.MakeFromMask(l_Name, aIsDirect);*)
 Result := Self.MakeForAttribute(aBaseName, Tk2Attributes.Instance.NameByID(anAttrID), aIsDirect);
 //Result := Self.MakeFromMask(l_Name + '*', aIsDirect);
//#UC END# *6717EC2E03D3_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeForAttribute

class function Tm3AttrIndexDumperJoin.MakeForAttribute(const aBaseName: TFileName;
 const anAttrName: AnsiString;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *671A2EAB038F_67175F5B013B_var*
var
 l_Name : TFileName;
 l_BaseName : TFileName;
 l_Helper : Im3DBHelper;
 l_A : Im3FilesEnumerable;
//#UC END# *671A2EAB038F_67175F5B013B_var*
begin
//#UC START# *671A2EAB038F_67175F5B013B_impl*
 l_Name := aBaseName;
 l_Name := l_Name + '#' + anAttrName;
 if aIsDirect then
  l_Name := l_Name + '_' + '(d)';
 l_Name := l_Name + '#';
 l_BaseName := l_Name;
 // -- https://mdp.garant.ru/pages/viewpage.action?pageId=872809175
 // PurgeIndexDeltas - не забыть вызывать. При поиске например.

(* l_Name := l_Name + '.idx';
 l_Name := l_Name + '*';*)
 // - не надо наверное по маске искать ибо там вообще могут быть недостроенные дельты

 l_A := nil;
 l_Helper := Tm3DBHelper.Make(l_BaseName);
 try
  Tm3StorageHolderList.DropFiles(l_Helper.GetIndexForPurgeFiles);
  {$IfDef Linux}
  {$IfDef AppClientSide}
  if (g_BaseEngine <> nil) then
   if not g_BaseEngine.WorkWithServer then
    l_Helper.PurgeIndexDeltas;
  {$Else  AppClientSide}
  // - тут надо проверять WorkWithServer
  l_Helper.PurgeIndexDeltas;
  {$EndIf AppClientSide}
  {$Else  Linux}
  l_Helper.PurgeIndexDeltas;
  {$EndIf Linux}

  l_A := l_Helper.GetAllIndexFiles;
 finally
  l_Helper := nil;
 end;//try..finally
 if (l_A <> nil)
    AND (l_A.Count > 0)
    then
  Result := Self.MakeFromFiles(l_A, aIsDirect)
 else
  Result := Tm3AttrIndexDumper.Make('');
 // - Stub or Null-Object for caching
  //Result := nil;
  //Result := nil;
 // - не надо наверное по маске искать ибо там вообще могут быть недостроенные дельты
 // Result := Self.MakeFromMask(l_Name, aIsDirect);
//#UC END# *671A2EAB038F_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeForAttribute

class function Tm3AttrIndexDumperJoin.MakeForAttribute(const aBaseName: TFileName;
 const anAttrID: array of Integer;
 aIsDirect: Boolean): Im3AttrIndexDumper;
//#UC START# *671A58B30070_67175F5B013B_var*
var
 l_Name : TFileName;
 l_Index : Integer;
//#UC END# *671A58B30070_67175F5B013B_var*
begin
//#UC START# *671A58B30070_67175F5B013B_impl*
 l_Name := '';
 for l_Index := Low(anAttrID) to High(anAttrID) do
 begin
  if (l_Name = '') then
   l_Name := Tk2Attributes.Instance.NameByID(anAttrID[l_Index])
  else
   l_Name := l_Name + '_' + Tk2Attributes.Instance.NameByID(anAttrID[l_Index]);
 end;//for l_Index
 Result := MakeForAttribute(aBaseName, l_Name, aIsDirect);
//#UC END# *671A58B30070_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeForAttribute

class function Tm3AttrIndexDumperJoin.MakeNameForAttr(const aName: Il3CString;
 const anAttrID: AnsiString;
 aIsDirect: Boolean): Il3CString;
//#UC START# *671CDDC20340_67175F5B013B_var*
var
 l_S : TFileName;
 l_Ext : TFileName;
//#UC END# *671CDDC20340_67175F5B013B_var*
begin
//#UC START# *671CDDC20340_67175F5B013B_impl*
 l_S := Il3CString_ToFileName(aName);
 l_Ext := ExtractFileExt(l_S);
 l_S := ChangeFileExt(l_S, '');
 l_S := l_S + '#' + anAttrID;
 if aIsDirect then
  l_S := l_S + '_' + '(d)';
 l_S := l_S + '#';
 l_S := l_S + l_Ext;
 Result := Il3CString_C(l_S);
//#UC END# *671CDDC20340_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeNameForAttr

class function Tm3AttrIndexDumperJoin.MakeFilesForAttr(const aFiles: Im3FilesEnumerable;
 const anAttrID: AnsiString;
 aIsDirect: Boolean): Im3FilesEnumerable;
//#UC START# *671CDDD501F2_67175F5B013B_var*
var
 l_L : Tl3StringList;
 l_It : Im3FilesEnumerator;
 l_S : Il3CString;
//#UC END# *671CDDD501F2_67175F5B013B_var*
begin
//#UC START# *671CDDD501F2_67175F5B013B_impl*
 l_L := Tl3StringList.Create;
 try
  l_It := aFiles.GetEnumerator;
  if (l_It <> nil) then
   while l_It.MoveNext do
   begin
    l_S := l_It.Current.AsCStr;
    l_S := Self.MakeNameForAttr(l_S, anAttrID, aIsDirect);
    if FileExists(Il3CString_ToFileName(l_S)) then
     l_L.Add(l_S);
   end;//l_It.MoveNext
  Result := l_L.AsEnumerable;
 finally
  FreeAndNil(l_L);
 end;//finally
//#UC END# *671CDDD501F2_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.MakeFilesForAttr

{$If NOT Defined(l3NoSRT)}
function Tm3AttrIndexDumperJoin.SetRefTo(var thePlace: Tm3AttrIndexDumperJoin): Boolean;
begin
 if (thePlace = Self) then
  Result := false
 else
 begin
  Result := true;
  thePlace.Free;
  thePlace := Self.Use;
 end;//thePlace = Self
end;//Tm3AttrIndexDumperJoin.SetRefTo
{$IfEnd} // NOT Defined(l3NoSRT)

function Tm3AttrIndexDumperJoin.pm_GetFirst: Il3RangeEnumerable;
//#UC START# *47D8233603DD_67175F5B013Bget_var*
//#UC END# *47D8233603DD_67175F5B013Bget_var*
begin
//#UC START# *47D8233603DD_67175F5B013Bget_impl*
 Result := nil;
 l3NI;
//#UC END# *47D8233603DD_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.pm_GetFirst

function Tm3AttrIndexDumperJoin.pm_GetLast: Il3RangeEnumerable;
//#UC START# *47D823570315_67175F5B013Bget_var*
//#UC END# *47D823570315_67175F5B013Bget_var*
begin
//#UC START# *47D823570315_67175F5B013Bget_impl*
 Result := nil;
 l3NI;
//#UC END# *47D823570315_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.pm_GetLast

function Tm3AttrIndexDumperJoin.pm_GetCount: Integer;
//#UC START# *4BB08B8902F2_67175F5B013Bget_var*
var
 l_It : Im3AttrIndexElementEnumerator;
//#UC END# *4BB08B8902F2_67175F5B013Bget_var*
begin
//#UC START# *4BB08B8902F2_67175F5B013Bget_impl*
 if (f_Count < 0) then
 begin
  f_Count := 0;
  l_It := Self.GetEnumerator;
  if (l_It <> nil) then
   while l_It.MoveNext do
    Inc(f_Count);
 end;//f_Count < 0
 Result := f_Count;
//#UC END# *4BB08B8902F2_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.pm_GetCount

function Tm3AttrIndexDumperJoin.GetEnumerator: Im3AttrIndexElementEnumerator;
 {* Получает Enumerator для перебора элементов "контейнера".

Вся  эта конструкция совместима с "синтаксическим сахаром" вида:

for Item in Container do Process(Item);

Который поддерживается в "новых" Delphi.

См. http://mdp.garant.ru/pages/viewpage.action?pageId=152961307 }
//#UC START# *5AB8C6B0003A_67175F5B013B_var*
var
 l_MainEnum : Im3AttrIndexElementEnumerator;
 l_DeltaEnum : Im3AttrIndexElementEnumerator;
 l_Modified : Il3RangeEnumerable;
 l_MainRange : Tm3IDRange;
 l_DeltaRange : Tm3IDRange;
//#UC END# *5AB8C6B0003A_67175F5B013B_var*
begin
//#UC START# *5AB8C6B0003A_67175F5B013B_impl*
 Result := nil;
 if f_IsDirect then
 begin
  l_MainEnum := f_Main.GetEnumerator;
  try
   if (l_MainEnum <> nil) then
   begin
    l_MainRange := f_Main.Range;
    l_DeltaRange := f_Delta.Range;
    if l_MainRange.IntersectsWith(l_DeltaRange) then
     l_Modified := f_Delta.ModifiedDocuments
    else
     l_Modified := nil;
    if (l_Modified <> nil) then
    begin
     l_MainEnum := TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Make(l_MainEnum, l_Modified);
     //l_MainEnum := TIm3AttrIndexElementEnumeratorWithFilteredModified.Make(l_MainEnum, l_Modified);
    end;//l_Modified <> nil
   end;//l_MainEnum <> nil
   l_DeltaEnum := f_Delta.GetEnumerator;
   try
    Result := TIm3DirectAttrIndexElementEnumeratorSortedJoin.Make(l_MainEnum, l_DeltaEnum);
    //Result := TIm3AttrIndexElementEnumeratorSortedJoin.Make(l_MainEnum, l_DeltaEnum);
   finally
    l_DeltaEnum := nil;
   end;//try..finally
  finally
   l_MainEnum := nil;
  end;//try..finally
 end//f_IsDirect
 else
 begin
  l_MainEnum := f_Main.GetEnumerator;
  try
   if (l_MainEnum <> nil) then
   begin
    l_MainRange := f_Main.Range;
    l_DeltaRange := f_Delta.Range;
    if l_MainRange.IntersectsWith(l_DeltaRange) then
     l_Modified := f_Delta.ModifiedDocuments
    else
     l_Modified := nil;
    if (l_Modified <> nil) then
    begin
     l_MainEnum := TIm3AttrIndexElementEnumeratorWithFilteredModified.Make(l_MainEnum, l_Modified);
    end;//l_Modified <> nil
   end;//l_MainEnum <> nil
   l_DeltaEnum := f_Delta.GetEnumerator;
   try
    Result := TIm3AttrIndexElementEnumeratorSortedJoin.Make(l_MainEnum, l_DeltaEnum);
   finally
    l_DeltaEnum := nil;
   end;//try..finally
  finally
   l_MainEnum := nil;
  end;//try..finally
 end;//f_IsDirect
//#UC END# *5AB8C6B0003A_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.GetEnumerator

function Tm3AttrIndexDumperJoin.ModifiedDocuments: Il3RangeEnumerable;
//#UC START# *5B2B66730195_67175F5B013B_var*
var
 l_Main : Il3RangeEnumerable;
 l_Delta : Il3RangeEnumerable;
//#UC END# *5B2B66730195_67175F5B013B_var*
begin
//#UC START# *5B2B66730195_67175F5B013B_impl*
 if (f_Modified = nil) then
 begin
  l_Main := f_Main.ModifiedDocuments;
  l_Delta := f_Delta.ModifiedDocuments;
  f_Modified := Il3RangeEnumerable_JoinWithOther(l_Main, l_Delta);
  // http://mdp.garant.ru/pages/viewpage.action?pageId=853281243 // Делать SortedJoin вместо Join, если возможно
  //f_Modified := l_Main.ModifiedDocuments.Join(l_Delta);
 end;//f_Modified = nil
 Result := f_Modified;
//#UC END# *5B2B66730195_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.ModifiedDocuments

function Tm3AttrIndexDumperJoin.IndexedVersions: Im3IndexedVersions;
//#UC START# *5B2E194003AE_67175F5B013B_var*
var
 l_MV : Im3IndexedVersions;
 l_DV : Im3IndexedVersions;
//#UC END# *5B2E194003AE_67175F5B013B_var*
begin
//#UC START# *5B2E194003AE_67175F5B013B_impl*
 if (f_Versions = nil) then
 begin
  l_MV := f_Main.IndexedVersions;
  l_DV := f_Delta.IndexedVersions;
  f_Versions := Tm3IndexedVersionsSortedJoin.Make(l_MV, l_DV);
 end;//f_Versions = nil
 Result := f_Versions;
//#UC END# *5B2E194003AE_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.IndexedVersions

function Tm3AttrIndexDumperJoin.Get_Range: Tm3IDRange;
//#UC START# *5DA9C0270207_67175F5B013Bget_var*
//#UC END# *5DA9C0270207_67175F5B013Bget_var*
begin
//#UC START# *5DA9C0270207_67175F5B013Bget_impl*
 Result := f_Main.Range.Add(f_Delta.Range);
//#UC END# *5DA9C0270207_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.Get_Range

function Tm3AttrIndexDumperJoin.ValuesByKey(aKey: Integer): Il3RangeEnumerable;
//#UC START# *66B4B9740318_67175F5B013B_var*
var
 l_Modified : Il3RangeEnumerable;
 l_Main : Il3RangeEnumerable;
 l_Delta : Il3RangeEnumerable;
 l_MainRange : Tm3IDRange;
 l_DeltaRange : Tm3IDRange;
//#UC END# *66B4B9740318_67175F5B013B_var*
begin
//#UC START# *66B4B9740318_67175F5B013B_impl*
 Result := nil;
 l_MainRange := f_Main.Range;
 l_DeltaRange := f_Delta.Range;
(* if not f_IsDirect then
 // https://mdp.garant.ru/pages/viewpage.action?pageId=875135987
 // -- ибо там неправильно обнулялось l_Modified
 begin
  if l_MainRange.IntersectsWith(l_DeltaRange) then
   l_Modified := f_Delta.ModifiedDocuments
  else
   l_Modified := nil;
 end//not f_IsDirect
 else
  {l_Modified := f_Delta.ModifiedDocuments};*)
 if f_IsDirect then
 begin
{  if l_DeltaRange.Has(aKey) then
  // - типа немного оптимизируем - не вычитываем Modified раньше
   Result := f_Delta.ValuesByKey(aKey)}
  // - НЕЛЬЗЯ ТАК оптимизировать - l_DeltaRange - ДВЕ ТОЧКИ, и между ними может быть ДЫРКА
  // и aKey НА САМОМ деле лежит в f_Main
{ Возможно надо написать так:
   if not l_DeltaRange.Has(aKey) then
    // - типа немного оптимизируем - не вычитываем Modified раньше
     Result := f_Main.ValuesByKey(aKey)}
{   Tm3AttrIndexSearcher.InternalHandleToExternalHandle - почему-то не находит в ПРЯМОМ индексе в БАЗЕ у коллег
   Номера:
   6
   7
   8
   9
   10
   11
   12
   13}
//  else
  begin
   if not l_DeltaRange.Has(aKey) then
   // - если не попали в диапазон, то значит это не в нашем куске
   begin
    Result := f_Main.ValuesByKey(aKey);
   end//not l_DeltaRange.Has(aKey)
   else
   begin
    l_Modified := f_Delta.ModifiedDocuments;
    if (l_Modified = nil) then
     Result := f_Main.ValuesByKey(aKey)
    else
    if l_Modified.Has(Tl3Range_C(aKey)) then
     Result := f_Delta.ValuesByKey(aKey)
    else
     Result := f_Main.ValuesByKey(aKey);
   end;//not l_DeltaRange.Has(aKey)
  end;//l_DeltaRange.Has(aKey)
 end//f_IsDirect
 else
 begin
  l_Main := f_Main.ValuesByKey(aKey);
  l_Delta := f_Delta.ValuesByKey(aKey);

  if (l_Main = nil) then
  begin
   Result := l_Delta;
   Exit;
  end;//l_Main = nil

  if l_MainRange.IntersectsWith(l_DeltaRange) then
   l_Modified := f_Delta.ModifiedDocuments
  else
   l_Modified := nil;

  Result := Il3RangeEnumerable_Diff(l_Main, l_Modified);

  if l_MainRange.Less(l_DeltaRange) then
  begin
   if (Result = nil) then
    Result := l_Delta
   else
    Result := Result.Join(l_Delta);
  end//l_MainRange.Less(l_DeltaRange)
  else
  if l_DeltaRange.Less(l_MainRange) then
  begin
   if (l_Delta <> nil) then
    Result := l_Delta.Join(Result);
   //else
   // Result := Result
  end//l_DeltaRange.Less(l_MainRange)
  else
   Result := Il3RangeEnumerable_JoinWithOther(Result, l_Delta);
 end;//f_IsDirect
//#UC END# *66B4B9740318_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.ValuesByKey

function Tm3AttrIndexDumperJoin.TimeStamp: TDateTime;
//#UC START# *66C46E6801D3_67175F5B013B_var*
//#UC END# *66C46E6801D3_67175F5B013B_var*
begin
//#UC START# *66C46E6801D3_67175F5B013B_impl*
 Result := BadDateTime;
 if (f_Delta <> nil) then
  Result := f_Delta.TimeStamp
 else
 if (f_Main <> nil) then
  Result := f_Main.TimeStamp;
//#UC END# *66C46E6801D3_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.TimeStamp

function Tm3AttrIndexDumperJoin.Get_MaxKeyID: Integer;
//#UC START# *671D18210090_67175F5B013Bget_var*
//#UC END# *671D18210090_67175F5B013Bget_var*
begin
//#UC START# *671D18210090_67175F5B013Bget_impl*
 Result := l3MinMax.Max(f_Main.MaxKeyID, f_Delta.MaxKeyID);
//#UC END# *671D18210090_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.Get_MaxKeyID

function Tm3AttrIndexDumperJoin.Get_Operation: Tm3GroupOperation;
//#UC START# *68E135160346_67175F5B013Bget_var*
//#UC END# *68E135160346_67175F5B013Bget_var*
begin
//#UC START# *68E135160346_67175F5B013Bget_impl*
 if (f_Main = nil) then
  Result := f_Delta.Operation
 else
 if (f_Delta = nil) then
  Result := f_Main.Operation
 else
 begin
  Result := f_Main.Operation;
  Assert(f_Delta.Operation = Result);
  Assert(Result = m3_gopSet);
 end;//f_Delta = nil
//#UC END# *68E135160346_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.Get_Operation

function Tm3AttrIndexDumperJoin.Get_MinKeyID: Integer;
//#UC START# *69B14AAE0155_67175F5B013Bget_var*
//#UC END# *69B14AAE0155_67175F5B013Bget_var*
begin
//#UC START# *69B14AAE0155_67175F5B013Bget_impl*
 Result := l3MinMax.Min(f_Main.MinKeyID, f_Delta.MinKeyID);
//#UC END# *69B14AAE0155_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.Get_MinKeyID

procedure Tm3AttrIndexDumperJoin.Set_SearchMode(aValue: Tm3SearchMode);
//#UC START# *69BBB8A101BA_67175F5B013Bset_var*
//#UC END# *69BBB8A101BA_67175F5B013Bset_var*
begin
//#UC START# *69BBB8A101BA_67175F5B013Bset_impl*
 if (f_Main <> nil) then
  f_Main.SearchMode := aValue;
 if (f_Delta <> nil) then
  f_Delta.SearchMode := aValue;
//#UC END# *69BBB8A101BA_67175F5B013Bset_impl*
end;//Tm3AttrIndexDumperJoin.Set_SearchMode

function Tm3AttrIndexDumperJoin.Get_Sequential: Boolean;
//#UC START# *69C1D45B01E5_67175F5B013Bget_var*
//#UC END# *69C1D45B01E5_67175F5B013Bget_var*
begin
//#UC START# *69C1D45B01E5_67175F5B013Bget_impl*
 if f_Delta.Sequential then
 begin
  if f_Main.Sequential then
  begin
   if f_Main.ToTheLeftOf(f_Delta) then
    Result := true
   else
   if f_Delta.ToTheLeftOf(f_Main) then
    Result := true
   else
    Result := false;
  end//f_Main.Sequential
  else
   Result := false;
 end//f_Delta.Sequential
 else
  Result := false;
//#UC END# *69C1D45B01E5_67175F5B013Bget_impl*
end;//Tm3AttrIndexDumperJoin.Get_Sequential

function Tm3AttrIndexDumperJoin.ToTheLeftOf(const anOther: Im3AttrIndexDumper): Boolean;
//#UC START# *69C1DE0300B2_67175F5B013B_var*
//#UC END# *69C1DE0300B2_67175F5B013B_var*
begin
//#UC START# *69C1DE0300B2_67175F5B013B_impl*
 Result := (Self.Get_MaxKeyID < anOther.MinKeyID);
//#UC END# *69C1DE0300B2_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.ToTheLeftOf

procedure Tm3AttrIndexDumperJoin.Cleanup;
 {* Функция очистки полей объекта. }
//#UC START# *479731C50290_67175F5B013B_var*
//#UC END# *479731C50290_67175F5B013B_var*
begin
//#UC START# *479731C50290_67175F5B013B_impl*
 inherited;
 f_Main := nil;
 f_Delta := nil;
 f_Modified := nil;
 f_Versions := nil;
//#UC END# *479731C50290_67175F5B013B_impl*
end;//Tm3AttrIndexDumperJoin.Cleanup

procedure Tm3AttrIndexDumperJoin.ClearFields;
begin
 f_Main := nil;
 f_Delta := nil;
 f_Modified := nil;
 f_Versions := nil;
 inherited;
end;//Tm3AttrIndexDumperJoin.ClearFields

constructor TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Create(const anOther: Im3AttrIndexElementEnumerator;
 const aModified: Il3RangeEnumerable);
//#UC START# *69BD1854034C_69BD13C5026D_var*
//#UC END# *69BD1854034C_69BD13C5026D_var*
begin
//#UC START# *69BD1854034C_69BD13C5026D_impl*
 Assert(anOther <> nil);
 Assert(aModified <> nil);
 f_Other := anOther;
 f_Modified := aModified;
 f_ModifiedIt := f_Modified.GetEnumerator;
 if not f_ModifiedIt.MoveNext then
  f_ModifiedIt := nil;
 inherited Create;
//#UC END# *69BD1854034C_69BD13C5026D_impl*
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Create

class function TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Make(const anOther: Im3AttrIndexElementEnumerator;
 const aModified: Il3RangeEnumerable): Im3AttrIndexElementEnumerator;
var
 l_Inst : TIm3DirectAttrIndexElementEnumeratorWithFilteredModified;
begin
 l_Inst := Create(anOther, aModified);
 try
  Result := l_Inst;
 finally
  l_Inst.Free;
 end;//try..finally
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Make

function TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.MoveNext: Boolean;
 {* Перемещается на следующий элемент контейнера. 

Возвращает true если элемент валидный. }
//#UC START# *5ACB6780016E_69BD13C5026D_var*
var
 l_NeedSkip : Boolean;
 l_ID : Integer;
 l_R : Tm3IDRange;
 l_NeedMoveOther : Boolean;
//#UC END# *5ACB6780016E_69BD13C5026D_var*
begin
//#UC START# *5ACB6780016E_69BD13C5026D_impl*
 if (f_Other = nil) then
  Result := false
 else
 begin
  if (f_ModifiedIt = nil) then
  begin
   Result := f_Other.MoveNext;
   if not Result then
    f_Other := nil;
   Exit;
  end;//f_ModifiedIt = nil
  l_NeedMoveOther := true;
  while true do
  begin
   if l_NeedMoveOther then
    Result := f_Other.MoveNext;
   if Result then
   begin
    if (f_ModifiedIt = nil) then
     break
    else
    begin
     l_ID := f_Other.pCurrent^.ID;
     l_R := f_ModifiedIt.Current;
     l_NeedSkip := l_R.Has(l_ID);
     // - мы попали внутрь текущего диапазона
     if l_NeedSkip then
     begin
     // - фильтруем те, которые попали в дельту
      l_NeedMoveOther := true;
      continue;
     end//l_NeedSkip
     else
     begin
      if (l_ID < l_R.rLow) then
      // - мы находимся левее текущего диапазона
       break
      else
      begin
       if f_ModifiedIt.MoveNext then
       begin
        l_R := f_ModifiedIt.Current;
        if (l_ID < l_R.rLow) then
        // - мы находимся левее текущего диапазона
         break
        else
        begin
         l_NeedMoveOther := false;
         continue;
         // - уходим на следующий круг, но без сдвига текущего
        end;//l_ID < l_R.rLow
       end//f_ModifiedIt.MoveNext
       else
       begin
        f_ModifiedIt := nil;
        break;
       end;//f_ModifiedIt.MoveNext
      end;//l_ID < l_R.rLow
     end;//l_NeedSkip
    end;//f_ModifiedIt <> nil
(*    l_NeedSkip := f_Modified.Has(Tl3Range_C(f_Other.pCurrent^.ID));
    if l_NeedSkip then
    // - фильтруем те, которые попали в дельту
    // Это можно сделать оптимальнее двигая ОБА итератора (курсора) но пока - так.
     continue
    else
     break;*)
   end//Result
   else
   begin
    f_Other := nil;
    break;
   end;//Result
  end;//while true
 end;//f_Other = nil
//#UC END# *5ACB6780016E_69BD13C5026D_impl*
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.MoveNext

function TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.pCurrent: Im3AttrIndexElementEnumerator_PCurrentItemType;
 {* Указатель на текущий элемент. Он "закеширован" }
//#UC START# *5C4EFC140038_69BD13C5026D_var*
//#UC END# *5C4EFC140038_69BD13C5026D_var*
begin
//#UC START# *5C4EFC140038_69BD13C5026D_impl*
 Result := f_Other.pCurrent;
//#UC END# *5C4EFC140038_69BD13C5026D_impl*
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.pCurrent

function TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.CanMoveNext: Boolean;
 {* Определяет - можно ли переместиться на следующий элемент контейнера. }
//#UC START# *5E60D4B30164_69BD13C5026D_var*
//#UC END# *5E60D4B30164_69BD13C5026D_var*
begin
//#UC START# *5E60D4B30164_69BD13C5026D_impl*
 if (f_Other = nil) then
  Result := false
 else
  Result := f_Other.CanMoveNext;
//#UC END# *5E60D4B30164_69BD13C5026D_impl*
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.CanMoveNext

function TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Get_Current: Im3AttrIndexElement;
//#UC START# *6739C0470374_69BD13C5026Dget_var*
//#UC END# *6739C0470374_69BD13C5026Dget_var*
begin
//#UC START# *6739C0470374_69BD13C5026Dget_impl*
 Result := f_Other.pCurrent^;
//#UC END# *6739C0470374_69BD13C5026Dget_impl*
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.Get_Current

procedure TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.ClearFields;
begin
 f_Other := nil;
 f_Modified := nil;
 f_ModifiedIt := nil;
 inherited;
end;//TIm3DirectAttrIndexElementEnumeratorWithFilteredModified.ClearFields


//#UC START# *67175F5B013BforDiagramm*
(*
+-----------------------------------------------------------------------+
|        m3AttrIndexDumperJoin - Объединение индексов                    |
|      (основной индекс + дельты) без физического слияния                |
+-----------------------------------------------------------------------+

+-----------------------------------------------------------------------+
|  Класс Tm3AttrIndexDumperJoin (объединяет ДВА индекса)                 |
+-----------------------------------------------------------------------+
|  f_Main    : Im3AttrIndexDumper   // основной индекс (полный)          |
|  f_Delta   : Im3AttrIndexDumper   // дельта (изменения)                |
|  f_IsDirect: Boolean              // true = прямой, false = обратный   |
+-----------------------------------------------------------------------+
                    |
                    v
+-----------------------------------------------------------------------+
|  Логика поиска ValuesByKey(aKey)                                      |
+-----------------------------------------------------------------------+
|                                                                       |
|  +-----------------------------------------------------------------+  |
|  |  Если f_IsDirect = true (ПРЯМОЙ индекс: ключ -> значения)       |  |
|  +-----------------------------------------------------------------+  |
|  |                                                                 |  |
|  |   if Дельта содержит aKey?                                      |  |
|  |        |                                                       |  |
|  |        +-- Да --> if aKey в ModifiedDocuments?                  |  |
|  |        |              |                                         |  |
|  |        |              +-- Да --> Result := f_Delta.ValuesByKey  |  |
|  |        |              |           (берём из дельты)             |  |
|  |        |              |                                         |  |
|  |        |              +-- Нет --> Result := f_Main.ValuesByKey   |  |
|  |        |                        (берём из основного)            |  |
|  |        |                                                       |  |
|  |        +-- Нет --> Result := f_Main.ValuesByKey                 |  |
|  |                  (берём из основного)                          |  |
|  |                                                                 |  |
|  |   Вывод: дельта ПЕРЕОПРЕДЕЛЯЕТ (добавляет/заменяет) значения    |  |
|  +-----------------------------------------------------------------+  |
|                                                                       |
|  +-----------------------------------------------------------------+  |
|  |  Если f_IsDirect = false (ОБРАТНЫЙ индекс: значение -> ключи)   |  |
|  +-----------------------------------------------------------------+  |
|  |                                                                 |  |
|  |   l_Main   := f_Main.ValuesByKey(aKey)   // из основного        |  |
|  |   l_Delta  := f_Delta.ValuesByKey(aKey)  // из дельты           |  |
|  |   l_Modified := f_Delta.ModifiedDocuments                       |  |
|  |                                                                 |  |
|  |   Result := l_Main МИНУС l_Modified                              |  |
|  |   Result := Result ОБЪЕДИНИТЬ с l_Delta                         |  |
|  |                                                                 |  |
|  |   Вывод: дельта УДАЛЯЕТ из основного и добавляет новые значения |  |
|  +-----------------------------------------------------------------+  |
+-----------------------------------------------------------------------+
                    |
                    v
+-----------------------------------------------------------------------+
|  Метод GetEnumerator - объединённый перебор                            |
+-----------------------------------------------------------------------+
|                                                                       |
|   l_MainEnum  := f_Main.GetEnumerator                                 |
|   l_DeltaEnum := f_Delta.GetEnumerator                                |
|                                                                       |
|   Result := SortedJoin.Make(l_MainEnum, l_DeltaEnum)                  |
|                                                                       |
|   // Слияние двух отсортированных списков (как в сортировке слиянием) |
|   // Результат: объединённый список без дубликатов                    |
+-----------------------------------------------------------------------+

+-----------------------------------------------------------------------+
|        Класс Tm3AttrIndexDumperJoinProxy (объединяет МНОГО индексов)   |
+-----------------------------------------------------------------------+
|                                                                       |
|  Поля:                                                                |
|  +-- f_Files     : список индексных файлов                            |
|  +-- f_OtherJoin : результат объединения всех, кроме последнего       |
|  +-- f_IsDirect  : направление индекса                                |
|                                                                       |
+-----------------------------------------------------------------------+
                    |
                    v
+-----------------------------------------------------------------------+
|  Схема объединения нескольких файлов                                   |
+-----------------------------------------------------------------------+
|                                                                       |
|   Файлы:   [A.idx]  [B.idxdelta]  [C.idxdelta]  [D.idxdelta]          |
|               |          |            |            |                  |
|               v          v            v            v                  |
|   +-----------------------------------------------------------------+ |
|   |                                                                 | |
|   |   f_OtherJoin = Join( Join( Join(A, B), C), D)                  | |
|   |                                                                 | |
|   |   // рекурсивное объединение: сначала два, потом результат      | |
|   |   // с третьим, и т.д.                                          | |
|   |                                                                 | |
|   +-----------------------------------------------------------------+ |
+-----------------------------------------------------------------------+

+-----------------------------------------------------------------------+
|              Эвристики выбора стратегии для больших индексов           |
+-----------------------------------------------------------------------+
|                                                                       |
|   +-----------------------------------------------------------------+ |
|   |  Если частей > 200:                                             | |
|   |      -> последовательный перебор (дешевле, чем ходить в файл)   | |
|   +-----------------------------------------------------------------+ |
|                                    |                                  |
|                                    v                                  |
|   +-----------------------------------------------------------------+ |
|   |  Вычислить плотность = (maxKey - minKey) / кол-во элементов     | |
|   |                                                                 | |
|   |  Если плотность > 5 (редкие ключи):                             | |
|   |      -> бинарный поиск (быстрее, чем перебирать пустые ключи)   | |
|   |                                                                 | |
|   |  Если плотность <= 5 (частые ключи):                            | |
|   |      -> последовательный перебор (эффективнее)                  | |
|   +-----------------------------------------------------------------+ |
+-----------------------------------------------------------------------+

+-----------------------------------------------------------------------+
|                 Схема использования в подсистеме m3                    |
+-----------------------------------------------------------------------+

   +-------------+     +-------------+     +-------------+
   | Основной    |     |  Дельта 1   |     |  Дельта 2   |
   | индекс      |     | (изменения) |     | (изменения) |
   | (полный)    |     |             |     |             |
   +------+------+     +------+------+     +------+------+
          |                   |                   |
          +-------------------+-------------------+
                              |
                              v
          +---------------------------------------+
          |         m3AttrIndexDumperJoin         |
          |                                       |
          |   Логическое объединение без          |
          |   физического слияния файлов          |
          +---------------------------------------+
                              |
                              v
          +---------------------------------------+
          |        Im3AttrIndexDumper             |
          |        (единый интерфейс)             |
          +---------------------------------------+
                              |
                              v
          +---------------------------------------+
          |        m3AttrIndexSearcher            |
          |        (поиск документов)             |
          +---------------------------------------+

+-----------------------------------------------------------------------+
|  Ключевая идея: дельты НАКАПЛИВАЮТСЯ, поиск идёт по ЛОГИЧЕСКИ         |
|  ОБЪЕДИНЁННОМУ индексу, физическое слияние происходит ТОЛЬКО          |
|  при достижении лимита.                                                |
+-----------------------------------------------------------------------+
*)
//#UC END# *67175F5B013BforDiagramm*

end.

Комментариев нет:

Отправить комментарий