//------------------------------------------------------------------------------
//
// Local and File-Server Engine
// Server uses Local Engine
//
//------------------------------------------------------------------------------

unit ABSBaseEngine;

{$I ABSVer.inc}

interface


uses SysUtils, Classes, Windows 
{$IFNDEF NO_DIALOGS}
{$IFNDEF D16H}
  ,Dialogs, Controls 
{$ELSE}
  ,VCL.Dialogs, VCL.Controls
{$ENDIF}
{$ENDIF}
, Math, DB,

// AbsoluteDatabase units

     {$IFDEF DEBUG_LOG}
     ABSDebug,
     {$ENDIF}
     ABSPage,
     ABSExcept,
     ABSBase,
     ABSMemory,
     ABSCompression,
     ABSSecurity,
     ABSTypes,
     ABSConverts,
     ABSVariant,
     ABSConst,
     ABSExpressions;

var
  NullStringAsBlankString: Boolean = False;

type


  TABSDatabaseData = class;
  TABSTableData = class;
  TABSIndex = class;
  TABSRecordBitmap = class;

////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseRecordManager
//
////////////////////////////////////////////////////////////////////////////////


  TABSBaseRecordManager = class (TObject)
   protected
    FRecordCount:           TABSRecordNo;
    FTableState:            Integer;
    FRecordBufferSize:      Integer;
   public
    procedure Empty(SessionID: TABSSessionID); virtual; abstract;
    // add record and return its number
    function AddRecord(SessionID: TABSSessionID; RecordBuffer: TABSRecordBuffer; var RecordID: TABSRecordID): Boolean; virtual; abstract;
    // update record, return true if record was updated, false if record was deleted
    function UpdateRecord(SessionID: TABSSessionID; RecordBuffer: TABSRecordBuffer; RecordID: TABSRecordID): Boolean; virtual; abstract;
    // delete record, return true if record was deleted, false if record was deleted earlier
    function DeleteRecord(SessionID: TABSSessionID; var RecordID: TABSRecordID): Boolean; virtual; abstract;
    // get record using physical order
    procedure GetRecordBuffer(SessionID: TABSSessionID; var NavigationInfo: TABSNavigationInfo); virtual; abstract;
    // return 0,1, or -1 if (1 = 2), (1 > 2) or (1 < 2)
    function CompareRecordID(RecordID1: TABSRecordID; RecordID2: TABSRecordID): Integer; virtual; abstract;
    // return record no
    function GetApproximateRecNo(SessionID: TABSSessionID; RecordID: TABSRecordID): TABSRecordNo; virtual; abstract;
    //procedure LoadFromStream(Stream: TStream); virtual; abstract;
    //procedure SaveToStream(Stream: TStream); virtual; abstract;
    // add loaded record
    //procedure AddLoadedRecord(RecordBuffer: TABSRecordBuffer); virtual; abstract;
   public
    property RecordCount: TABSRecordNo read FRecordCount write FRecordCount;
    property TableState: Integer read FTableState write FTableState;
    property RecordBufferSize: Integer read FRecordBufferSize write FRecordBufferSize;
  end;


////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseFieldManager
//
////////////////////////////////////////////////////////////////////////////////


  TABSBaseFieldManager = class (TObject)
   private
    FFieldDefs:       TABSFieldDefs;
    FTableData:       TABSTableData;
   protected
    FLastAutoIncValues: array of Int64;
    function GetNextAutoincVal(FieldNo: Integer): TABSSequenceValue; virtual;
   public
    constructor Create(TableData: TABSTableData);
    destructor Destroy; override;

    procedure LoadMetadata(Stream: TStream);
    procedure SaveMetadata(Stream: TStream);
    procedure LoadMostUpdated(Buf: TAbsPByte; var Offset: Integer);
    procedure SaveMostUpdated(Buf: TAbsPByte; var Offset: Integer);

    procedure RecalcFieldOffsets;
    procedure InitLastAutoIncValues;

    procedure DiskRecordBufferToMemRecordBuffer(DiskRecordBuffer, MemRecordBuffer: TAbsPByte);
    procedure MemRecordBufferToDiskRecordBuffer(MemRecordBuffer, DiskRecordBuffer: TAbsPByte);
    function CompareRecordBuffers(RecordBuffer1,RecordBuffer2: TABSRecordBuffer): Boolean;
    function BlobFieldsPresent: Boolean;

    // Set default values to fields
    procedure ApplyDefaultValuesToRecordBuffer(Session: TABSBaseSession; RecordBuffer: TABSRecordBuffer); virtual;
    // Set new values of Autioincs to fields
    procedure ApplyAutoincsToRecordBuffer(Session: TABSBaseSession; RecordBuffer: TABSRecordBuffer); virtual;

    function GetNextAutoinc(Session: TABSBaseSession; FieldNo: Integer): TABSSequenceValue; virtual;
    function GetLastAutoinc(Session: TABSBaseSession; FieldNo: Integer): TABSSequenceValue; virtual;
    procedure UpdateLastAutoinc(Session: TABSBaseSession; FieldNo: Integer; InsertedValue: Int64);

   public
    property FieldDefs: TABSFieldDefs read FFieldDefs;
  end; // TABSBaseFieldManager



////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseIndexManager
//
////////////////////////////////////////////////////////////////////////////////


  TABSBaseIndexManager = class (TObject)
   protected
    FTableData:       TABSTableData;
    FIndexDefs:       TABSIndexDefs;
    FOpenIndexList:   TList; // list of TABSIndex objects
    FPageManager:          TABSPageManager;
    FTemporaryPageManager: TABSPageManager;
    FSynchronizingTemporaryIndexes:  Boolean;

    procedure InternalCreateIndex(
            Cursor: TABSCursor;
            IndexDef: TABSIndexDef
                                  );
    function InternalOpenIndex(
            IndexID: TABSObjectID
                               ): TABSIndex;

   public
    procedure LoadMetadata(Stream: TStream); virtual;
    procedure SaveMetadata(Stream: TStream); virtual;
    constructor Create(aTableData: TABSTableData; TempPageManager: TABSPageManager);
    destructor Destroy; override;

    function GetPageManager(Index: TABSIndex): TABSPageManager;

    procedure DropTemporaryIndexes(SessionID: TABSSessionID);
    function IsIndexExists(FieldNames, AscDescList, CaseSensitivityList: TStringList;
                   SessionID: TABSSessionID; FieldDefs: TABSFieldDefs): Boolean;
    function FindIndex(FieldNames, AscDescList, CaseSensitivityList: TStringList;
                       SessionID: TABSSessionID; FieldDefs: TABSFieldDefs): TABSObjectID;
    function CreateTemporaryIndex(
                    Cursor:         TABSCursor;
                    FieldNamesList, AscDescList, CaseSensitivityList: TStringList
                                 ): TABSObjectID;
    function FindOrCreateIndex(Cursor: TABSCursor; FieldNamesList, AscDescList, CaseSensitivityList: TStringList; var IsCreated: Boolean): TABSObjectID;

    // create index definitions list
    procedure CreateIndexDefs(aIndexDefs: TABSIndexDefs); virtual;
    procedure CreateIndexesByIndexDefs(Cursor: TABSCursor);

    function FindOpenIndex(IndexID: TABSObjectID): TABSIndex;
    function CreateIndex(
                         Cursor: TABSCursor;
                         IndexDef: TABSIndexDef
                        ): TABSObjectID;
    function OpenIndex(IndexID: TABSObjectID): TABSIndex;
    procedure CloseIndex(IndexID: TABSObjectID);
    procedure DropIndex(SessionID: TABSSessionID; IndexID: TABSObjectID);
    procedure DropAllIndexes(SessionID: TABSSessionID);
    procedure EmptyIndex(SessionID: TABSSessionID; IndexID: TABSObjectID);
    procedure EmptyAllIndexes(SessionID: TABSSessionID);

    procedure GetRecordBuffer(
                               SessionID: TABSSessionID;
                               var NavigationInfo: TABSNavigationInfo;
                               IndexPositionCache: TABSIndexPositionCache;
                               TableState:         Integer
                             );
    procedure InsertRecord(Cursor: TABSCursor);
    procedure UpdateRecord(Cursor: TABSCursor; BeforeNewRecordIsStored: Boolean);
    procedure DeleteRecord(Cursor: TABSCursor);

    procedure SynchronizeTemporaryIndexes(Cursor: TABSCursor; var IsChanged: Boolean);
    procedure RenameField(FieldName, NewFieldName: string);

    property IndexDefs: TABSIndexDefs read FIndexDefs;
    property TableData: TABSTableData read FTableData;
    property PageManager: TABSPageManager read FPageManager;
  end; // TABSBaseIndexManager


////////////////////////////////////////////////////////////////////////////////
//
// TABSIndex
//
////////////////////////////////////////////////////////////////////////////////

  TABSIndex = class (TObject)
   protected
    FIndexDef:      TABSIndexDef;
    FIndexManager:  TABSBaseIndexManager;
   public
    constructor Create(aIndexManager: TABSBaseIndexManager);
    destructor Destroy; override;
    procedure CreateIndex(Cursor: TABSCursor; aIndexDef: TABSIndexDef); virtual;
    procedure DropIndex(SessionID: TABSSessionID); virtual; abstract;
    procedure EmptyIndex(SessionID: TABSSessionID); virtual; abstract;
    procedure OpenIndex(aIndexDef: TABSIndexDef); virtual;
    procedure RebuildTemporaryIndex(Cursor: TABSCursor); virtual; abstract;

    procedure GetRecordBuffer(SessionID:          TABSSessionID;
                              var NavigationInfo: TABSNavigationInfo;
                              IndexPositionCache: TABSIndexPositionCache;
                              TableState:         Integer); virtual; abstract;
    procedure GetClosestRecordBuffer(
                              SessionID:          TABSSessionID;
                              var NavigationInfo: TABSNavigationInfo;
                               IndexPositionCache: TABSIndexPositionCache;
                               TableState:         Integer
                               ); virtual; abstract;
    function CreateIndexPosition: TABSIndexPosition; virtual; abstract;
    procedure FreeIndexPosition(var IndexPosition: TABSIndexPosition); virtual; abstract;
    function GetIndexPosition(
                               SessionID:      TABSSessionID;
                               RecordID:       TABSRecordID;
                               RecordBuffer:   TABSRecordBuffer;
                               IndexPosition:  TABSIndexPosition
                             ): Boolean; virtual; abstract;
    // return 0, 1, -1 if (Pos1 = Pos2), (Pos1 > Pos2), (Pos1 < Pos2)
    function CompareRecordPositionsInIndex(
                        RecordPosition1: TABSIndexPosition;
                        RecordPosition2: TABSIndexPosition
                                          ): Integer; virtual; abstract;
    function GetRecNoByRecordID(
                                SessionID:      TABSSessionID;
                                RecordID:       TABSRecordID;
                                RecordBuffer:   TABSRecordBuffer
                               ): TABSRecordNo; virtual; abstract;
    function GetRecordIDByRecNo(
                                SessionID:      TABSSessionID;
                                RecNo:          TABSRecordNo
                               ): TABSRecordID; virtual; abstract;
    procedure ApplyDistinctToRecordBitmap(
                                SessionID:      TABSSessionID;
                                Bitmap:         TABSRecordBitmap;
                                DistinctFieldCount: Integer
                                         ); virtual; abstract;
    function CreateSearchInfo: TABSSearchInfo; virtual; abstract;
    procedure FreeSearchInfo(SearchInfo: TABSSearchInfo); virtual; abstract;
    function FindRecord(
                       SessionID:           TABSSessionID;
                       Restart:             Boolean;
                       GoForward:           Boolean;
                       StartScanCondition:  TABSScanSearchCondition;
                       EndScanCondition:    TABSScanSearchCondition;
                       RecordBuffer:        TABSRecordBuffer;
                       var RecordID:        TABSRecordID;
                       SearchInfo:          TABSSearchInfo
                      ): Boolean; virtual; abstract;
    // return 0 if record buffers are equal in this index
    // return 1 if Buffer1 is higher than Buffer 2 (Pos1 > Pos2)
    // return -1 if Buffer1 is lower than Buffer 2 (Pos1 < Pos2)
    function CompareRecordBuffersByIndex(
                        Buffer1: TABSRecordBuffer;
                        Buffer2: TABSRecordBuffer;
                        IndexFieldCount: Integer
                                        ): Integer; virtual; abstract;

    // return 0 if conditions are equal in this index
    // return 1 if Condition1 is higher than Condition2
    // return -1 if Condition1 is lower than Condition2
    function CompareConditions(
                    Condition1:   TABSScanSearchCondition;
                    Condition2:   TABSScanSearchCondition
                              ): Integer; virtual; abstract;
    // approximate record count berween range conditions
    function GetApproxRangeRecordCount(
                    SessionID:         TABSSessionID;
                    TableRecordCount:  TABSRecordNo;
                    RangeCondition1:   TABSScanSearchCondition;
                    RangeCondition2:   TABSScanSearchCondition
                                      ): TABSRecordNo; virtual; abstract;
    function CanInsertRecord(
                    SessionID:      TABSSessionID;
                    RecordBuffer:   TABSRecordBuffer
                            ): Boolean; virtual; abstract;
    function CanUpdateRecord(
                    SessionID:      TABSSessionID;
                    OldRecordBuffer, NewRecordBuffer: TABSRecordBuffer
                            ): Boolean; virtual; abstract;
    procedure InsertRecord(Cursor: TABSCursor); virtual; abstract;
    procedure DeleteRecord(Cursor: TABSCursor); virtual; abstract;
    procedure UpdateRecord(Cursor: TABSCursor; BeforeNewRecordIsStored: Boolean); virtual; abstract;

    function GetMaxEntriesPerPage: Integer; virtual; abstract;
    procedure GetRecordIDByIndexPosition(SessionID: TABSSessionID; IndexPos: TABSIndexPosition; var RecordID: TABSRecordID); virtual; abstract;

    property IndexDef: TABSIndexDef read FIndexDef;
    property IndexManager: TABSBaseIndexManager read FIndexManager;
  end; // TABSIndex




////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseConstraintManager
//
////////////////////////////////////////////////////////////////////////////////


  TABSBaseConstraintManager = class (TObject)
   private
    FConstraintDefs:    TABSConstraintDefs;
    FTableData:         TABSTableData;
   public
    procedure LoadMetadata(Stream: TStream);
    procedure SaveMetadata(Stream: TStream);
    constructor Create(aTableData: TABSTableData); //virtual;
    destructor Destroy; override;

    // check constraint conditions
    function CheckConstraints(
                                SessionID:       TABSSessionID;
                                NewRecordBuffer: TABSRecordBuffer;
                                OldRecordBuffer: TABSRecordBuffer;
                                ToInsert:        Boolean;
                                CurrentRecordID: TABSRecordID;
                                RaiseException:  Boolean;
                                CheckUniqueIndexes: Boolean = True
                              ): Boolean;

    // Link object id
    procedure LinkObjectId(ConstraintDef: TABSConstraintDef);
    // Link object ids between constraints and fields
    procedure LinkObjectIds;
    // Generate Constraint Names for empty constraints
    procedure FillConstraintAutoNames;

    // add constraint for primary or Unique index
    procedure AddConstraintFromIndex(IndexDef: TABSIndexDef);
    // Delete Constraint Linked with deleted index
    procedure DeleteConstraintForIndexID(IndexObjectID: TABSObjectID);

   public
    property ConstraintDefs: TABSConstraintDefs read FConstraintDefs write FConstraintDefs;

  end; // TABSBaseConstraintManager


////////////////////////////////////////////////////////////////////////////////
//
// TABSRecordBitmap
//
////////////////////////////////////////////////////////////////////////////////

  TABSRecordBitmap = class (TObject)
  private
   FBitmap:     TABSBitsArray;
   FTableData:  TABSTableData;
   FActive:     Boolean;
   FIndexBased: Boolean;
   FTableState: Integer;
  protected
   function GetSize: TABSRecordNo;
   procedure SetSize(NewSize: TABSRecordNo);
   function GetOutdated: Boolean;
  public
   constructor Create(aTableData: TABSTableData);
   destructor Destroy; override;
   function GetRecordCount: TABSRecordNo;
   function GetRecNoByRecordID(RecordID: TABSRecordID): TABSRecordNo;
   function GetRecNoByIndexPos(IndexID: TABSObjectID; IndexPos: TABSIndexPosition): TABSRecordNo;
   function GetRecordIDByRecNo(RecNo: TABSRecordNo): TABSRecordID;
   procedure GetIndexPosByRecNo(IndexID: TABSObjectID; RecNo: TABSRecordNo; IndexPos: TABSIndexPosition);
   function IsRecordVisible(RecordID: TABSRecordID): Boolean; overload;
   function IsRecordVisible(IndexID: TABSObjectID; IndexPos: TABSIndexPosition): Boolean; overload;
   procedure ShowRecord(RecordID: TABSRecordID); overload;
   procedure ShowRecord(IndexID: TABSObjectID; IndexPos: TABSIndexPosition); overload;
   procedure HideRecord(RecordID: TABSRecordID); overload;
   procedure HideRecord(IndexID: TABSObjectID; IndexPos: TABSIndexPosition); overload;
   procedure SetAllBits;
   procedure Clear;
   function FindRecord(Restart, GoForward: Boolean; CurRecordID: TABSRecordID;
                       var FoundRecordID: TABSRecordID): Boolean; overload;
   function FindRecord(Restart, GoForward: Boolean; CurIndexPos, FoundRecordIndexPos: TABSIndexPosition;
                       IndexID: TABSObjectID): Boolean; overload;
  public
   property Active: Boolean read FActive write FActive;
   property Size: TABSRecordNo read GetSize write SetSize;
   property Outdated: Boolean read GetOutdated;
   property IndexBased: Boolean read FIndexBased write FIndexBased;
   property TableState: Integer read FTableState write FTableState;
  end; // TABSRecordBitmap



////////////////////////////////////////////////////////////////////////////////
//
// TABSTableData
//
////////////////////////////////////////////////////////////////////////////////


  TABSTableData = class (TObject)
  private
    FCSect:                        TRTLCriticalSection;
    FIsRepairing:                  Boolean;
  protected
    FPageManager:                  TABSPageManager;
    FDatabaseData:                 TABSDatabaseData;
    FTableName:                    string;
    FBLOBCompression:              TABSCompression;
    FActive:                       Boolean;
    FFieldManager:                 TABSBaseFieldManager;
    FConstraintManager:            TABSBaseConstraintManager;
    FIndexManager:                 TABSBaseIndexManager;
    FRecordManager:                TABSBaseRecordManager;
    FCursorList:                   TList;
    FDisableTempFiles:             Boolean;

    // Fill New ObjectID for all defs
    procedure FillDefsByObjectId(Defs: TABSMetaObjectDefs);
    function GetRecordBufferSize: Integer;
    procedure CreateRecordManager; virtual; abstract;
    procedure CreateFieldManager(FieldDefs: TABSFieldDefs); virtual;
    procedure CreateIndexManager(IndexDefs: TABSIndexDefs); virtual;
    procedure CreateConstraintManager(ConstraintDefs: TABSConstraintDefs); virtual;
    procedure InitCursor(Cursor: TABSCursor); virtual;
  public
    constructor Create(aDatabaseData: TABSDatabaseData);
    destructor Destroy; override;

    // lock
    procedure Lock;
    // unlock
    procedure Unlock;

    procedure ApplyChanges(SessionID: TABSSessionID; InTransaction: Boolean); virtual;
    procedure CancelChanges(SessionID: TABSSessionID; InTransaction: Boolean); virtual;

    // table operations
    procedure CreateTable(
                          Cursor: TABSCursor;
                          FieldDefs: TABSFieldDefs;
                          IndexDefs: TABSIndexDefs;
                          ConstraintDefs: TABSConstraintDefs
                         ); virtual; abstract;
    procedure DeleteTable(Cursor: TABSCursor; DesignMode: Boolean = False); virtual;
    procedure EmptyTable(Cursor: TABSCursor); virtual;
    procedure RenameTable(NewTableName: string; Cursor: TABSCursor); virtual;

    procedure OpenTable(Cursor: TABSCursor); virtual;
    procedure CloseTable(Cursor: TABSCursor); virtual;

    // Rename Field by Field Index in FieldDefs
    procedure RenameField(FieldName, NewFieldName: string); virtual;

    procedure AddIndex(IndexDef: TABSIndexDef; Cursor: TABSCursor); virtual;
    procedure DeleteIndex(IndexID: TABSObjectID; Cursor: TABSCursor); virtual;
    procedure EmptyIndex(IndexID: TABSObjectID; SessionID: TABSSessionID);
    procedure DeleteAllIndexes(Cursor: TABSCursor);
    procedure EmptyAllIndexes(SessionID: TABSSessionID);
    // return index name
    function FindOrCreateIndex(
                                Cursor: TABSCursor;
                                FieldNamesList,
                                AscDescList,
                                CaseSensitivityList: TStringList;
                                var IsCreated: Boolean
                              ): string;
    function IndexExists(Cursor: TABSCursor;
        FieldNamesList, AscDescList, CaseSensitivityList: TStringList): Boolean;


    // return true if Unique Constraint Failed
    function IsUniqueConstraintFailed(
                                        SessionID:        TABSSessionID;
                                        IndexID:          TABSObjectID;
                                        NewRecordBuffer:  TABSRecordBuffer;
                                        OldRecordBuffer:  TABSRecordBuffer;
                                        ToInsert:         Boolean;
                                        CurrentRecordID:  TABSRecordID
                                     ): Boolean;

    //---------------- Search and navigation methods ---------------------------
   protected
    // clear bitmap and set state and indexbased
    procedure InitRecordBitmap(RecordBitmap: TABSRecordBitmap; IndexID: TABSObjectID);
    // if necessary - build record bitmap for cursor
    procedure RefreshCursorRecordBitmap(Cursor: TABSCursor);
    // build record bitmap for cursor
    procedure BuildCursorRecordBitmap(Cursor: TABSCursor);
    // return filter bitmap rec count
    function GetBitmapRecordCount(SessionID: TABSSessionID): TABSRecordNo; virtual; abstract;
    // return filter bitmap rec no by record id
    function GetBitmapRecNoByRecordID(RecordID: TABSRecordID): TABSRecordNo; virtual; abstract;
    // return filter bitmap rec no by record id
    function GetRecordIDByBitmapRecNo(RecordNo: TABSRecordNo): TABSRecordID; virtual; abstract;
    // return filter bitmap rec no by record id
    function GetBitmapRecNoByIndexPosition(IndexID: TABSObjectID; IndexPos: TABSIndexPosition): TABSRecordNo;
    // return filter bitmap rec no by record id
    procedure GetIndexPositionByBitmapRecNo(IndexID: TABSObjectID; RecordNo: TABSRecordNo; IndexPos: TABSIndexPosition);
    // return index position by record id
    function GetIndexPositionByRecordID(SessionID: TABSSessionID; IndexID: TABSObjectID; RecordID: TABSRecordID; IndexPos: TABSIndexPosition): Boolean;
    // return record id by index position
    procedure GetRecordIDByIndexPosition(SessionID: TABSSessionID; IndexID: TABSObjectID; IndexPos: TABSIndexPosition; var RecordID: TABSRecordID);

    // return true if record is in specified range
    function IsRecordInRange(Cursor: TABSCursor): Boolean;
    // return true if record is not equal to the previous record
    function IsRecordInDistinct(Cursor: TABSCursor): Boolean;
    // return true if record is visible (with applied filters, ranges, OnFilterRecord)
    function IsRecordVisible(Cursor: TABSCursor): Boolean;

    procedure MergeAndCheckSearchConditionsCompatibility(
                                var Condition1:            TABSScanSearchCondition;
                                var Condition2:            TABSScanSearchCondition;
                                out Incompatible:          Boolean;
                                out HaveBeenMerged:        Boolean
                                           );
    // returns true if conditions are equal
    function IsEqualConditions(
                                var Condition1:            TABSScanSearchCondition;
                                var Condition2:            TABSScanSearchCondition
                                           ): Boolean;
    // removes duplicate conditions
    procedure RemoveDuplicateConditions(Conditions: TABSScanSearchConditionArray);
    // sorts Conditions array and removes unnecessary conditions
    procedure OptimizeSearchConditions(
                                var Conditions:               TABSScanSearchConditionArray;
                                out NonCompatibleConditions:  Boolean
                                     );
    // prepare conditions array
    procedure PrepareConditions(
                           Cursor:            TABSCursor;
                           Conditions:        TABSScanSearchConditionArray;
                           KeyCondition:      TABSScanSearchCondition;
                           SearchExpression:  TABSExpression;
                           GoForward:         Boolean
                               );
    // try to find the best scan condition with min range record count
    function ChooseScanConditionsWithMinScanCost(
                           SessionID:              TABSSessionID;
                           Conditions:             TABSScanSearchConditionArray;
                           CurrentIndexID:         TABSObjectID;
                           CurrentRecordID:        TABSRecordID;
                           FilterRecordPtr:        Pointer;
                           var ScanConditionNo:    Integer;
                           var ScanEndConditionNo: Integer
                                  ): Boolean;
    // if index is defined as unique or contain unique field
    function IsIndexUnique(IndexID: TABSObjectID): Boolean;
    // try to find the best scan condition using heuristics
    function ChooseScanConditionsByHeuristsics(
                           Conditions:             TABSScanSearchConditionArray;
                           CurrentIndexID:         TABSObjectID;
                           var ScanConditionNo:    Integer;
                           var ScanEndConditionNo: Integer
                                  ): Boolean;
    // try to find the best scan condition
    procedure ChooseScanConditions(
                           SessionID:              TABSSessionID;
                           Conditions:             TABSScanSearchConditionArray;
                           CurrentIndexID:         TABSObjectID;
                           CurrentRecordID:        TABSRecordID;
                           GoForward:              Boolean;
                           FilterRecordPtr:        Pointer;
                           var ScanConditionNo:    Integer;
                           var ScanEndConditionNo: Integer
                                  );
    // scan records bitmap to find record
    function FindRecordByBitmapScan(SessionID: TABSSessionID;
                                    Restart, GoForward: Boolean;
                                    CurrentRecordID: TABSRecordID;
                                    var ResultRecordID: TABSRecordID;
                                    RecordBitmap: TABSRecordBitmap;
                                    IndexID: TABSObjectID): Boolean;
    // return true if record was found and is visible by cursor
    // prepares params for FindRecordByScan and calls it
    function FindRecord(
                        Cursor:           TABSCursor;
                        SearchExpression: TABSExpression; // locate
                        KeyCondition:     TABSScanSearchCondition; // find key
                        // find first or last record in order specified by index settings
                        Restart:          Boolean;
                        // go from first record to last or vice versa
                        GoForward:        Boolean;
                        // if record was found then return record id
                        ResultRecordID:   PABSRecordID;
                        RecordBitmap:     TABSRecordBitmap;
                        // if specified - fill the whole record bitmap
                        ForceFillWholeBitmap: Boolean = False
                       ): Boolean;
    // return true if record match specified condition
    function IsRecordMatchCondition(
                                var Condition:        TABSScanSearchCondition;
                                RecordBuffer:         TABSRecordBuffer
                                   ): Boolean;
    // need to check conditions or index conditions are enough
    function NeedToCheckOtherConditions(
                              Conditions:           TABSScanSearchConditionArray;
                              ExcludedConditionNo:  Integer; // INVALID_ID4 if not specified
                              ExcludedConditionNo2: Integer; // INVALID_ID4 if not specified
                              FilterRecordPtr:      Pointer): Boolean;
    // return true if record match specified conditions
    function IsRecordMatchConditions(
                              Conditions:           TABSScanSearchConditionArray;
                              ExcludedConditionNo:  Integer; // INVALID_ID4 if not specified
                              ExcludedConditionNo2: Integer; // INVALID_ID4 if not specified
                              FilterRecordPtr:      Pointer;
                              Dataset:              Pointer;
                              RecordBuffer:         TABSRecordBuffer;
                              RecordID:             TABSRecordID
                                    ): Boolean;

    // scan all records and check conditions
    function FindRecordByScanWithoutCondition(
                              SessionID:          TABSSessionID;
                              Conditions:         TABSScanSearchConditionArray;
                              ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                              ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                              CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                              FilterRecordPtr:    Pointer;
                              Dataset:            Pointer;
                              CurrentRecordID:    TABSRecordID;
                              // find first or last record in order specified by index settings
                              Restart:            Boolean;
                              // go from first record to last or vice versa
                              GoForward:          Boolean;
                              // if record was found then return record id
                              ResultRecordID:     PABSRecordID;
                              // if specified - fill record bitmap
                              RecordBitmap:       TABSRecordBitmap;
                              ForceFillWholeBitmap: Boolean
                             ): Boolean;

    // find records by index and condition specified by ScanConditionNo
    // condition index is the same as CurrentIndex
    function FindRecordByScanWithConditionAndConditionIndex(
                              SessionID:          TABSSessionID;
                              Conditions:         TABSScanSearchConditionArray;
                              ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                              ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                              CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                              FilterRecordPtr:    Pointer;
                              Dataset:            Pointer;
                              CurrentRecordID:    TABSRecordID;
                              // find first or last record in order specified by index settings
                              Restart:            Boolean;
                              // go from first record to last or vice versa
                              GoForward:          Boolean;
                              // if record was found then return record id
                              ResultRecordID:     PABSRecordID;
                              // if specified - fill record bitmap
                              RecordBitmap:       TABSRecordBitmap;
                              ForceFillWholeBitmap: Boolean
                             ): Boolean;

    // find records by index and condition specified by ScanConditionNo
    // there is no active index
    function FindRecordByScanWithConditionAndWihtoutCurrentIndex(
                              SessionID:          TABSSessionID;
                              Conditions:         TABSScanSearchConditionArray;
                              ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                              ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                              CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                              FilterRecordPtr:    Pointer;
                              Dataset:            Pointer;
                              CurrentRecordID:    TABSRecordID;
                              // find first or last record in order specified by index settings
                              Restart:            Boolean;
                              // go from first record to last or vice versa
                              GoForward:          Boolean;
                              // if record was found then return record id
                              ResultRecordID:     PABSRecordID;
                              // if specified - fill record bitmap
                              RecordBitmap:       TABSRecordBitmap;
                              ForceFillWholeBitmap: Boolean
                             ): Boolean;

    // find records by index and condition specified by ScanConditionNo
    // condition index is NOT the same as CurrentIndex
    function FindRecordByScanWithConditionAndNonConditionIndex(
                              SessionID:          TABSSessionID;
                              Conditions:           TABSScanSearchConditionArray;
                              ScanConditionNo:      Integer; // INVALID_ID4 if not specified
                              ScanEndConditionNo:   Integer; // INVALID_ID4 if not specified
                              CurrentIndexID:       TABSObjectID; // INVALID_OBJECT_ID if not specified
                              FilterRecordPtr:      Pointer;
                              Dataset:              Pointer;
                              CurrentRecordID:      TABSRecordID;
                              // find first or last record in order specified by index settings
                              Restart:              Boolean;
                              // go from first record to last or vice versa
                              GoForward:            Boolean;
                              // if record was found then return record id
                              ResultRecordID:       PABSRecordID;
                              // if specified - fill record bitmap
                              RecordBitmap:         TABSRecordBitmap;
                              ForceFillWholeBitmap: Boolean
                             ): Boolean;

    // return true if record was found
    function FindRecordByScan(
                              SessionID:            TABSSessionID;
                              Conditions:           TABSScanSearchConditionArray;
                              ScanConditionNo:      Integer; // INVALID_ID4 if not specified
                              ScanEndConditionNo:   Integer; // INVALID_ID4 if not specified
                              CurrentIndexID:       TABSObjectID; // INVALID_OBJECT_ID if not specified
                              FilterRecordPtr:      Pointer;
                              Dataset:              Pointer;
                              CurrentRecordID:      TABSRecordID;
                              // find first or last record in order specified by index settings
                              Restart:              Boolean;
                              // go from first record to last or vice versa
                              GoForward:            Boolean;
                              // if record was found then return record id
                              ResultRecordID:       PABSRecordID;
                              RecordBitmap:         TABSRecordBitmap;
                              // if specified - fill record bitmap
                              ForceFillWholeBitmap: Boolean
                             ): Boolean;
    // used by GetRecordBuffer - find or get record
    function InternalFindOrGetRecordBuffer(
                              Cursor:         TABSCursor;
                              GetRecordMode:  TABSGetRecordMode;
                              // if specified - fill record bitmap
                              ForceFillWholeBitmap: Boolean=False
                            ): TABSGetRecordResult;
    // get record using current index or physical order
    procedure InternalGetRecordBuffer(
                                   SessionID:          TABSSessionID;
                                   var NavigationInfo: TABSNavigationInfo;
                                   IndexPositionCache: TABSIndexPositionCache;
                                   TableState:         Integer
                                      );
   public
    procedure ClearBlobsCacheInRecordBuffer(Buffer: TABSRecordBuffer); virtual;
    // read record with Distinct, SQLFilter, SQLTopRowCount, Filter, Range, OnFilterRecord
    function GetRecordBuffer(
                              Cursor:         TABSCursor;
                              GetRecordMode:  TABSGetRecordMode
                            ): TABSGetRecordResult; virtual;
    // locate
    function Locate(Cursor: TABSCursor; SearchExpression: TABSExpression): Boolean; virtual;
    // find key
    function FindKey(Cursor: TABSCursor; SearchCondition: TABSSearchCondition): Boolean; virtual;

    //---------------------------------------------------------------------------
    // BLOB methods
    //---------------------------------------------------------------------------
    procedure WriteBLOBFieldToRecordBuffer(
              Cursor:     TABSCursor;
              FieldNo:    Integer;
              BLOBStream: TABSStream
              ); virtual; abstract;

    procedure ClearBLOBFieldInRecordBuffer(
              RecordBuffer: TABSRecordBuffer;
              FieldNo:    Integer
              ); virtual;

    procedure ClearBLOBFieldsInRecordBuffer(
              RecordBuffer: TABSRecordBuffer
              ); virtual;

    function InternalCreateBlobStream(
              Cursor:   TABSCursor;
              ToInsert: Boolean;
              FieldNo:  Integer;
              OpenMode: TABSBLOBOpenMode
              ): TABSStream; virtual; abstract;

    procedure GetDirectBlobData(
              Cursor:     TABSCursor;
              FieldNo:    Integer;
              RecordBuffer: TABSRecordBuffer;
              var BLOBDescriptor: TABSPartialTemporaryBLOBDescriptor;
              var pBlobData: TAbsPByte); virtual; abstract;

    procedure SetDirectBlobData(
              Cursor:     TABSCursor;
              FieldNo:    Integer;
              RecordBuffer: TABSRecordBuffer;
              var BLOBDescriptor: TABSPartialTemporaryBLOBDescriptor;
              var pBlobData: TAbsPByte); virtual; abstract;

    procedure FreeDirectBlobData(
              Cursor:     TABSCursor;
              FieldNo:    Integer;
              RecordBuffer: TABSRecordBuffer;
              var BLOBDescriptor: TABSPartialTemporaryBLOBDescriptor;
              var pBlobData: TAbsPByte); virtual; abstract;

    function InsertRecord(var Cursor: TABSCursor): Boolean; virtual; abstract;
    function DeleteRecord(Cursor: TABSCursor): Boolean; virtual; abstract;
    function UpdateRecord(Cursor: TABSCursor): Boolean; virtual; abstract;

    // record operations
    function GetRecordCount(Cursor: TABSCursor): TABSRecordNo;
    procedure SetRecNo(Cursor: TABSCursor; RecNo: TABSRecordNo); virtual;
    function GetRecNo(Cursor: TABSCursor): TABSRecordNo; virtual;

    procedure InternalSetRecNo(Cursor: TABSCursor; RecNo: TABSRecordNo); virtual; abstract;
    function InternalGetRecNo(Cursor: TABSCursor): TABSRecordNo; virtual; abstract;
    function InternalGetRecordCount(Cursor: TABSCursor): TABSRecordNo; virtual; abstract;

    function LastAutoincValue(FieldNo: Integer; Session: TABSBaseSession): Int64; virtual;

    // transaction
    procedure Rollback(SessionID: TABSSessionID); virtual;
    procedure RebuildTemporaryIndexes(SessionID: TABSSessionID);

   public
    property TableName: string read FTableName write FTableName;
    property DatabaseData: TABSDatabaseData read FDatabaseData;
    property FieldManager: TABSBaseFieldManager read FFieldManager;
    property RecordManager: TABSBaseRecordManager read FRecordManager;
    property ConstraintManager: TABSBaseConstraintManager read FConstraintManager;
    property IndexManager: TABSBaseIndexManager read FIndexManager;
    property PageManager: TABSPageManager read FPageManager;
    property CursorList: TList read FCursorList;
    property IsRepairing: Boolean read FIsRepairing write FIsRepairing;
  end; // TABSTableData


////////////////////////////////////////////////////////////////////////////////
//
// TABSDatabaseData
//
////////////////////////////////////////////////////////////////////////////////


  TABSDatabaseData = class (TObject)
   protected
    FCSect:               TRTLCriticalSection;
    FSessionList:         TList;
    FTableDataList:       TList;
    FDatabaseName:        string;
    FPageManager:         TABSPageManager;
    FReadOnly:            Boolean;
    FTryCount:            Integer;
    FDelayMS:             Integer;
    FMultiUser:           Boolean;
    // 5.12 fix read-only problem in multiuser mode within application
    FSessionName: string;
   public
    constructor Create;
    destructor Destroy; override;
    procedure Lock;
    procedure Unlock;
    procedure ConnectSession(Session: TABSBaseSession); virtual;
    procedure DisconnectSession(Session: TABSBaseSession); virtual;
    procedure FreeIfNoSessionsConnected; virtual;
    procedure GetTablesList(List: TStrings); virtual;
    function TableExists(TableName: string): Boolean;

    procedure CreateDatabase; virtual;
    // create table data
    function CreateTableData(Cursor: TABSCursor): TABSTableData; virtual; abstract;
    // find table data
    function FindTableData(Cursor: TABSCursor): TABSTableData; virtual;
    // find or create table data
    function FindOrCreateTableData(Cursor: TABSCursor): TABSTableData; virtual;
    // get new ObjectId
    function GetNewObjectId: TABSObjectID; virtual; abstract;

    procedure Commit(SessionID: TABSSessionID; DoFlushBuffers: Boolean=True); virtual;
    procedure Rollback(SessionID: TABSSessionID); virtual;
    procedure FlushBuffers; virtual;

   public
    property DatabaseName: string read FDatabaseName write FDatabaseName;
    property PageManager: TABSPageManager read FPageManager write FPageManager;
    property ReadOnly: Boolean read FReadOnly write FReadOnly;
    property SessionList: TList read FSessionList;
    property MultiUser: Boolean read FMultiUser write FMultiUser;
    // 5.12
    property SessionName: string read FSessionName write FSessionName;
  end; // TABSDatabaseData




////////////////////////////////////////////////////////////////////////////////
//
// General functions and procedures
//
////////////////////////////////////////////////////////////////////////////////


  procedure ShowErrorMessage(ErrorMessage: string);
  procedure ShowWarningMessage(WarningMessage: string);


  // return false if field value is null
  function GetFieldData(
            // field number in FieldDefs
            FieldNo: Integer;
            // field definitions
            FieldDefs: TABSFieldDefs;
            // pointer to allocated buffer for storing field value
            Buffer: Pointer;
            // record buffer
            RecordBuffer: TABSRecordBuffer
                       ): Boolean;

  // returns false if field value is null
  procedure SetFieldData(
            // physical field No
            FieldNo: Integer;
            // field definitions
            FieldDefs: TABSFieldDefs;
            // pointer to allocated buffer with new field value
            Buffer: Pointer;
            // record buffer
            RecordBuffer: TABSRecordBuffer
                        );

 // return true if record buffers are binary identical
 function CompareRecordBuffers(RecordBuffer1,RecordBuffer2: TABSRecordBuffer;
                              RecordBufferSize: Integer): Boolean;


 var
  IsDesignMode:          Boolean;

implementation

uses

// AbsoluteDatabase units
     ABSMain,
     ABSBTree,
     ABSLocalEngine,
     ABSDiskEngine;



////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseFieldManager
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// GetNextAutoincVal
//------------------------------------------------------------------------------
function TABSBaseFieldManager.GetNextAutoincVal(FieldNo: Integer): TABSSequenceValue;
begin
  if (FieldNo >= FieldDefs.Count) then
    raise EABSException.Create(30402, ErrorGFieldWithObjectIdNotFound, [FieldNo]);
  if (FieldNo >= Length(FLastAutoIncValues)) then
    raise EABSException.Create(30403, ErrorGFieldWithObjectIdNotFound, [FieldNo]);

  if (FFieldDefs.Items[FieldNo].AutoincIncrement <> 0) then
    Inc(FLastAutoIncValues[FieldNo], FFieldDefs.Items[FieldNo].AutoincIncrement)
  else
    Inc(FLastAutoIncValues[FieldNo]);
  if ((FFieldDefs.Items[FieldNo].AutoincMaxValue <> 0) and
      (FLastAutoIncValues[FieldNo] > FFieldDefs.Items[FieldNo].AutoincMaxValue)) then
    if (FFieldDefs.Items[FieldNo].AutoincCycled) then
      FLastAutoIncValues[FieldNo] := FFieldDefs.Items[FieldNo].AutoincMinValue
    else
      raise EABSException.Create(20160, ErrorAAutoincMaxValueExceeded, [FFieldDefs.Items[FieldNo].Name]);

  Result := FLastAutoIncValues[FieldNo];
end;//GetNextAutoincVal


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSBaseFieldManager.Create(TableData: TABSTableData);
begin
  FTableData := TableData;
  FFieldDefs := TABSFieldDefs.Create;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSBaseFieldManager.Destroy;
begin
  FFieldDefs.Free;
end;//Destroy


//------------------------------------------------------------------------------
// load from stream
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.LoadMetadata(Stream: TStream);
begin
 FieldDefs.LoadFromStream(Stream);
 RecalcFieldOffsets;
 InitLastAutoIncValues;
end; // LoadMetadata


//------------------------------------------------------------------------------
// save to stream
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.SaveMetadata(Stream: TStream);
begin
 FieldDefs.SaveToStream(Stream);
end; // SaveMetadata


//------------------------------------------------------------------------------
// LoadMostUpdated
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.LoadMostUpdated(Buf: TAbsPByte; var Offset: Integer);
var cnt,i: Integer;
begin
 cnt := PInteger(Buf+Offset)^;
 Inc(Offset, sizeof(Integer));
 SetLength(FLastAutoIncValues, cnt);
 for i:=0 to cnt-1 do
   begin
     FLastAutoIncValues[i] := PInt64(Buf+Offset)^;
     Inc(Offset, sizeof(Int64));
   end;
end;// LoadMostUpdated


//------------------------------------------------------------------------------
// SaveMostUpdated
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.SaveMostUpdated(Buf: TAbsPByte; var Offset: Integer);
var cnt,i: Integer;
begin
 cnt := Length(FLastAutoIncValues);
 PInteger(Buf+Offset)^ := cnt;
 Inc(Offset, sizeof(Integer));
 for i:=0 to cnt-1 do
   begin
     PInt64(Buf+Offset)^ := FLastAutoIncValues[i];
     Inc(Offset, sizeof(Int64));
   end;
end;// SaveMostUpdated


//------------------------------------------------------------------------------
// RecalcFieldOffsets
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.RecalcFieldOffsets;
begin
 FFieldDefs.RecalcFieldOffsets;
end;//RecalcFieldOffsets


//------------------------------------------------------------------------------
// InitLastAutoIncValues
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.InitLastAutoIncValues;
var
  i: Integer;
begin
  SetLength(FLastAutoIncValues, FieldDefs.Count);
  for i:=0 to Length(FLastAutoIncValues)-1 do
    if (IsAutoincFieldType(FieldDefs.Items[i].AdvancedFieldType)) then
      FLastAutoIncValues[i] := FieldDefs.Items[i].AutoincInitialValue
    else
      FLastAutoIncValues[i] := 0;
end;// InitLastAutoIncValues


//------------------------------------------------------------------------------
// DiskRecordBufferToMemRecordBuffer
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.DiskRecordBufferToMemRecordBuffer(DiskRecordBuffer, MemRecordBuffer: TAbsPByte);
var
  i: Integer;
  NullFlagsSize: Integer;
begin
  FillChar(MemRecordBuffer^, FFieldDefs.GetMemoryRecordBufferSize, #0);

  // move NULL flags
  NullFlagsSize := FFieldDefs.Count div 8;
  if ((FFieldDefs.Count mod 8) > 0) then
   Inc(NullFlagsSize);
  Move(DiskRecordBuffer^, MemRecordBuffer^, NullFlagsSize);

  // move fields
  for i:=0 to FFieldDefs.Count-1 do
    Move((DiskRecordBuffer+FFieldDefs[i].DiskOffset)^,
         (MemRecordBuffer+FFieldDefs[i].MemoryOffset)^,
         FFieldDefs[i].DiskDataSize);
end;// DiskRecordBufferToMemRecordBuffer


//------------------------------------------------------------------------------
// MemRecordBufferToDiskRecordBuffer
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.MemRecordBufferToDiskRecordBuffer(MemRecordBuffer, DiskRecordBuffer: TAbsPByte);
var
  i: Integer;
  NullFlagsSize: Integer;
begin
  FillChar(DiskRecordBuffer^, FFieldDefs.GetDiskRecordBufferSize, #0);

  // move NULL flags
  NullFlagsSize := FFieldDefs.Count div 8;
  if ((FFieldDefs.Count mod 8) > 0) then
   Inc(NullFlagsSize);
  Move(MemRecordBuffer^, DiskRecordBuffer^, NullFlagsSize);

  // move fields
  for i:=0 to FFieldDefs.Count-1 do
    Move((MemRecordBuffer+FFieldDefs[i].MemoryOffset)^,
         (DiskRecordBuffer+FFieldDefs[i].DiskOffset)^,
         FFieldDefs[i].DiskDataSize);
end;// MemRecordBufferToDiskRecordBuffer


//------------------------------------------------------------------------------
// CompareRecordBuffers
//------------------------------------------------------------------------------
function TABSBaseFieldManager.CompareRecordBuffers(RecordBuffer1,
                                     RecordBuffer2: TABSRecordBuffer): Boolean;
var
  i: Integer;
  NullFlagsSize: Integer;
begin
  // compare NULL flags
  NullFlagsSize := FFieldDefs.Count div 8;
  if ((FFieldDefs.Count mod 8) > 0) then
   Inc(NullFlagsSize);
  Result := (StrLComp(PAnsiChar(RecordBuffer1), PAnsiChar(RecordBuffer2), NullFlagsSize) = 0);
  // compare fields?
  if (Result) then
    for i:=0 to FFieldDefs.Count-1 do
      begin
        Result := (StrLComp(PAnsiChar(RecordBuffer1+FFieldDefs[i].MemoryOffset),
                            PAnsiChar(RecordBuffer2+FFieldDefs[i].MemoryOffset),
                            FFieldDefs[i].DiskDataSize) = 0);
        if (not Result) then
          break;
      end;
end;// CompareRecordBuffers


//------------------------------------------------------------------------------
// BlobFieldsPresent
//------------------------------------------------------------------------------
function TABSBaseFieldManager.BlobFieldsPresent: Boolean;
var
  i: Integer;
begin
 Result := False;
 for i := 0 to FieldDefs.Count - 1 do
  if (IsBLOBFieldType(FieldDefs[i].BaseFieldType)) then
   begin
    Result := True;
    if (FieldDefs[i].BLOBBlockSize = 0) then
     raise EABSException.Create(10421,ErrorLZeroBlockSizeIsNotAllowedForField,[FieldDefs[i].Name]);
    break;
   end;
end;// BlobFieldsPresent


//------------------------------------------------------------------------------
// Set default values to fields
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.ApplyDefaultValuesToRecordBuffer(Session: TABSBaseSession;
  RecordBuffer: TABSRecordBuffer);
var
  i: Integer;
  value: TABSVariant;
begin
  for i:=0 to FFieldDefs.Count-1 do
    if (not IsBLOBFieldType(FFieldDefs[i].BaseFieldType)) then
      if (not FFieldDefs[i].DefaultValue.IsNull) then
        begin
          if (FFieldDefs[i].DefaultValueExpr <> nil) then
            begin
              value := TABSExpression(FFieldDefs[i].DefaultValueExpr).GetValue;
              value.Cast(FFieldDefs[i].BaseFieldType);
              value.CopyDataToAddress(RecordBuffer + FFieldDefs[i].MemoryOffset);
            end
          else
            FFieldDefs[i].DefaultValue.CopyDataToAddress(RecordBuffer + FFieldDefs[i].MemoryOffset);
          SetNullFlag(false, i, RecordBuffer);
        end;
end;//ApplyDefaultValuesToRecordBuffer


//------------------------------------------------------------------------------
// ApplyAutoincsToRecordBuffer
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.ApplyAutoincsToRecordBuffer(Session: TABSBaseSession;
  RecordBuffer: TABSRecordBuffer);
var
  i: Integer;
  SequenceValue: TABSSequenceValue;
begin
  for i:=0 to FFieldDefs.Count-1 do
      if (IsAutoincFieldType(FFieldDefs[i].AdvancedFieldType)) then
        if (CheckNullFlag(i, RecordBuffer)) then
          begin
            SequenceValue := GetNextAutoinc(Session, i);
            Move(SequenceValue, (RecordBuffer + FFieldDefs[i].MemoryOffset)^,
                 FFieldDefs[i].MemoryDataSize);
            SetNullFlag(false, i, RecordBuffer);
          end
        else
          begin
            SequenceValue := 0;
            // update auto inc to the inserted value
            Move((RecordBuffer + FFieldDefs[i].MemoryOffset)^, SequenceValue,
                 FFieldDefs[i].MemoryDataSize);
            UpdateLastAutoInc(Session, i, SequenceValue);
          end;
end;//ApplyAutoincsToRecordBuffer


//------------------------------------------------------------------------------
// GetNextAutoinc
//------------------------------------------------------------------------------
function TABSBaseFieldManager.GetNextAutoinc(Session: TABSBaseSession;
  FieldNo: Integer): TABSSequenceValue;
begin
  // Get Next Value
  Result := GetNextAutoincVal(FieldNo);
  // Put Value to Session...
  Session.SetObject(TABSSessionNamedObjectSequenceValue.Create(
                                        FFieldDefs[FieldNo].ObjectID,
                                        Result));
end;//GetNextAutoinc


//------------------------------------------------------------------------------
// GetLastAutoinc
//------------------------------------------------------------------------------
function TABSBaseFieldManager.GetLastAutoinc(Session: TABSBaseSession;
  FieldNo: Integer): TABSSequenceValue;
var
  Value: TABSSessionNamedObjectSequenceValue;
begin
  Value := TABSSessionNamedObjectSequenceValue(
                          Session.GetObjectByID(FFieldDefs[FieldNo].ObjectID));
  if Value = nil then
  begin
    // 5.08, improve GetLastAutoinc
    if Session.MultiUser then
    // end 5.08 fix
      raise EABSException.Create(30012, ErrorGSequenceLastValueFailed,
                                                       [FFieldDefs[FieldNo].Name])
    // 5.08, improve GetLastAutoinc
    else begin

      if (IsAutoincFieldType(FieldDefs.Items[FieldNo].AdvancedFieldType)) then
        Result := FLastAutoIncValues[FieldNo]
      else
        Result := 0;
    // end 5.08 fix
    end;
  end
  else
    Result := Value.Value;
end;//GetLastAutoinc


//------------------------------------------------------------------------------
// UpdateAutoinc
//------------------------------------------------------------------------------
procedure TABSBaseFieldManager.UpdateLastAutoinc(Session: TABSBaseSession;
                                        FieldNo: Integer; InsertedValue: Int64);
begin
  if (InsertedValue > FLastAutoIncValues[FieldNo]) then
    begin
      FLastAutoIncValues[FieldNo] := InsertedValue;
      // Put Value to Session...
      Session.SetObject(TABSSessionNamedObjectSequenceValue.Create(
                                        FFieldDefs[FieldNo].ObjectID,
                                        InsertedValue));
    end;
end;// UpdateLastAutoinc



////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseIndexManager
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// load from stream
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.LoadMetadata(Stream: TStream);
begin
  FIndexDefs.LoadFromStream(Stream);
end; // SaveToStream


//------------------------------------------------------------------------------
// save to stream
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.SaveMetadata(Stream: TStream);
begin
  FIndexDefs.SaveToStream(Stream);
end; // SaveToStream


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
constructor TABSBaseIndexManager.Create(aTableData: TABSTableData; TempPageManager: TABSPageManager);
begin
 FOpenIndexList := TList.Create; // list of TABSIndex objects
 FIndexDefs := TABSIndexDefs.Create;
 FTableData := aTableData;
 FPageManager := FTableData.PageManager;
 FTemporaryPageManager := TempPageManager;
 FSynchronizingTemporaryIndexes := False;
end;// Create


//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSBaseIndexManager.Destroy;
begin
 while (FOpenIndexList.Count > 0) do
   CloseIndex(TABSIndex(FOpenIndexList.Items[0]).IndexDef.ObjectID);
 FOpenIndexList.Free;
 FIndexDefs.Free;
end;// Destroy


//------------------------------------------------------------------------------
// return PageManager for index
//------------------------------------------------------------------------------
function TABSBaseIndexManager.GetPageManager(Index: TABSIndex): TABSPageManager;
begin
  if (Index.IndexDef.Temporary) then
    Result := FTemporaryPageManager
  else
    Result := FPageManager;
end; // GetPageManager


//------------------------------------------------------------------------------
// DropTemporaryIndexes
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.DropTemporaryIndexes(SessionID: TABSSessionID);
var i: Integer;
begin
  i := 0;
  while (i < IndexDefs.Count) do
   begin
    if (IndexDefs[i].Temporary) then
     DropIndex(SessionID, IndexDefs[i].ObjectID)
    else
     Inc(i);
   end;
end;// DropTemporaryIndexes


//------------------------------------------------------------------------------
// return true if coresponding index was found
//------------------------------------------------------------------------------
function TABSBaseIndexManager.IsIndexExists(FieldNames, AscDescList, CaseSensitivityList: TStringList;
                                     SessionID: TABSSessionID; FieldDefs: TABSFieldDefs): Boolean;
begin
  Result := FIndexDefs.IsIndexExists(FieldNames,AscDescList,CaseSensitivityList,SessionID,FieldDefs);
end; // IsIndexExists


//------------------------------------------------------------------------------
// return IndexID if coresponding index was found
//------------------------------------------------------------------------------
function TABSBaseIndexManager.FindIndex(FieldNames, AscDescList, CaseSensitivityList: TStringList;
                              SessionID: TABSSessionID; FieldDefs: TABSFieldDefs): TABSObjectID;
begin
  Result := FIndexDefs.FindIndex(FieldNames,AscDescList,CaseSensitivityList, SessionID, FieldDefs);
end; // FindIndex


//------------------------------------------------------------------------------
// create temporary index
//------------------------------------------------------------------------------
function TABSBaseIndexManager.CreateTemporaryIndex(
          Cursor: TABSCursor;
          FieldNamesList, AscDescList, CaseSensitivityList: TStringList
                                                  ): TABSObjectID;
var IndexDef: TABSIndexDef;
begin
  IndexDef := TABSIndexDef.Create;
  try
   repeat
     IndexDef.Name := TABSObjectName(GetTemporaryName('TEMPORARY_INDEX_'));
   until (IndexDefs.GetDefNumberByName(IndexDef.Name) = -1);
   IndexDef.Temporary := True;
   IndexDef.IndexType := itBTree;
   IndexDef.Unique := False;
   IndexDef.Primary := False;
   IndexDef.AssignByNames(FieldNamesList,AscDescList,CaseSensitivityList);
   IndexDef.SessionID := Cursor.Session.SessionID;
   IndexDef.TableState := FTableData.RecordManager.TableState;
   Result := CreateIndex(Cursor, IndexDef);
  finally
   IndexDef.Free;
  end;
end; // CreateTemporaryIndex


//------------------------------------------------------------------------------
// find or create temporary index
//------------------------------------------------------------------------------
function TABSBaseIndexManager.FindOrCreateIndex(Cursor: TABSCursor;
                FieldNamesList, AscDescList, CaseSensitivityList: TStringList; var IsCreated: Boolean): TABSObjectID;
begin
  Result := FindIndex(FieldNamesList,AscDescList,CaseSensitivityList,
                      Cursor.Session.SessionID, Cursor.FieldDefs);
  if (Result = INVALID_OBJECT_ID) then
    begin
      Result := CreateTemporaryIndex(Cursor, FieldNamesList,AscDescList,CaseSensitivityList);
      IsCreated := True;
    end
  else
    IsCreated := False;
end; // FindOrCreateIndex


//------------------------------------------------------------------------------
// create index definitions list
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.CreateIndexDefs(aIndexDefs: TABSIndexDefs);
begin
  FIndexDefs.Assign(aIndexDefs);
end;// CreateIndexDefs


//------------------------------------------------------------------------------
// CreateIndexesByIndexDefs
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.CreateIndexesByIndexDefs(Cursor: TABSCursor);
var
  i: Integer;
begin
  for i := 0 to IndexDefs.Count-1 do
   InternalCreateIndex(Cursor, IndexDefs.Items[i]);
end;// CreateIndexesByIndexDefs


//------------------------------------------------------------------------------
// FindOpenIndex
//------------------------------------------------------------------------------
function TABSBaseIndexManager.FindOpenIndex(IndexID: TABSObjectID): TABSIndex;
var
  i: Integer;
begin
  Result := nil;
  for i := 0 to FOpenIndexList.Count-1 do
   if (TABSIndex(FOpenIndexList.Items[i]).IndexDef.ObjectID = IndexID) then
    begin
     Result := TABSIndex(FOpenIndexList.Items[i]);
     break;
    end;
end;// FindOpenIndex


//------------------------------------------------------------------------------
// create index
//------------------------------------------------------------------------------
function TABSBaseIndexManager.CreateIndex(
                         Cursor: TABSCursor;
                         IndexDef: TABSIndexDef
                        ): TABSObjectID;
begin
  IndexDef.ObjectID := FTableData.DatabaseData.GetNewObjectID;
  FIndexDefs.AddCreated.Assign(IndexDef);
  InternalCreateIndex(Cursor, IndexDef);
  Result := IndexDef.ObjectID;
end;// CreateIndex


//------------------------------------------------------------------------------
// return index ID if Index exists, otherwise return NULL_ID
//------------------------------------------------------------------------------
function TABSBaseIndexManager.OpenIndex(IndexID: TABSObjectID): TABSIndex;
begin
  Result := FindOpenIndex(IndexID);
  if (Result = nil) then
    Result := InternalOpenIndex(IndexID);
end;// OpenIndex


//------------------------------------------------------------------------------
// close index
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.CloseIndex(IndexID: TABSObjectID);
var
  Index: TABSBTreeRecordIndex;
begin
  Index := TABSBTreeRecordIndex(FindOpenIndex(IndexID));
  if (Index <> nil) then
   begin
    FOpenIndexList.Remove(Index);
    Index.Free;
   end;
end;// CloseIndex


//------------------------------------------------------------------------------
// drop index
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.DropIndex(SessionID: TABSSessionID; IndexID: TABSObjectID);
var
  Index: TABSIndex;
begin
  Index := OpenIndex(IndexID);
  Index.DropIndex(SessionID);
  CloseIndex(IndexID);
  FIndexDefs.Delete(FIndexDefs.GetDefNumberByObjectId(IndexID));
end;// DropIndex


//------------------------------------------------------------------------------
// DropAllIndexes
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.DropAllIndexes(SessionID: TABSSessionID);
begin
 while (IndexDefs.Count > 0) do
   DropIndex(SessionID, IndexDefs.Items[0].ObjectID);
end;// DropAllIndexes


//------------------------------------------------------------------------------
// EmptyIndex
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.EmptyIndex(SessionID: TABSSessionID; IndexID: TABSObjectID);
var
  Index: TABSIndex;
begin
  Index := OpenIndex(IndexID);
  Index.EmptyIndex(SessionID);
  CloseIndex(IndexID);
end;// EmptyIndex


//------------------------------------------------------------------------------
// EmptyAllIndexes
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.EmptyAllIndexes(SessionID: TABSSessionID);
var
  i: Integer;
begin
  for i:=0 to IndexDefs.Count-1 do
   EmptyIndex(SessionID, IndexDefs.Items[i].ObjectID);
end;// EmptyAllIndexes


//------------------------------------------------------------------------------
// GetRecordBuffer
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.GetRecordBuffer(
                               SessionID: TABSSessionID;
                               var NavigationInfo: TABSNavigationInfo;
                               IndexPositionCache: TABSIndexPositionCache;
                               TableState:         Integer
                             );
var
  NavigationInfo1: TABSNavigationInfo;
begin
  NavigationInfo1 := NavigationInfo;
  NavigationInfo1.GetRecordResult := grrOK;
  if ((not NavigationInfo.FirstPosition) and (not NavigationInfo.LastPosition)) then
   begin
    NavigationInfo1.GetRecordMode := grmCurrent;
    NavigationInfo1.FirstPosition := False;
    NavigationInfo1.LastPosition := False;
    TableData.RecordManager.GetRecordBuffer(SessionID, NavigationInfo1);
   end;
  if (NavigationInfo1.GetRecordResult <> grrError) then
   OpenIndex(NavigationInfo.IndexID).GetRecordBuffer(SessionID, NavigationInfo,
                                               IndexPositionCache, TableState)
  else
   OpenIndex(NavigationInfo.IndexID).GetClosestRecordBuffer(SessionID, NavigationInfo,
                                               IndexPositionCache, TableState)

end;// GetRecordBuffer


//------------------------------------------------------------------------------
// InsertRecord
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.InsertRecord(Cursor: TABSCursor);
var
  i: Integer;
  Index: TABSIndex;
begin
  for i := 0 to FIndexDefs.Count-1 do
   begin
    Index := OpenIndex(FIndexDefs.Items[i].ObjectID);
    Index.InsertRecord(Cursor);
    FIndexDefs.Items[i].TableState := FIndexDefs.Items[i].TableState + 1;
    if (FIndexDefs.Items[i].RootPageNo <> Index.IndexDef.RootPageNo) then
      FIndexDefs.Items[i].RootPageNo := Index.IndexDef.RootPageNo;
   end;
end;// InsertRecord


//------------------------------------------------------------------------------
// UpdateRecord
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.UpdateRecord(Cursor: TABSCursor; BeforeNewRecordIsStored: Boolean);
var
  i:           Integer;
  Index:       TABSIndex;
begin
  for i := 0 to FIndexDefs.Count-1 do
    begin
     Index := OpenIndex(FIndexDefs.Items[i].ObjectID);
     Index.UpdateRecord(Cursor, BeforeNewRecordIsStored);
     if (not BeforeNewRecordIsStored) then
       FIndexDefs.Items[i].TableState := FIndexDefs.Items[i].TableState + 1;
    end;
end;// UpdateRecord


//------------------------------------------------------------------------------
// DeleteRecord
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.DeleteRecord(Cursor: TABSCursor);
var
  i:              Integer;
  Index:          TABSIndex;
  NavigationInfo: TABSNavigationInfo;
begin
  NavigationInfo.RecordBuffer := Cursor.CurrentRecordBuffer;
  NavigationInfo.RecordID := Cursor.CurrentRecordID;
  NavigationInfo.FirstPosition := False;
  NavigationInfo.LastPosition := False;
  NavigationInfo.GetRecordMode := grmCurrent;
  TableData.RecordManager.GetRecordBuffer(Cursor.Session.SessionID, NavigationInfo);

  if (NavigationInfo.GetRecordResult <> grrError) then
   for i := 0 to FIndexDefs.Count-1 do
    begin
     Index := OpenIndex(FIndexDefs.Items[i].ObjectID);
     Index.DeleteRecord(Cursor);
     FIndexDefs.Items[i].TableState := FIndexDefs.Items[i].TableState + 1;
    end;
end;// DeleteRecord


//------------------------------------------------------------------------------
// Rebuild Temporary Indexes if necessary
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.SynchronizeTemporaryIndexes(Cursor: TABSCursor; var IsChanged: Boolean);
var
  i:     Integer;
  Index: TABSIndex;
begin
  IsChanged := false;
  if (not FSynchronizingTemporaryIndexes) then
    begin
      for i := 0 to FIndexDefs.Count-1 do
        if ((FIndexDefs.Items[i].Temporary) and
            (FIndexDefs.Items[i].TableState <> FTableData.RecordManager.TableState)) then
          begin
            FSynchronizingTemporaryIndexes := True;
            try
              Index := OpenIndex(FIndexDefs.Items[i].ObjectID);
              Index.RebuildTemporaryIndex(Cursor);
              FIndexDefs.Items[i].TableState := FTableData.RecordManager.TableState;
              IsChanged := True;
            finally
              FSynchronizingTemporaryIndexes := False;
            end;
          end;
    end;
end;// SynchronizeTemporaryIndexes


//------------------------------------------------------------------------------
// RenameField
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.RenameField(FieldName, NewFieldName: string);
var
  i,j:     Integer;
begin
  for i := 0 to FIndexDefs.Count-1 do
    for j := 0 to FIndexDefs.Items[i].ColumnCount-1 do
      if (AnsiLowerCase(FIndexDefs.Items[i].Columns[j].FieldName) =
          AnsiLowerCase(FieldName)) then
        FIndexDefs.Items[i].Columns[j].FieldName := NewFieldName;
end;// RenameField


//------------------------------------------------------------------------------
// create index
//------------------------------------------------------------------------------
procedure TABSBaseIndexManager.InternalCreateIndex(Cursor: TABSCursor; IndexDef: TABSIndexDef);
var
  Index: TABSBTreeRecordIndex;
begin
  Index := TABSBTreeRecordIndex.Create(Self);
  try
   try
    Index.CreateIndex(Cursor, IndexDef);
    FIndexDefs.Items[FIndexDefs.GetDefNumberByObjectId(IndexDef.ObjectID)].Assign(IndexDef);
   except
    FIndexDefs.Delete(FIndexDefs.GetDefNumberByObjectId(IndexDef.ObjectID));
    raise;
   end;
  finally
   Index.Free;
  end;
end;// InternalCreateIndex


//------------------------------------------------------------------------------
// Internal open index
//------------------------------------------------------------------------------
function TABSBaseIndexManager.InternalOpenIndex(
                IndexID: TABSObjectID
                                       ): TABSIndex;
var
  Index: TABSBTreeRecordIndex;
  IndexNo: Integer;
begin
  IndexNo := FIndexDefs.GetDefNumberByObjectId(IndexID);
  if (IndexNo = -1) then
   raise EABSException.Create(20018, ErrorAIndexNotFound);
  Index := TABSBTreeRecordIndex.Create(Self);
  Index.OpenIndex(FIndexDefs[IndexNo]);
  FOpenIndexList.Add(Index);
  Result := Index;
end;// InternalOpenIndex



////////////////////////////////////////////////////////////////////////////////
//
// TABSIndex
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
constructor TABSIndex.Create(aIndexManager: TABSBaseIndexManager);
begin
  FIndexManager := aIndexManager;
  FIndexDef := nil;
end;// Create



//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSIndex.Destroy;
begin
  if (FIndexDef <> nil) then
   FIndexDef.Free;
end;// Destroy


//------------------------------------------------------------------------------
// create index
//------------------------------------------------------------------------------
procedure TABSIndex.CreateIndex(Cursor: TABSCursor; aIndexDef: TABSIndexDef);
begin
 if (FIndexDef <> aIndexDef) then
   begin
     if (FIndexDef <> nil) then
      FIndexDef.Free;
     FIndexDef := TABSIndexDef.Create;
     FIndexDef.Assign(aIndexDef);
   end;
end;// CreateIndex


//------------------------------------------------------------------------------
// OpenIndex
//------------------------------------------------------------------------------
procedure TABSIndex.OpenIndex(aIndexDef: TABSIndexDef);
begin
 if (FIndexDef <> nil) then
  FIndexDef.Free;
 FIndexDef := TABSIndexDef.Create;
 FIndexDef.Assign(aIndexDef);
end;// OpenIndex



////////////////////////////////////////////////////////////////////////////////
//
// TABSBaseConstraintManager
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// load from stream
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.LoadMetadata(Stream: TStream);
begin
 ConstraintDefs.LoadFromStream(Stream);
end; // LoadMetadata


//------------------------------------------------------------------------------
// save to stream
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.SaveMetadata(Stream: TStream);
begin
 ConstraintDefs.SaveToStream(Stream);
end; // SaveMetadata


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSBaseConstraintManager.Create(aTableData: TABSTableData);
begin
  FTableData := aTableData;
  FConstraintDefs := TABSConstraintDefs.Create;
end;//Create


//------------------------------------------------------------------------------
// Denstructor
//------------------------------------------------------------------------------
destructor TABSBaseConstraintManager.Destroy;
begin
  FConstraintDefs.Free;
end;//Destroy


//------------------------------------------------------------------------------
// check constraint conditions
//------------------------------------------------------------------------------
function TABSBaseConstraintManager.CheckConstraints(
                                SessionID:       TABSSessionID;
                                NewRecordBuffer: TABSRecordBuffer;
                                OldRecordBuffer: TABSRecordBuffer;
                                ToInsert:        Boolean;
                                CurrentRecordID: TABSRecordID;
                                RaiseException:  Boolean;
                                CheckUniqueIndexes: Boolean
                                                    ): Boolean;
var i,j: Integer;
    ObjId: TABSObjectID;
    //FieldDef: TABSFieldDef;
    n: Integer;
    AllNull: Boolean;
    fs: string;
begin
  Result := True;
  for i:=0 to ConstraintDefs.Count-1 do
    case ConstraintDefs[i].ConstraintType of
      ctNotNull:
        begin
          ObjId := TABSConstraintDefNotNull(ConstraintDefs[i]).ColumnObjectID;
          n := FTableData.FieldManager.FieldDefs.GetDefNumberByObjectId(ObjId);
          if n = -1 then
            raise EABSException.Create(30024, ErrorGFieldWithObjectIdNotFound, [ObjId]);
          if CheckNullFlag(n, NewRecordBuffer) then
            if (RaiseException) then
              raise EABSException.Create(30026, ErrorGConstraintNotNull,
                                   [ConstraintDefs[i].Name,
                                    FTableData.FieldManager.FieldDefs[n].Name])
            else
              begin
                Result := False;
                break;
              end;
        end;
      ctCheck:
        begin
          ObjId := TABSConstraintDefCheck(ConstraintDefs[i]).ColumnObjectID;
          n := FTableData.FieldManager.FieldDefs.GetDefNumberByObjectId(ObjId);
          if n = -1 then
            raise EABSException.Create(30028, ErrorGFieldWithObjectIdNotFound, [ObjId]);
          if (not CheckNullFlag(n, NewRecordBuffer)) then
           begin
             // Check Min Constraint ( Min > Value ==> Raise )
             if (not TABSConstraintDefCheck(ConstraintDefs[i]).MinValue.IsNull) then
              if CompareValueBuffers(
                              TABSConstraintDefCheck(ConstraintDefs[i]).MinValue.pData,
                              NewRecordBuffer + FTableData.FieldManager.FieldDefs[n].MemoryOffset,
                              TABSConstraintDefCheck(ConstraintDefs[i]).MinValue.DataType,
                              FTableData.FieldManager.FieldDefs[n].BaseFieldType
                                    ) = cmprGreater then
                if (RaiseException) then
                  raise EABSException.Create(30029, ErrorGConstraintCheckMinViolated,
                       [ConstraintDefs[i].Name,
                        FTableData.FieldManager.FieldDefs[n].Name,
                        TABSConstraintDefCheck(ConstraintDefs[i]).MinValue.AsAnsiString])
                else
                  begin
                    Result := False;
                    break;
                  end;

             // Check Max Constraint ( Max < Value ==> Raise )
             if (not TABSConstraintDefCheck(ConstraintDefs[i]).MaxValue.IsNull) then
              if CompareValueBuffers(
                              TABSConstraintDefCheck(ConstraintDefs[i]).MaxValue.pData,
                              NewRecordBuffer + FTableData.FieldManager.FieldDefs[n].MemoryOffset,
                              TABSConstraintDefCheck(ConstraintDefs[i]).MaxValue.DataType,
                              FTableData.FieldManager.FieldDefs[n].BaseFieldType
                                    ) = cmprLower then
                if (RaiseException) then
                  raise EABSException.Create(30030, ErrorGConstraintCheckMaxViolated,
                       [ConstraintDefs[i].Name,
                        FTableData.FieldManager.FieldDefs[n].Name,
                        TABSConstraintDefCheck(ConstraintDefs[i]).MaxValue.AsAnsiString])
                else
                  begin
                    Result := False;
                    break;
                  end;
           end;
        end;
      ctPK:
        if (CheckUniqueIndexes) then
          begin
            // Check NULL
            AllNull := True;
            fs := '';
            for j:=0 to Length(TABSConstraintDefPrimary(ConstraintDefs[i]).Columns)-1 do
             begin
              ObjId := TABSConstraintDefPrimary(ConstraintDefs[i]).Columns[j].ColumnObjectID;
              n := FTableData.FieldManager.FieldDefs.GetDefNumberByObjectId(ObjId);
              if n = -1 then
                raise EABSException.Create(30329, ErrorGFieldWithObjectIdNotFound, [ObjId]);
              fs := fs + FTableData.FieldManager.FieldDefs[n].Name + ',';
              if not CheckNullFlag(n, NewRecordBuffer) then
                begin
                  AllNull := False;
                  Break;
                end;
             end;
             SetLength(fs, Length(fs)-1);
             if AllNull then
               if (RaiseException) then
                 raise EABSException.Create(30330, ErrorGConstraintNotNull,
                                     [ConstraintDefs[i].Name, fs])
               else
                begin
                  Result := False;
                  break;
                end;
            // Check UNIQUE
            if (FTableData.IsUniqueConstraintFailed(
                SessionID, TABSConstraintDefPrimary(ConstraintDefs[i]).IndexObjectID,
                 NewRecordBuffer, OldRecordBuffer, ToInsert, CurrentRecordID)) then
               if (RaiseException) then
                 raise EABSException.Create(30319, ErrorGConstraintPrimaryKeyViolated,
                                         [ConstraintDefs[i].Name])
               else
                begin
                  Result := False;
                  break;
                end;
          end;

      ctUnique:
        begin
          if (CheckUniqueIndexes) then
            begin
              if (FTableData.IsUniqueConstraintFailed(
                  SessionID, TABSConstraintDefUnique(ConstraintDefs[i]).IndexObjectID,
                   NewRecordBuffer, OldRecordBuffer, ToInsert, CurrentRecordID)) then
                if (RaiseException) then
                  raise EABSException.Create(30320, ErrorGConstraintUniqueViolated,
                                           [ConstraintDefs[i].Name])
                else
                  begin
                    Result := False;
                    break;
                  end;
            end;
        end;
      else
        raise EABSException.Create(30036, ErrorGNotImplementedYet);
    end;
end;//CheckConstraints


//------------------------------------------------------------------------------
//  Link object id
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.LinkObjectId(ConstraintDef: TABSConstraintDef);
var j: Integer;
    ColumnName, IndexName: TABSObjectName;
    n: Integer;
begin
    case ConstraintDef.ConstraintType of
      ctNotNull,
      ctCheck:
        begin
          ColumnName := TABSConstraintDefNotNull(ConstraintDef).ColumnName;
          n := FTableData.FieldManager.FieldDefs.GetDefNumberByName(ColumnName);
          if n = -1 then
            raise EABSException.Create(30027, ErrorGFieldWithNameNotFound,[ColumnName]);
          TABSConstraintDefNotNull(ConstraintDef).ColumnObjectID :=
                                  FTableData.FieldManager.FieldDefs[n].ObjectID;
        end;
      ctPK:
        begin
          // Set Index Object ID
          IndexName := TABSConstraintDefPrimary(ConstraintDef).IndexName;
          n := FTableData.IndexManager.FIndexDefs.GetDefNumberByName(IndexName);
          if n = -1 then
            raise EABSException.Create(30325, ErrorGIndexNameNotFound,[IndexName]);
          TABSConstraintDefPrimary(ConstraintDef).IndexObjectID :=
                                 FTableData.IndexManager.FIndexDefs[n].ObjectID;

          // Set Columns Object ID
          for j:=0 to Length(TABSConstraintDefPrimary(ConstraintDef).Columns)-1 do
            begin
              ColumnName := TABSConstraintDefPrimary(ConstraintDef).Columns[j].ColumnName;
              n := FTableData.FieldManager.FieldDefs.GetDefNumberByName(ColumnName);
              if n = -1 then
                raise EABSException.Create(30331, ErrorGFieldWithNameNotFound,[ColumnName]);
              TABSConstraintDefPrimary(ConstraintDef).Columns[j].ColumnObjectID :=
                              FTableData.FieldManager.FieldDefs[n].ObjectID;

            end;
        end;
      ctUnique:
        begin
          // Set Index Object ID
          IndexName := TABSConstraintDefUnique(ConstraintDef).IndexName;
          n := FTableData.IndexManager.FIndexDefs.GetDefNumberByName(IndexName);
          if n = -1 then
            raise EABSException.Create(30326, ErrorGIndexNameNotFound,[IndexName]);
          TABSConstraintDefUnique(ConstraintDef).IndexObjectID :=
                                 FTableData.IndexManager.FIndexDefs[n].ObjectID;

          // Set Columns Object ID
          for j:=0 to Length(TABSConstraintDefUnique(ConstraintDef).Columns)-1 do
            begin
              ColumnName := TABSConstraintDefUnique(ConstraintDef).Columns[j].ColumnName;
              n := FTableData.FieldManager.FieldDefs.GetDefNumberByName(ColumnName);
              if n = -1 then
                raise EABSException.Create(30332, ErrorGFieldWithNameNotFound,[ColumnName]);
              TABSConstraintDefUnique(ConstraintDef).Columns[j].ColumnObjectID :=
                              FTableData.FieldManager.FieldDefs[n].ObjectID;

            end;

        end;
      else
        raise EABSException.Create(30037, ErrorGNotImplementedYet);
    end;
end;//LinkObjectId


//------------------------------------------------------------------------------
//  Link object ids between constraints and fields
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.LinkObjectIds;
var
  i: Integer;
begin
  for i:=0 to ConstraintDefs.Count-1 do
    LinkObjectId(ConstraintDefs[i]);
end;//FillObjectIds


//------------------------------------------------------------------------------
// Generate Constraint Names for empty constraints
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.FillConstraintAutoNames;
var
  i, n: Integer;
  s, CName: string;
begin
  for i:=0 to FConstraintDefs.Count-1 do
   if (FConstraintDefs[i].Name = '') then
    begin
     s := AutoNameSymbol;
     case FConstraintDefs[i].ConstraintType of
      ctNotNull:
        begin
         s := s + AutoNameConstraintNotNullPrefix + AutoNameSymbol +
              TABSConstraintDefNotNull(FConstraintDefs[i]).TableName + AutoNameSymbol +
              TABSConstraintDefNotNull(FConstraintDefs[i]).ColumnName;
        end;
      ctCheck:
        begin
         s := s + AutoNameConstraintCheckPrefix + AutoNameSymbol +
              TABSConstraintDefNotNull(FConstraintDefs[i]).TableName + AutoNameSymbol +
              TABSConstraintDefNotNull(FConstraintDefs[i]).ColumnName;
        end;
      else
        raise EABSException.Create(30038, ErrorGNotImplementedYet);
     end;

     CName := s;

     // Check Unic Names
     n := FConstraintDefs.GetDefNumberByName(CName);
     while n <> -1 do
      begin
       CName := s + IntToStr(Random(MAXWORD));
       n := FConstraintDefs.GetDefNumberByName(CName);
      end;

      FConstraintDefs[i].Name := CName;
    end;

end;//FillConstraintAutoNames


//------------------------------------------------------------------------------
// add constraint for primary or Unique index
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.AddConstraintFromIndex(IndexDef: TABSIndexDef);
var
  ConstraintDef: TABSConstraintDef;
begin
  ConstraintDef := AddConstraintForIndex(IndexDef, FConstraintDefs);
  if (ConstraintDef <> nil) then
    begin
      ConstraintDef.ObjectID := FTableData.DatabaseData.GetNewObjectID;
      LinkObjectId(ConstraintDef);
    end;
end;//AddConstraintFromIndex


//------------------------------------------------------------------------------
// Delete Constraint Linked with deleted index
//------------------------------------------------------------------------------
procedure TABSBaseConstraintManager.DeleteConstraintForIndexID(IndexObjectID: TABSObjectID);
var
  i: Integer;
begin
  for i:=0 to FConstraintDefs.Count-1 do
    case FConstraintDefs[i].ConstraintType of
      ctPK:
        if (TABSConstraintDefPrimary(FConstraintDefs[i]).IndexObjectID = IndexObjectID ) then
          begin
            FConstraintDefs.Delete(i);
            break;
          end;
      ctUnique:
        if (TABSConstraintDefUnique(FConstraintDefs[i]).IndexObjectID = IndexObjectID ) then
          begin
            FConstraintDefs.Delete(i);
            break;
          end;
    end;
end;//DeleteConstraintForIndexID


////////////////////////////////////////////////////////////////////////////////
//
// TABSRecordBitmap
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// get size
//------------------------------------------------------------------------------
function TABSRecordBitmap.GetSize: TABSRecordNo;
begin
 Result := FBitmap.Size;
end; // GetSize

//------------------------------------------------------------------------------
// set size
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.SetSize(NewSize: TABSRecordNo);
begin
 FBitMap.Size := NewSize;
end; // SetSize


//------------------------------------------------------------------------------
// GetOutdated
//------------------------------------------------------------------------------
function TABSRecordBitmap.GetOutdated: Boolean;
begin
  Result := (FTableState <> FTableData.RecordManager.TableState);
end;// GetOutdated


//------------------------------------------------------------------------------
// Create
//------------------------------------------------------------------------------
constructor TABSRecordBitmap.Create(aTableData: TABSTableData);
begin
 if (aTableData = nil) then
  raise EABSException.Create(10425,ErrorLNilPointer);
 FTableData := aTableData;
 FBitmap := TABSBitsArray.Create;
 FActive := False;
 FIndexBased := False;
 FTableState := -1;
end; // Create


//------------------------------------------------------------------------------
// Destroy
//------------------------------------------------------------------------------
destructor TABSRecordBitmap.Destroy;
begin
 FBitmap.Free;
 FBitmap := nil;
 inherited;
end; // Destroy


//------------------------------------------------------------------------------
// return record count
//------------------------------------------------------------------------------
function TABSRecordBitmap.GetRecordCount: TABSRecordNo;
begin
 Result := FBitmap.NonZeroBitCount;
end; // GetRecordCount


//------------------------------------------------------------------------------
// return recno by RecordID
//------------------------------------------------------------------------------
function TABSRecordBitmap.GetRecNoByRecordID(RecordID: TABSRecordID): TABSRecordNo;
var
  BitNo: Integer;
begin
 BitNo := FTableData.GetBitmapRecNoByRecordID(RecordID);
 if (BitNo >= FBitmap.Size) then
   Result := -1
 else
   Result := FBitmap.GetBitPositionByBitNo(BitNo)+1;
end; // GetRecNoByRecordID


//------------------------------------------------------------------------------
// GetRecNoByIndexPos
//------------------------------------------------------------------------------
function TABSRecordBitmap.GetRecNoByIndexPos(IndexID: TABSObjectID; IndexPos: TABSIndexPosition): TABSRecordNo;
var
  BitNo: Integer;
begin
 BitNo := FTableData.GetBitmapRecNoByIndexPosition(IndexID, IndexPos);
 if (BitNo >= FBitmap.Size) then
   Result := -1
 else
   Result := FBitmap.GetBitPositionByBitNo(BitNo)+1;
end;// GetRecNoByIndexPos


//------------------------------------------------------------------------------
// return RecordID by recno
//------------------------------------------------------------------------------
function TABSRecordBitmap.GetRecordIDByRecNo(RecNo: TABSRecordNo): TABSRecordID;
begin
 Result := FTableData.GetRecordIDByBitmapRecNo(
            FBitmap.GetBitNoByBitPosition(RecNo-1));
end; // GetRecordIDByRecNo


//------------------------------------------------------------------------------
// GetIndexPosByRecNo
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.GetIndexPosByRecNo(IndexID: TABSObjectID; RecNo: TABSRecordNo; IndexPos: TABSIndexPosition);
begin
 FTableData.GetIndexPositionByBitmapRecNo(
                     IndexID, FBitmap.GetBitNoByBitPosition(RecNo-1), IndexPos);
end;// GetIndexPosByRecNo


//------------------------------------------------------------------------------
// return true if record is visible
//------------------------------------------------------------------------------
function TABSRecordBitmap.IsRecordVisible(RecordID: TABSRecordID): Boolean;
var
  RecNo: TABSRecordNo;
begin
  RecNo := FTableData.GetBitmapRecNoByRecordID(RecordID);
  if (RecNo < FBitmap.Size) then
    Result := FBitmap.GetBit(RecNo)
  else
    Result := False;
end; // IsRecordVisible


//------------------------------------------------------------------------------
// IsRecordVisible
//------------------------------------------------------------------------------
function TABSRecordBitmap.IsRecordVisible(IndexID: TABSObjectID; IndexPos: TABSIndexPosition): Boolean;
var
  RecNo: TABSRecordNo;
begin
  RecNo := FTableData.GetBitmapRecNoByIndexPosition(IndexID, IndexPos);
  if (RecNo < FBitmap.Size) then
    Result := FBitmap.GetBit(RecNo)
  else
    Result := False;
end;// IsRecordVisible


//------------------------------------------------------------------------------
// show record
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.ShowRecord(RecordID: TABSRecordID);
var
  RecNo: TABSRecordNo;
begin
  if (not IndexBased) then
    begin
      RecNo := FTableData.GetBitmapRecNoByRecordID(RecordID);
      if (RecNo >= FBitmap.Size) then
        FBitmap.Size := RecNo+1;
      FBitmap.SetBit(RecNo,True);
    end
  else
    Clear;
end; // ShowRecord


//------------------------------------------------------------------------------
// ShowRecord
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.ShowRecord(IndexID: TABSObjectID; IndexPos: TABSIndexPosition);
var
  RecNo: TABSRecordNo;
begin
  RecNo := FTableData.GetBitmapRecNoByIndexPosition(IndexID, IndexPos);
  if (RecNo >= FBitmap.Size) then
    FBitmap.Size := RecNo+1;
  FBitmap.SetBit(RecNo,True);
end;// ShowRecord


//------------------------------------------------------------------------------
// hide record
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.HideRecord(RecordID: TABSRecordID);
var RecNo: TABSRecordNo;
begin
  if (not IndexBased) then
    begin
      RecNo := FTableData.GetBitmapRecNoByRecordID(RecordID);
      if (RecNo >= FBitmap.Size) then
        FBitmap.Size := RecNo+1;
      FBitmap.SetBit(RecNo,False);
    end
  else
    Clear;
end; // HideRecord


//------------------------------------------------------------------------------
// HideRecord
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.HideRecord(IndexID: TABSObjectID; IndexPos: TABSIndexPosition);
var RecNo: TABSRecordNo;
begin
  RecNo := FTableData.GetBitmapRecNoByIndexPosition(IndexID, IndexPos);
  if (RecNo >= FBitmap.Size) then
    FBitmap.Size := RecNo+1;
  FBitmap.SetBit(RecNo,False);
end;// HideRecord


//------------------------------------------------------------------------------
// set all bits
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.SetAllBits;
begin
 FBitMap.SetAllBits;
end; // SetAllBits


//------------------------------------------------------------------------------
// Clear
//------------------------------------------------------------------------------
procedure TABSRecordBitmap.Clear;
begin
  FActive := False;
  Size := 0;
end;// Clear


//------------------------------------------------------------------------------
// FindRecord
//------------------------------------------------------------------------------
function TABSRecordBitmap.FindRecord(Restart, GoForward: Boolean; CurRecordID: TABSRecordID;
                   var FoundRecordID: TABSRecordID): Boolean;
var
  CurRecNo, FoundRecNo: Integer;
begin
  {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSRecordBitmap.FindRecord1 start');{$ENDIF}
  try
    CurRecNo := FTableData.GetBitmapRecNoByRecordID(CurRecordID);
    Result := FBitMap.Find(Restart, GoForward, CurRecNo, FoundRecNo);
    if (Result) then
      FoundRecordID := FTableData.GetRecordIDByBitmapRecNo(FoundRecNo);
  finally
    {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSRecordBitmap.FindRecord1 end');{$ENDIF}
  end;
{ TODO : apply first/last used bitno }
end;// FindRecord


//------------------------------------------------------------------------------
// FindRecord
//------------------------------------------------------------------------------
function TABSRecordBitmap.FindRecord(Restart, GoForward: Boolean; CurIndexPos, FoundRecordIndexPos: TABSIndexPosition;
                       IndexID: TABSObjectID): Boolean;
var
  CurRecNo, FoundRecNo: Integer;
begin
  {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSRecordBitmap.FindRecord2 start');{$ENDIF}
  try
    if (not Restart) then
      CurRecNo := FTableData.GetBitmapRecNoByIndexPosition(IndexID,CurIndexPos);
    Result := FBitMap.Find(Restart, GoForward, CurRecNo, FoundRecNo);
    if (Result) then
      FTableData.GetIndexPositionByBitmapRecNo(IndexID, FoundRecNo, FoundRecordIndexPos);
  finally
    {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSRecordBitmap.FindRecord2 end');{$ENDIF}
  end;
{ TODO : apply first/last used bitno }
end;// FindRecord



////////////////////////////////////////////////////////////////////////////////
//
// TABSTableData
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Fill New ObjectID for all defs
//------------------------------------------------------------------------------
procedure TABSTableData.FillDefsByObjectId(Defs: TABSMetaObjectDefs);
var i: Integer;
begin
  for i:=0 to Defs.Count-1 do
      Defs[i].ObjectId := FDatabaseData.GetNewObjectId;
end;//FillDefsByObjectId


//------------------------------------------------------------------------------
// calc record buffer size
//------------------------------------------------------------------------------
function TABSTableData.GetRecordBufferSize: Integer;
begin
 if ((FFieldManager.FieldDefs.Count <= 0) and (not IsDesignMode)) then
  raise EABSException.Create(10131,ErrorLNoFields);
 Result := FFieldManager.FieldDefs.GetMemoryRecordBufferSize;
end;// GetRecordBufferSize


//------------------------------------------------------------------------------
// create and init FieldManager
//------------------------------------------------------------------------------
procedure TABSTableData.CreateFieldManager(FieldDefs: TABSFieldDefs);
begin
 FillDefsByObjectId(FieldDefs);
 FFieldManager.FieldDefs.Assign(FieldDefs);
 FFieldManager.RecalcFieldOffsets;
 FFieldManager.InitLastAutoIncValues;
end;// CreateFieldManager


//------------------------------------------------------------------------------
// create and init IndexManager
//------------------------------------------------------------------------------
procedure TABSTableData.CreateIndexManager(IndexDefs: TABSIndexDefs);
begin
 FillDefsByObjectId(IndexDefs);
 FIndexManager.CreateIndexDefs(IndexDefs);
end;// CreateIndexManager


//------------------------------------------------------------------------------
// Create ConstraintManager
//------------------------------------------------------------------------------
procedure TABSTableData.CreateConstraintManager(ConstraintDefs: TABSConstraintDefs);
var i: Integer;
begin
  // Fill New ObjectIds
  FillDefsByObjectId(ConstraintDefs);
  // Set TableName to Constraits
  for i:=0 to ConstraintDefs.Count-1 do
   case ConstraintDefs[i].ConstraintType of
    ctNotNull:
      TABSConstraintDefNotNull(ConstraintDefs[i]).TableName := FTableName;
    ctCheck:
      TABSConstraintDefCheck(ConstraintDefs[i]).TableName := FTableName;
    ctPK:
      TABSConstraintDefPrimary(ConstraintDefs[i]).TableName := FTableName;
    ctUnique:
      TABSConstraintDefUnique(ConstraintDefs[i]).TableName := FTableName;
    else
      raise EABSException.Create(30058, ErrorGNotImplementedYet);
   end;
  FConstraintManager.ConstraintDefs.Assign(ConstraintDefs);
  // Links ObjectIds
  FConstraintManager.LinkObjectIds;
  // Set enpty Names
  FConstraintManager.FillConstraintAutoNames;
end;//CreateConstraintManager


//------------------------------------------------------------------------------
// init cursor
//------------------------------------------------------------------------------
procedure TABSTableData.InitCursor(Cursor: TABSCursor);
begin
 if (FFieldManager.FieldDefs.Count > 0) then
   Cursor.FieldValuesOffset := FFieldManager.FieldDefs[0].MemoryOffset
 else
   Cursor.FieldValuesOffset := 0;
 Cursor.RecordSize := GetRecordBufferSize;
end;// InitCursor


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
constructor TABSTableData.Create(aDatabaseData: TABSDatabaseData);
begin
 InitializeCriticalSection(FCSect);
 FCursorList := TList.Create;
 FFieldManager := nil;
 FRecordManager := nil;
 FIndexManager := nil;
 FDatabaseData := aDatabaseData;
 FConstraintManager := nil;
 FDisableTempFiles := False;
end;// Create


//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSTableData.Destroy;
begin
 if (FFieldManager <> nil) then
  FFieldManager.Free;
 FFieldManager := nil;
 if (FIndexManager <> nil) then
  FIndexManager.Free;
 FIndexManager := nil;
 if (FConstraintManager <> nil) then
  FConstraintManager.Free;
 FConstraintManager := nil;
 if (FRecordManager <> nil) then
  FRecordManager.Free;
 FRecordManager := nil;
 DeleteCriticalSection(FCSect);
 if (FCursorList <> nil) then
   FCursorList.Free;
 FCursorList := nil;
 FDatabaseData.FTableDataList.Remove(Self);
 inherited;
end;// Destroy


//------------------------------------------------------------------------------
// lock
//------------------------------------------------------------------------------
procedure TABSTableData.Lock;
begin
  FDatabaseData.Lock;
end;// Lock


//------------------------------------------------------------------------------
// unlock
//------------------------------------------------------------------------------
procedure TABSTableData.Unlock;
begin
//  LeaveCriticalSection(FCSect);
  FDatabaseData.Unlock;
end;// Unlock


//------------------------------------------------------------------------------
// ApplyChanges
//------------------------------------------------------------------------------
procedure TABSTableData.ApplyChanges(SessionID: TABSSessionID; InTransaction: Boolean);
begin
  if (not InTransaction) then
    FPageManager.ApplyChanges(SessionID);
end;// ApplyChanges


//------------------------------------------------------------------------------
// CancelChanges
//------------------------------------------------------------------------------
procedure TABSTableData.CancelChanges(SessionID: TABSSessionID; InTransaction: Boolean);
begin
  if (not InTransaction) then
    FPageManager.CancelChanges(SessionID);
end;// CancelChanges


//------------------------------------------------------------------------------
// delete table
//------------------------------------------------------------------------------
procedure TABSTableData.DeleteTable(Cursor: TABSCursor; DesignMode: Boolean);
begin
 if (not DesignMode) then
 if (FCursorList.Count > 0) then
  raise EABSException.Create(20017, ErrorACannotDeleteBusyTable, [FTableName]);
end; // DeleteTable


//------------------------------------------------------------------------------
// empty table
//------------------------------------------------------------------------------
procedure TABSTableData.EmptyTable(Cursor: TABSCursor);
begin
{ if (FCursorList.Count > 0) then
  raise EABSException.Create(10072, ErrorLCannotDeleteBusyTable, [FTableName]);}
 if (FFieldManager <> nil) then
   FFieldManager.InitLastAutoIncValues;
 if (Cursor <> nil) then
   EmptyAllIndexes(Cursor.Session.SessionID)
 else
   EmptyAllIndexes(SYSTEM_SESSION_ID);
end; // EmptyTable


//------------------------------------------------------------------------------
// rename table
//------------------------------------------------------------------------------
procedure TABSTableData.RenameTable(NewTableName: string; Cursor: TABSCursor);
begin
 if (FCursorList.Count > 0) then
  raise EABSException.Create(10150, ErrorLCannotRenameBusyTable, [FTableName]);
 FTableName := NewTableName;
end; // EmptyTable


//------------------------------------------------------------------------------
// open table
//------------------------------------------------------------------------------
procedure TABSTableData.OpenTable(Cursor: TABSCursor);
begin
 FCursorList.Add(Cursor);
end;// OpenTable


//------------------------------------------------------------------------------
// close table
//------------------------------------------------------------------------------
procedure TABSTableData.CloseTable(Cursor: TABSCursor);
begin
 if (FCursorList <> nil) then
   FCursorList.Remove(Cursor);
end;// CloseTable


//------------------------------------------------------------------------------
// Rename Field by Field Index in FieldDefs
//------------------------------------------------------------------------------
procedure TABSTableData.RenameField(FieldName, NewFieldName: string);
var
  fd: TABSFieldDef;
begin
  // Check Field Exists
  fd := FFieldManager.FieldDefs.GetFieldDefByName(FieldName);
  if (fd = nil) then
    raise EABSException.Create(30343, ErrorGFieldWithNameNotFound, [FieldName]);

  // Check For Duplicate FieldName
  if ( FFieldManager.FieldDefs.GetFieldDefByName(NewFieldName) <> nil ) then
   raise EABSException.Create(30344, ErrorGCannotRenameField,
                                     [FieldName, NewFieldName]);

  fd.Name := NewFieldName;

  FIndexManager.RenameField(FieldName, NewFieldName);
end;//RenameField


//------------------------------------------------------------------------------
// add index
//------------------------------------------------------------------------------
procedure TABSTableData.AddIndex(IndexDef: TABSIndexDef; Cursor: TABSCursor);
var
  OldPos: Pointer;
begin
 OldPos := Cursor.SavePosition;
 try
  FIndexManager.CreateIndex(Cursor, IndexDef);
  if ( (not IndexDef.Temporary) and (not Cursor.Temporary) ) then
    FConstraintManager.AddConstraintFromIndex(IndexDef);
 finally
  Cursor.RestorePosition(OldPos);
  Cursor.FreePosition(OldPos);
 end;
end;// AddIndex


//------------------------------------------------------------------------------
// DeleteIndex
//------------------------------------------------------------------------------
procedure TABSTableData.DeleteIndex(IndexID: TABSObjectID; Cursor: TABSCursor);
var
  IndexDef: TABSIndexDef;
begin
  // Drop Constraint
  if (Cursor <> nil) then
   begin
    IndexDef := TABSIndexDef(FIndexManager.IndexDefs.GetDefByObjectID(IndexID));
    if ( (not IndexDef.Temporary) and (not Cursor.Temporary) ) then
       FConstraintManager.DeleteConstraintForIndexID(IndexID);
    FIndexManager.DropIndex(Cursor.Session.SessionID, IndexID);
   end
  else
    FIndexManager.DropIndex(SYSTEM_SESSION_ID, IndexID);
end;// DeleteIndex


//------------------------------------------------------------------------------
// EmptyIndex
//------------------------------------------------------------------------------
procedure TABSTableData.EmptyIndex(IndexID: TABSObjectID; SessionID: TABSSessionID);
begin
 Lock;
 try
  FIndexManager.EmptyIndex(SessionID, IndexID)
 finally
  Unlock;
 end;
end;// EmptyIndex


//------------------------------------------------------------------------------
// DeleteAllIndexes
//------------------------------------------------------------------------------
procedure TABSTableData.DeleteAllIndexes(Cursor: TABSCursor);
begin
  if (FIndexManager <> nil) then
   while (FIndexManager.IndexDefs.Count > 0) do
    DeleteIndex(FIndexManager.IndexDefs.Items[0].ObjectID, Cursor);
end;// DeleteAllIndexes


//------------------------------------------------------------------------------
// EmptyAllIndexes
//------------------------------------------------------------------------------
procedure TABSTableData.EmptyAllIndexes(SessionID: TABSSessionID);
begin
 if (FIndexManager <> nil) then
  FIndexManager.EmptyAllIndexes(SessionID);
end;// EmptyAllIndexes


//------------------------------------------------------------------------------
// return index name
//------------------------------------------------------------------------------
function TABSTableData.FindOrCreateIndex(
                                Cursor:         TABSCursor;
                                FieldNamesList, AscDescList, CaseSensitivityList: TStringList;
                                var IsCreated: Boolean
                                        ): string;
var ID:       TABSObjectID;
    IndexDef: TABSMetaObjectDef;
    OldPos: Pointer;
begin
 Lock;
 OldPos := Cursor.SavePosition;
 try
   try
     ID := FIndexManager.FindOrCreateIndex(Cursor, FieldNamesList, AscDescList, CaseSensitivityList, IsCreated);
     IndexDef := FIndexManager.IndexDefs.GetDefByObjectId(ID);
     if (IndexDef = nil) then
       raise EABSException.Create(10324,ErrorLCannotFindIndexByID,[ID]);
     Result := IndexDef.Name;
     ApplyChanges(Cursor.Session.SessionID, Cursor.Session.InTransaction);
   except
     CancelChanges(Cursor.Session.SessionID, Cursor.Session.InTransaction);
     raise;
   end;
 finally
  Cursor.RestorePosition(OldPos);
  Cursor.FreePosition(OldPos);
  Unlock;
 end;
end; // FindOrCreateIndex


//------------------------------------------------------------------------------
// IndexExists
//------------------------------------------------------------------------------
function TABSTableData.IndexExists(Cursor: TABSCursor; FieldNamesList, AscDescList, CaseSensitivityList: TStringList): Boolean;
begin
 Lock;
 try
  Result := FIndexManager.IsIndexExists(FieldNamesList, AscDescList, CaseSensitivityList, Cursor.Session.SessionID, Cursor.FieldDefs);
 finally
  Unlock;
 end;
end;// IndexExists


//------------------------------------------------------------------------------
// return true if Unique Constraint Failed
//------------------------------------------------------------------------------
function TABSTableData.IsUniqueConstraintFailed(
                                        SessionID:        TABSSessionID;
                                        IndexID:          TABSObjectID;
                                        NewRecordBuffer:  TABSRecordBuffer;
                                        OldRecordBuffer:  TABSRecordBuffer;
                                        ToInsert:         Boolean;
                                        CurrentRecordID:  TABSRecordID
                                                ): Boolean;
begin
  if (FRecordManager.RecordCount > 0) then
   if (ToInsert) then
     Result := not FIndexManager.OpenIndex(IndexID).CanInsertRecord(SessionID, NewRecordBuffer)
   else
     Result := not FIndexManager.OpenIndex(IndexID).CanUpdateRecord(SessionID, OldRecordBuffer, NewRecordBuffer)
  else
   Result := False;
end;//IsUniqueConstraintFailed


//------------------------------------------------------------------------------
// clear bitmap and set state and indexbased
//------------------------------------------------------------------------------
procedure TABSTableData.InitRecordBitmap(RecordBitmap: TABSRecordBitmap; IndexID: TABSObjectID);
begin
  with RecordBitmap do
    begin
      Clear;
      TableState := RecordManager.TableState;
      IndexBased := (IndexID <> INVALID_OBJECT_ID);
    end;
end;// InitRecordBitmap


//------------------------------------------------------------------------------
// if necessary - build record bitmap for cursor
//------------------------------------------------------------------------------
procedure TABSTableData.RefreshCursorRecordBitmap(Cursor: TABSCursor);
begin
  if (TABSRecordBitmap(Cursor.RecordBitmap).Active) then
    begin
      if (TABSRecordBitmap(Cursor.RecordBitmap).Outdated) then
        BuildCursorRecordBitmap(Cursor);
    end
  else
    begin
      if (Cursor.IsViewRestricted) then
        BuildCursorRecordBitmap(Cursor);
    end;
end;// RefreshCursorRecordBitmap


//------------------------------------------------------------------------------
// build record bitmap for cursor
//------------------------------------------------------------------------------
procedure TABSTableData.BuildCursorRecordBitmap(Cursor: TABSCursor);
var 
    OldPos:         Pointer;
    OldBuffer:      TABSRecordBuffer;
begin
 Lock;
 try
   TABSRecordBitmap(Cursor.RecordBitmap).Clear;
   TABSRecordBitmap(Cursor.RecordBitmap).Size := GetBitmapRecordCount(Cursor.Session.SessionID);
   if (FRecordManager.RecordCount > 0) then
    begin
     OldBuffer := Cursor.CurrentRecordBuffer;
     OldPos := Cursor.SavePosition;
     Cursor.CurrentRecordBuffer := nil;
     Cursor.FirstPosition := True;
     Cursor.LastPosition := False;
     try
      // build cursor bitmap and make it active
      FindRecord(Cursor,nil,nil,True,True,nil,Cursor.RecordBitmap,True);
      // apply distinct
{      if (Cursor.DistinctFieldCount > 0) then
        FIndexManager.OpenIndex(Cursor.IndexID).
            ApplyDistinctToRecordBitmap(Cursor.Session.SessionID,
              TABSRecordBitmap(Cursor.RecordBitmap), Cursor.DistinctFieldCount);}

     finally
      Cursor.CurrentRecordBuffer := OldBuffer;
      Cursor.RestorePosition(OldPos);
      Cursor.FreePosition(OldPos);
     end;
    end;
 finally
  Unlock;
 end;
end; // BuildCursorRecordBitmap


//------------------------------------------------------------------------------
// return filter bitmap rec no by record id
//------------------------------------------------------------------------------
function TABSTableData.GetBitmapRecNoByIndexPosition(IndexID: TABSObjectID; IndexPos: TABSIndexPosition): TABSRecordNo;
var
  MaxEntriesPerPage: Integer;
begin
  MaxEntriesPerPage := IndexManager.OpenIndex(IndexID).GetMaxEntriesPerPage;
  Result := TABSKeyPath(IndexPos).GetBitmapRecNoByIndexPosition(MaxEntriesPerPage);
end;// GetBitmapRecNoByIndexPosition


//------------------------------------------------------------------------------
// return filter bitmap rec no by record id
//------------------------------------------------------------------------------
procedure TABSTableData.GetIndexPositionByBitmapRecNo(IndexID: TABSObjectID; RecordNo: TABSRecordNo; IndexPos: TABSIndexPosition);
var
  MaxEntriesPerPage: Integer;
begin
  MaxEntriesPerPage := IndexManager.OpenIndex(IndexID).GetMaxEntriesPerPage;
  TABSKeyPath(IndexPos).FillItemsByBitmapRecNo(RecordNo, MaxEntriesPerPage);
end;// GetIndexPositionByBitmapRecNo


//------------------------------------------------------------------------------
// return index position by record id
//------------------------------------------------------------------------------
function TABSTableData.GetIndexPositionByRecordID(SessionID: TABSSessionID; IndexID: TABSObjectID; RecordID: TABSRecordID; IndexPos: TABSIndexPosition): Boolean;
var
  RecordBuffer: TABSRecordBuffer;
  NavigationInfo: TABSNavigationInfo;
begin
  RecordBuffer := MemoryManager.AllocMem(RecordManager.RecordBufferSize);
  try
    NavigationInfo.GetRecordMode := grmCurrent;
    NavigationInfo.IndexID := INVALID_OBJECT_ID;
    NavigationInfo.RecordID := RecordID;
    NavigationInfo.RecordBuffer := RecordBuffer;
    NavigationInfo.FirstPosition := False;
    NavigationInfo.LastPosition := False;
    FRecordManager.GetRecordBuffer(SessionID,NavigationInfo);
    Result := (NavigationInfo.GetRecordResult = grrOK);
    if (Result) then
// 6.02 fix: record edited by another user, table indexed and filtered
//      if (not IndexManager.OpenIndex(IndexID).GetIndexPosition(SessionID, RecordID, RecordBuffer, IndexPos)) then
//        raise EABSException.Create(20253, ErrorACannotRetreiveRecord);
      Result := IndexManager.OpenIndex(IndexID).GetIndexPosition(SessionID, RecordID, RecordBuffer, IndexPos);
  finally
    MemoryManager.FreeAndNillMem(RecordBuffer);
  end;
end;// GetIndexPositionByRecordID


//------------------------------------------------------------------------------
// return record id by index position
//------------------------------------------------------------------------------
procedure TABSTableData.GetRecordIDByIndexPosition(SessionID: TABSSessionID; IndexID: TABSObjectID; IndexPos: TABSIndexPosition; var RecordID: TABSRecordID);
begin
  IndexManager.OpenIndex(IndexID).GetRecordIDByIndexPosition(SessionID, IndexPos, RecordID);
end;// GetRecordIDByIndexPosition


//------------------------------------------------------------------------------
// return true if record is in specified range
//------------------------------------------------------------------------------
function TABSTableData.IsRecordInRange(Cursor: TABSCursor): Boolean;
var Res:  Integer;
begin
  Result := True;
  if (Cursor.RangeStartBuffer <> nil) then
    begin
     Res := FIndexManager.OpenIndex(Cursor.IndexID).
              CompareRecordBuffersByIndex(
                Cursor.CurrentRecordBuffer,
                Cursor.RangeStartBuffer,
                Cursor.RangeStartKeyFieldCount);
     if (Cursor.RangeStartExclusive) then
      Result := (Res > 0)
     else
      Result := (Res >= 0);
    end;
  if (Result) then
   if (Cursor.RangeEndBuffer <> nil) then
    begin
     Res := FIndexManager.OpenIndex(Cursor.IndexID).
              CompareRecordBuffersByIndex(
                Cursor.CurrentRecordBuffer,
                Cursor.RangeEndBuffer,
                Cursor.RangeEndKeyFieldCount);
     if (Cursor.RangeEndExclusive) then
      Result := (Res < 0)
     else
      Result := (Res <= 0);
    end;
end; // IsRecordInRange


//------------------------------------------------------------------------------
// return true if record is not equal to the previous record
//------------------------------------------------------------------------------
function TABSTableData.IsRecordInDistinct(Cursor: TABSCursor): Boolean;
var Res:  Integer;
begin
  Res := FIndexManager.OpenIndex(Cursor.IndexID).
          CompareRecordBuffersByIndex(
            Cursor.CurrentRecordBuffer,
            Cursor.DistinctRecordBuffer,
            Cursor.DistinctFieldCount);
  Result := (Res <> 0);
end; // IsRecordInDistinct


//------------------------------------------------------------------------------
// return true if record is visible (with applied filters, ranges, OnFilterRecord)
//------------------------------------------------------------------------------
function TABSTableData.IsRecordVisible(Cursor: TABSCursor): Boolean;
var
  aDataset:         Pointer;
  aRecordBuffer:    TABSRecordBuffer;
  FunctionAddress:  Pointer;

{$ifdef WIN64}
function CallMethod(const Buffer: TABSRecordBuffer; const Dataset: Pointer; const funcAddr:Pointer): Boolean; register;
asm // rcx,rdx,r8
  PUSH   RCX
  PUSH   RDX
  PUSH   R8
  MOV    RAX, funcAddr // r8
  MOV    R8,  Dataset  // rdx
  MOV    RDX, Buffer   // rcx
  //function TABSDataSet.FilterRecord(Buffer: TABSRecordBuffer; Dataset: Pointer): Boolean;
  CALL   RAX                   // Call Function (rcx,rdx)
  MOV    Result, AL                 // Save Result
  POP    R8
  POP    RDX
  POP    RCX
end;
{$endif}

begin
 Result := True;
 if (Result) then
  Result := IsRecordInRange(Cursor);
 if (Result) then
   if (Cursor.FilterExpression <> nil) then
    begin
     TABSExpression(Cursor.FilterExpression).AssignCursorBuffer(Cursor.CurrentRecordBuffer, False);
     Result := TABSExpression(Cursor.FilterExpression).GetResult;
    end;
 if (Result) then
   if (Cursor.SQLFilterExpression <> nil) then
    begin
     TABSExpression(Cursor.SQLFilterExpression).AssignCursorBuffer(Cursor.CurrentRecordBuffer, False);
     Result := TABSExpression(Cursor.SQLFilterExpression).GetResult;
    end;
 if (Result) then
  if (Pointer(Cursor.FilterRecord) <> nil) then
   begin
    aDataset := Cursor.Dataset;
    aRecordBuffer := Cursor.CurrentRecordBuffer;
    // Borland parameters transmitting bug fix
    // Get Function address from Virtual Method Table
    FunctionAddress := Pointer(PNativeUInt(Pointer(NativeUInt(@Cursor.FilterRecord)))^);
{$ifndef WIN64}
    asm
      MOV    ECX, aDataset                // Dataset
      MOV    EDX, aRecordBuffer           // Buffer
      MOV    EAX, FunctionAddress         // Address of Function
      CALL   EAX                          // Call Function
      MOV    Result, AL                   // Save Result
    end;
{$else}
    Result := CallMethod(aRecordBuffer, aDataset, FunctionAddress);
{$endif}
   end;
end; // IsRecordVisible


//------------------------------------------------------------------------------
// return true if conditions are incompatible
//------------------------------------------------------------------------------
procedure TABSTableData.MergeAndCheckSearchConditionsCompatibility(
                                var Condition1:            TABSScanSearchCondition;
                                var Condition2:            TABSScanSearchCondition;
                                out Incompatible:          Boolean;
                                out HaveBeenMerged:        Boolean
                                           );
var ResMin:         Integer;
    IndexID:        TABSObjectID;
    MinFieldCount:  Integer;
    MaxFieldCount:  Integer;
    NewCondition:   TABSScanSearchCondition;

 procedure SetFirstCondition;
 begin
  HaveBeenMerged := True;
  NewCondition.Assign(Condition1);
 end; // SetFirstCondition

 procedure SetSecondCondition;
 begin
  HaveBeenMerged := True;
  NewCondition.Assign(Condition2);
 end; // SetMaxKFCCondition

 procedure SetMinKFCCondition;
 begin
  HaveBeenMerged := True;
  NewCondition.KeyFieldCount := MinFieldCount;
  if (Condition1.KeyFieldCount = MinFieldCount) then
    NewCondition.Assign(Condition1)
  else
    NewCondition.Assign(Condition2);
 end; // SetMinKFCCondition

 procedure SetMaxKFCCondition;
 begin
  HaveBeenMerged := True;
  NewCondition.KeyFieldCount := MaxFieldCount;
  if (Condition1.KeyFieldCount = MaxFieldCount) then
    NewCondition.Assign(Condition1)
  else
    NewCondition.Assign(Condition2);
 end; // SetMaxKFCCondition

begin
 IndexID := Condition1.IndexID;
 MinFieldCount := Min(Condition1.KeyFieldCount,Condition2.KeyFieldCount);
 MaxFieldCount := Max(Condition1.KeyFieldCount,Condition2.KeyFieldCount);
 ResMin := FIndexManager.OpenIndex(IndexID).CompareRecordBuffersByIndex(
          Condition1.KeyRecordBuffer,Condition2.KeyRecordBuffer,MinFieldCount);
 Incompatible := False;
 HaveBeenMerged := False;
 NewCondition := TABSScanSearchCondition.Create(False);
 try
 NewCondition.IndexID := IndexID;
 NewCondition.Expression := nil;
 if (Condition1.Condition <> scNone) and (Condition2.Condition <> scNone) then
   case (Condition1.Condition) of
     scEqual:
       case (Condition2.Condition) of
         scEqual:
          begin
            if ((ResMin > 0) or (ResMin < 0)) then
             Incompatible := True
            else
             SetMaxKFCCondition;
          end;
         scGreater:
          begin
            if ((ResMin = 0) or (ResMin < 0)) then
             Incompatible := True
            else
             SetFirstCondition;
          end;
         scLower:
          begin
            if ((ResMin = 0) or (ResMin > 0)) then
             Incompatible := True
            else
             SetFirstCondition;
          end;
         scGreaterEqual:
          begin
            if (ResMin < 0) then
             Incompatible := True
            else
            if ((ResMin > 0) or
                ((ResMin = 0) and (Condition1.KeyFieldCount = MaxFieldCount))) then
             SetFirstCondition; // otherwise not merge
          end;
         scLowerEqual:
          begin
            if (ResMin > 0) then
             Incompatible := True
            else
            if ((ResMin < 0) or
                ((ResMin = 0) and (Condition1.KeyFieldCount = MaxFieldCount))) then
             SetFirstCondition; // otherwise not merge
          end;
       end; // Condition2
     scGreater:
       case (Condition2.Condition) of
         scEqual:
          begin
            if ((ResMin = 0) or (ResMin > 0)) then
             Incompatible := True
            else
             SetSecondCondition;
          end;
         scGreater:
          begin
            if (ResMin > 0) then
             SetFirstCondition
            else
            if (ResMin < 0) then
             SetSecondCondition
            else
             SetMinKFCCondition;
          end;
         scLower:
          begin
            if ((ResMin > 0) or (ResMin = 0)) then
             Incompatible := True; // otherwise not merge
          end;
         scGreaterEqual:
          begin
            if ((ResMin = 0) or (ResMin > 0)) then
             SetFirstCondition
            else
            if (ResMin < 0) then
             SetSecondCondition;
          end;
         scLowerEqual:
          begin
            if ((ResMin > 0) or
                ((ResMin = 0) and (Condition1.KeyFieldCount = MinFieldCount))) then
             Incompatible := True; // otherwise not merge
          end;
       end; // Condition2
     scLower:
       case (Condition2.Condition) of
         scEqual:
          begin
            if ((ResMin = 0) or (ResMin < 0)) then
             Incompatible := True
            else
             SetSecondCondition;
          end;
         scGreater:
          begin
            if ((ResMin < 0) or (ResMin = 0)) then
             Incompatible := True; // otherwise not merge
          end;
         scLower:
          begin
            if (ResMin < 0) then
             SetFirstCondition
            else
            if (ResMin > 0) then
             SetSecondCondition
            else
             SetMinKFCCondition;
          end;
         scGreaterEqual:
          begin
            if ((ResMin < 0) or
                ((ResMin = 0) and (Condition1.KeyFieldCount = MinFieldCount))) then
             Incompatible := True; // otherwise not merge
          end;
         scLowerEqual:
          begin
            if ((ResMin < 0) or (ResMin = 0)) then
             SetFirstCondition
            else
            if (ResMin > 0) then
             SetSecondCondition;
          end;
       end; // Condition2
     scGreaterEqual:
       case (Condition2.Condition) of
         scEqual:
          begin
            if (ResMin > 0) then
             Incompatible := True
            else
            if ((ResMin < 0) or
                ((ResMin = 0) and
                 (Condition2.KeyFieldCount = MaxFieldCount))) then
             SetSecondCondition;
          end;
         scGreater:
          begin
            if (ResMin > 0) then
             SetFirstCondition
            else
             SetSecondCondition;
          end;
         scLower:
          begin
            if ((ResMin > 0) or
                ((ResMin = 0) and (Condition2.KeyFieldCount = MinFieldCount))) then
             Incompatible := True; // otherwise not merge
          end;
         scGreaterEqual:
          begin
            if (ResMin > 0) then
             SetFirstCondition
            else
            if (ResMin < 0) then
             SetSecondCondition
            else
             SetMaxKFCCondition;
          end;
         scLowerEqual:
          begin
            if (ResMin > 0) then
             Incompatible := True // otherwise not merge
            else
             if ((ResMin = 0) and (MinFieldCount = MaxFieldCount)) then
              begin
               SetMaxKFCCondition;
               NewCondition.Condition := scEqual;
              end;
          end;
       end; // Condition2
     scLowerEqual:
       case (Condition2.Condition) of
         scEqual:
          begin
            if (ResMin < 0) then
             Incompatible := True
            else
            if ((ResMin > 0) or
                ((ResMin = 0) and (Condition2.KeyFieldCount = MaxFieldCount)))
               then
             SetSecondCondition; // otherwise not merge
          end;
         scGreater:
          begin
            if ((ResMin < 0) or
                ((ResMin = 0) and (Condition2.KeyFieldCount = MinFieldCount))) then
             Incompatible := True; // otherwise not merge
          end;
         scLower:
          begin
            if (ResMin < 0) then
             SetFirstCondition
            else
            if (ResMin > 0) then
             SetSecondCondition;
          end;
         scGreaterEqual:
          begin
            if (ResMin < 0) then
             Incompatible := True // otherwise not merge
            else
             if ((ResMin = 0) and (MinFieldCount = MaxFieldCount)) then
              begin
               SetMaxKFCCondition;
               NewCondition.Condition := scEqual;
              end;
          end;
         scLowerEqual:
          begin
            if (ResMin < 0) then
             SetFirstCondition
            else
            if (ResMin > 0) then
             SetSecondCondition
            else
             SetMaxKFCCondition;
          end;
       end; // Condition2
   end; // Condition1
  if (HaveBeenMerged) then
   begin
      Condition1.Assign(NewCondition);
   end;
 finally
  NewCondition.Free;
 end;
end; // MergeAndCheckSearchConditionsCompatibility


//------------------------------------------------------------------------------
// returns true if conditions are equal
//------------------------------------------------------------------------------
function TABSTableData.IsEqualConditions(
                            var Condition1:            TABSScanSearchCondition;
                            var Condition2:            TABSScanSearchCondition
                                       ): Boolean;
begin
 Result := False;
 if (Condition1.IndexId = Condition2.IndexId) then
  if ((Condition1.Condition = Condition2.Condition) and
      (Condition1.Condition <> scNone)) then
   if (Condition1.KeyFieldCount = Condition2.KeyFieldCount) then
    begin
     Result := (FIndexManager.OpenIndex(Condition1.IndexId).
                  CompareRecordBuffersByIndex(
                            Condition1.KeyRecordBuffer,
                            Condition2.KeyRecordBuffer,
                            Condition1.KeyFieldCount) = 0);
    end;
end; // IsEqualConditions


//------------------------------------------------------------------------------
// removes duplicate conditions
//------------------------------------------------------------------------------
procedure TABSTableData.RemoveDuplicateConditions(Conditions: TABSScanSearchConditionArray);
var i,j:        Integer;
    ToRemove:   Boolean;
begin
 i := 0;
 while (i < Conditions.Count) and (Conditions.Count > 1) do
  begin
   ToRemove := False;
   for j := 0 to Conditions.Count - 1 do
    if (j <> i) then
     if (IsEqualConditions(Conditions.Items[i],Conditions.Items[j])) then
      ToRemove := True;
   if (ToRemove) then
    Conditions.Delete(i)
   else
    Inc(i);
  end;
end; // RemoveDuplicateConditions


//------------------------------------------------------------------------------
// sorts Conditions array and removes unnecessary conditions
//------------------------------------------------------------------------------
procedure TABSTableData.OptimizeSearchConditions(
                            var Conditions:               TABSScanSearchConditionArray;
                            out NonCompatibleConditions:  Boolean
                                 );
var IndexIDs:       TABSIntegerArray;
    i,j,k:          Integer;
    IndexID:        TABSObjectID;
    NewConditions:  TABSScanSearchConditionArray;
    HaveBeenMerged: Boolean;
    DoAppend:       Boolean;

// conditions like "f1=const1 and (f1,f2)>(const1,const2)" cannot be merged
function CanBeCorrectlyMerged(Condition1,Condition2: TABSScanSearchCondition): Boolean;
var
  EqualConditionFieldCount: Integer;
  StrictConditionFieldCount: Integer;
begin
  Result := True;
  if (Condition1.Condition = scEqual) and (Condition2.Condition in [scGreater, scLower]) or
     (Condition2.Condition = scEqual) and (Condition1.Condition in [scGreater, scLower]) then
       begin
         if (Condition1.Condition = scEqual) then
           begin
             EqualConditionFieldCount := Condition1.KeyFieldCount;
             StrictConditionFieldCount := Condition2.KeyFieldCount;
           end
         else
           begin
             EqualConditionFieldCount := Condition2.KeyFieldCount;
             StrictConditionFieldCount := Condition1.KeyFieldCount;
           end;
         Result := (EqualConditionFieldCount >= StrictConditionFieldCount);
       end;
end;

begin
 IndexIDs := TABSIntegerArray.Create(0,Conditions.Count,Conditions.Count);
 NewConditions := TABSScanSearchConditionArray.Create;
 NonCompatibleConditions := False;
 try
  for i := 0 to Conditions.Count - 1 do
   if (Conditions.Items[i].Expression = nil) then
    begin
     IndexID := Conditions.Items[i].IndexID;
     if (not IndexIDs.IsValueExists(IndexID)) then
       IndexIDs.Append(IndexID);
    end;
  for i := 0 to IndexIDs.ItemCount-1 do
   if (NonCompatibleConditions) then
    break
   else
     begin
      IndexID := IndexIDs.Items[i];
      for k := 0 to Conditions.Count-1 do
       if (NonCompatibleConditions) then
        break
       else
         if (Conditions.Items[k].Expression = nil) then
           if (Conditions.Items[k].IndexID = IndexID) then
             begin
              DoAppend := True;
               for j := 0 to NewConditions.Count-1 do
                if (NewConditions.Items[j].IndexID = IndexID) then
                  if (CanBeCorrectlyMerged(NewConditions.Items[j], Conditions.Items[k])) then
                    begin
                     MergeAndCheckSearchConditionsCompatibility(
                       NewConditions.Items[j],
                       Conditions.Items[k],
                       NonCompatibleConditions,
                       HaveBeenMerged
                                                               );
                     if (NonCompatibleConditions) then
                      break;
                     if (HaveBeenMerged) then
                      DoAppend := False;
                    end; // j
              if (DoAppend) then
                NewConditions.AddCondition(Conditions.Items[k]);
             end; // k condition with current index
     end; // i
  if (not NonCompatibleConditions) then
    RemoveDuplicateConditions(NewConditions);

  if (not NonCompatibleConditions) then
    for k := 0 to Conditions.Count-1 do
     if (Conditions.Items[k].Expression <> nil) then
      NewConditions.AddCondition(Conditions.Items[k]);
 finally
  IndexIDs.Free;
  if (NonCompatibleConditions) then
   NewConditions.Free
  else
   begin
    Conditions.Free;
    Conditions := NewConditions;
   end;
 end;
end; // OptimizeSearchConditions


//------------------------------------------------------------------------------
// prepare conditions array
//------------------------------------------------------------------------------
procedure TABSTableData.PrepareConditions(
                           Cursor:            TABSCursor;
                           Conditions:        TABSScanSearchConditionArray;
                           KeyCondition:      TABSScanSearchCondition;
                           SearchExpression:  TABSExpression;
                           GoForward:         Boolean
                           );
var
  RangeCondition: TABSScanSearchCondition;
  DistinctCondition:  TABSScanSearchCondition;
begin
  RangeCondition := TABSScanSearchCondition.Create(True);
  DistinctCondition := TABSScanSearchCondition.Create(True);
  try
    if (KeyCondition <> nil) then
      begin
        if (KeyCondition.IndexID = INVALID_OBJECT_ID) then
          raise EABSException.Create(20258,ErrorANoKeyIndex);
        Conditions.AddCondition(KeyCondition);
      end;
    if (SearchExpression <> nil) then
       Conditions.AddExpression(SearchExpression);
    if (Cursor.FilterExpression <> nil) then
      Conditions.AddExpression(Cursor.FilterExpression);
    if (Cursor.SQLFilterExpression <> nil) then
      Conditions.AddExpression(Cursor.SQLFilterExpression);
    if (Cursor.IsRangeApplied) then
     begin
       if (Cursor.RangeStartBuffer <> nil) then
        begin
          RangeCondition.KeyRecordBuffer := Cursor.RangeStartBuffer;
          RangeCondition.KeyRecordBufferSize := Cursor.KeyBufferSize;
          RangeCondition.KeyFieldCount := Cursor.RangeStartKeyFieldCount;
          if (Cursor.RangeStartExclusive) then
            RangeCondition.Condition := scGreater
          else
            RangeCondition.Condition := scGreaterEqual;
          RangeCondition.IndexID := Cursor.IndexID;
          RangeCondition.Expression := nil;
          Conditions.AddCondition(RangeCondition);
        end;
       if (Cursor.RangeEndBuffer <> nil) then
        begin
          RangeCondition.KeyRecordBuffer := Cursor.RangeEndBuffer;
          RangeCondition.KeyRecordBufferSize := Cursor.KeyBufferSize;
          RangeCondition.KeyFieldCount := Cursor.RangeEndKeyFieldCount;
          if (Cursor.RangeEndExclusive) then
            RangeCondition.Condition := scLower
          else
            RangeCondition.Condition := scLowerEqual;
          RangeCondition.IndexID := Cursor.IndexID;
          RangeCondition.Expression := nil;
          Conditions.AddCondition(RangeCondition);
        end;
     end;
    if ((Cursor.IsDistinctApplied) and (Cursor.DistinctRecordBuffer <> nil) and
        ((not Cursor.FirstPosition) and (not Cursor.LastPosition))) then
     begin
      if (Cursor.DistinctRecordBuffer <> nil) then
        DistinctCondition.KeyRecordBuffer := Cursor.DistinctRecordBuffer
      else
        begin
          DistinctCondition.KeyRecordBuffer := Cursor.AllocateRecordBuffer;
          DistinctCondition.FExternalKeyRecordBuffer := false;
        end;
      DistinctCondition.KeyRecordBufferSize := Cursor.RecordBufferSize;
      DistinctCondition.KeyFieldCount := Cursor.DistinctFieldCount;
      if (GoForward) then
        DistinctCondition.Condition := scGreater
      else
        DistinctCondition.Condition := scLower;
      DistinctCondition.IndexID := Cursor.IndexID;
      DistinctCondition.Expression := nil;
      Conditions.AddCondition(DistinctCondition);
     end;
  finally
    RangeCondition.Free;
    DistinctCondition.Free;
  end;
end;// PrepareConditions


//------------------------------------------------------------------------------
// try to find the best scan condition with min scan cost
//------------------------------------------------------------------------------
function TABSTableData.ChooseScanConditionsWithMinScanCost(
                       SessionID:              TABSSessionID;
                       Conditions:             TABSScanSearchConditionArray;
                       CurrentIndexID:         TABSObjectID;
                       CurrentRecordID:        TABSRecordID;
                       FilterRecordPtr:        Pointer;
                       var ScanConditionNo:    Integer;
                       var ScanEndConditionNo: Integer
                              ): Boolean;
var
  i:                        Integer;
  StartConditionNo:         Integer;
  EndConditionNo:           Integer;
  MinScanIndexCost:         Double;
  ScanIndexCost:            Double;
  ScanRecordsCost:          Double;



  // cost of full records scan
  function GetUnconditionalScanCost: Double;
  begin
    if (CurrentIndexID <> INVALID_OBJECT_ID) then
      begin
        Result := (FRecordManager.RecordCount - FRecordManager.GetApproximateRecNo(SessionID, CurrentRecordID));
      end
    else
      Result :=
       (FRecordManager.RecordCount - FRecordManager.GetApproximateRecNo(SessionID, CurrentRecordID))*
                   (FFieldManager.FieldDefs.GetDiskRecordBufferSize / FPageManager.PageDataSize);

  end;

  // cost = NumberOfPagesToRead
  // read page time is assumed to be the largest time
  function GetIndexScanCost(ConditionNo1, ConditionNo2: Integer): Double;
  var
    ScanIndexRecCount:        TABSRecordNo;
    Index:                    TABSIndex;
    KeyEntrySize:             Integer;
  begin

    Index := FIndexManager.OpenIndex(Conditions.Items[ConditionNo1].IndexID);
    if (Index = nil) then
      raise EABSException.Create(20058, ErrorANilPointer);

    ScanIndexRecCount := Index.GetApproxRangeRecordCount(SessionID, FRecordManager.RecordCount,
                Conditions.Items[ConditionNo1], Conditions.Items[ConditionNo2]);

    KeyEntrySize := TABSBTreeRecordIndex(Index).KeyRef.ShortKeySize+TABSBTreeRecordIndex(Index).KeyRef.ReferenceSize;
    ScanIndexCost := ScanIndexRecCount * KeyEntrySize / FPageManager.PageDataSize;

    Conditions.ExtractChosenConditionsFromExpressions(ConditionNo1, ConditionNo2);
    try
      // read records cost
      if (NeedToCheckOtherConditions(Conditions, ConditionNo1, ConditionNo2, FilterRecordPtr) or
          ((CurrentIndexID <> INVALID_OBJECT_ID) and (CurrentIndexID <> Conditions.Items[ConditionNo1].IndexID))) then
        // cost of getting record buffers
        ScanIndexCost := ScanIndexCost + ScanIndexRecCount * (FFieldManager.FieldDefs.GetDiskRecordBufferSize / FPageManager.PageDataSize);
    finally
      Conditions.ReturnExtractedConditionsToExpressions(ConditionNo1, ConditionNo2);
    end;

    // find position in another index cost
    if (CurrentIndexID <> INVALID_OBJECT_ID) and (CurrentIndexID <> Conditions.Items[ConditionNo1].IndexID) then
      // + cost of scan in current-index
      ScanIndexCost := ScanIndexCost + ScanIndexRecCount * KeyEntrySize / FPageManager.PageDataSize;
    Result := ScanIndexCost;
  end;




begin
  Result := False;
  // scan without condition
  ScanConditionNo := INVALID_ID4;
  MinScanIndexCost := FRecordManager.RecordCount;
  // find cheapest index scan
  i := 0;
  while (i < Conditions.Count) do
   begin
     if (Conditions.Items[i].Expression = nil) then
      begin
       StartConditionNo := i;
       if (i < Conditions.Count-1) then
        if (Conditions.Items[i].IndexID = Conditions.Items[i+1].IndexID) then
         Inc(i);
       EndConditionNo := i;
       ScanIndexCost := GetIndexScanCost(StartConditionNo, EndConditionNo);
       if (ScanIndexCost < MinScanIndexCost) then
        begin
          ScanConditionNo := StartConditionNo;
          if (StartConditionNo <> EndConditionNo) then
            ScanEndConditionNo := EndConditionNo
          else
            ScanEndConditionNo := INVALID_ID4;
          MinScanIndexCost := ScanIndexCost;
        end;
      end;
     Inc(i);
   end;

  // compare best conditional scan with with non-conditional scan
  // using cost-based method
  if ((ScanConditionNo <> INVALID_OBJECT_ID) and (FPageManager.PageDataSize > 0)) then
    begin
      //--- scan records cost
      ScanRecordsCost := GetUnconditionalScanCost;
      // compare costs
      if (ScanRecordsCost < MinScanIndexCost) then
        begin
          ScanConditionNo := INVALID_OBJECT_ID;
          ScanEndConditionNo := INVALID_OBJECT_ID;
        end;
    end;
end;// ChooseScanConditionsWithMinScanCost


//------------------------------------------------------------------------------
// if index is defined as unique or contain unique field
//------------------------------------------------------------------------------
function TABSTableData.IsIndexUnique(IndexID: TABSObjectID): Boolean;
var
  IndexDef: TABSIndexDef;
  FieldDef: TABSFieldDef;
begin
  IndexDef := TABSIndexDef(FIndexManager.IndexDefs.GetDefByObjectId(IndexID));
  Result := IndexDef.Unique or IndexDef.Primary;
  if (not Result) then
    begin
      FieldDef := FFieldManager.FieldDefs.GetFieldDefByName(IndexDef.Columns[0].FieldName);
      if (IsAutoincFieldType(FieldDef.AdvancedFieldType)) then
        Result := True;
    end;
end;// IsIndexUnique


//------------------------------------------------------------------------------
// try to find the best scan condition using heuristics
//------------------------------------------------------------------------------
function TABSTableData.ChooseScanConditionsByHeuristsics(
                       Conditions:             TABSScanSearchConditionArray;
                       CurrentIndexID:         TABSObjectID;
                       var ScanConditionNo:    Integer;
                       var ScanEndConditionNo: Integer
                              ): Boolean;
var
  i:       Integer;
  Found:   Boolean;
  FoundNo: Integer;
begin
  FoundNo := INVALID_ID4;
  ScanConditionNo := INVALID_ID4;
  ScanEndConditionNo := INVALID_ID4;
  Result := False;
  if (Conditions.Count > 0) then
   begin
     // any condition '=' for unique index?
     for i := 0 to Conditions.Count-1 do
       if (Conditions.Items[i].Expression = nil) and
          (Conditions.Items[i].Condition = scEqual) and
          (IsIndexUnique(Conditions.Items[i].IndexID) and
          (FIndexManager.OpenIndex(Conditions.Items[i].IndexID).FIndexDef.ColumnCount = 1)) then
        begin
          ScanConditionNo := i;
          Result := True;
          break;
        end;
     // check for single condition with current index
     if (not Result) then
      begin
        Found := False;
        for i := 0 to Conditions.Count-1 do
         if (Conditions.Items[i].Expression = nil) then
          begin
           if (Conditions.Items[i].IndexID = CurrentIndexID) then
            begin
              if (not Found) then
               begin
                 Found := True;
                 FoundNo := i;
               end
              else
               begin
                 Found := False;
                 break;
               end;
            end
           else
             begin
               Found := False;
               break;
             end;
          end;
        if (Found) then
         begin
          ScanConditionNo := FoundNo;
          Result := True;
         end;
      end;
   end;
end;// ChooseScanConditionsByHeuristsics


//------------------------------------------------------------------------------
// try to find the best scan condition
//------------------------------------------------------------------------------
procedure TABSTableData.ChooseScanConditions(
                           SessionID:              TABSSessionID;
                           Conditions:             TABSScanSearchConditionArray;
                           CurrentIndexID:         TABSObjectID;
                           CurrentRecordID:        TABSRecordID;
                           GoForward:              Boolean;
                           FilterRecordPtr:        Pointer;
                           var ScanConditionNo:    Integer;
                           var ScanEndConditionNo: Integer
                                            );
var
  StartConditionNo:         Integer;
  EndConditionNo:           Integer;
  Index:                    TABSIndex;
begin
  if (not ChooseScanConditionsByHeuristsics(
                                            Conditions,
                                            CurrentIndexID,
                                            ScanConditionNo,
                                            ScanEndConditionNo
                                           )) then
    begin
      ChooseScanConditionsWithMinScanCost(
                                            SessionID,
                                            Conditions,
                                            CurrentIndexID,
                                            CurrentRecordID,
                                            FilterRecordPtr,
                                            ScanConditionNo,
                                            ScanEndConditionNo
                                                 );
      // need to swap ScanEnd and Scan conditions?
      if (ScanEndConditionNo <> INVALID_ID4) then
       begin
        Index := FIndexManager.OpenIndex(Conditions.Items[ScanConditionNo].IndexID);
        if (Index = nil) then
         raise EABSException.Create(20273, ErrorANilPointer);
         if (Index.CompareConditions(
                       Conditions.Items[ScanConditionNo],
                       Conditions.Items[ScanEndConditionNo]) < 0) then
          begin
            StartConditionNo := ScanConditionNo;
            EndConditionNo := ScanEndConditionNo;
          end
         else
          begin
            StartConditionNo := ScanEndConditionNo;
            EndConditionNo := ScanConditionNo;
          end;
         if (GoForward) then
          begin
            ScanConditionNo := StartConditionNo;
            ScanEndConditionNo := EndConditionNo;
          end
         else
          begin
            ScanConditionNo := EndConditionNo;
            ScanEndConditionNo := StartConditionNo;
          end;
       end;
   end;
end;// ChooseScanConditions


//------------------------------------------------------------------------------
// scan records bitmap to find record
//------------------------------------------------------------------------------
function TABSTableData.FindRecordByBitmapScan(SessionID: TABSSessionID;
                                Restart, GoForward: Boolean;
                                CurrentRecordID: TABSRecordID;
                                var ResultRecordID: TABSRecordID;
                                RecordBitmap: TABSRecordBitmap;
                                IndexID: TABSObjectID): Boolean;
var
  CurrentIndexPos, FoundRecordIndexPos: TABSKeyPath;
begin
  {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.FindRecordByBitmapScan start');{$ENDIF}
  try
    if (RecordBitmap.IndexBased) then
      begin
       {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('index based');{$ENDIF}
        FoundRecordIndexPos := TABSKeyPath.Create;
        CurrentIndexPos := TABSKeyPath.Create;
        try
          if (not Restart) then
            Result := GetIndexPositionByRecordID(SessionID, IndexID, CurrentRecordID, CurrentIndexPos)
          else
            Result := True;
         {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('after possible GetIndexPositionByRecordID');{$ENDIF}
          if (Result) then
            Result := RecordBitmap.FindRecord(Restart, GoForward, CurrentIndexPos, FoundRecordIndexPos, IndexID);
          if (Result) then
            GetRecordIDByIndexPosition(SessionID, IndexID, FoundRecordIndexPos, ResultRecordID);
        finally
          FoundRecordIndexPos.Free;
          CurrentIndexPos.Free;
        end;
      end
    else
      Result := RecordBitmap.FindRecord(Restart, GoForward, CurrentRecordID,ResultRecordID);
  finally
    {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.FindRecordByBitmapScan end');{$ENDIF}
  end;
end;// FindRecordByBitmapScan


//------------------------------------------------------------------------------
// return true if record was found and is visible by cursor
// prepares params for FindRecordByScan and calls it
//------------------------------------------------------------------------------
function TABSTableData.FindRecord(
                    Cursor:           TABSCursor;
                    SearchExpression: TABSExpression; // locate
                    KeyCondition:     TABSScanSearchCondition; // find key
                    Restart:          Boolean;
                    GoForward:        Boolean;
                    // if record was found then return record id
                    ResultRecordID:   PABSRecordID;
                    RecordBitmap:     TABSRecordBitmap;
                    // if specified - fill the whole record bitmap
                    ForceFillWholeBitmap: Boolean = False
                   ): Boolean;
var
  Conditions:                TABSScanSearchConditionArray;
  ScanConditionNo:           Integer;
  ScanEndConditionNo:        Integer;
  bNonCompatibleConditions:  Boolean;
  DummyRecordID:             TABSRecordID;
begin
  {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.FindRecord start');{$ENDIF}
  Lock;
  try
    if (FRecordManager.RecordCount = 0) then
      Result := False
    else
      begin
       {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('RecordCount > 0');{$ENDIF}
        // easier code
        if (ResultRecordID = nil) then
          ResultRecordID := @DummyRecordID;
        // reset records bitmap?
        if (RecordBitmap <> nil) then
          if (ForceFillWholeBitmap or TABSRecordBitmap(RecordBitmap).Outdated{ or
              Cursor.IsDistinctApplied}) then
            InitRecordBitmap(Cursor.RecordBitmap, Cursor.IndexID);

       {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('After possible init');{$ENDIF}
        // find in bitmap?
        if (RecordBitmap <> nil) and (TABSRecordBitmap(Cursor.RecordBitmap).Active) then
          Result := FindRecordByBitmapScan(
                  Cursor.Session.SessionID,Restart, GoForward,
                  Cursor.CurrentRecordID, ResultRecordID^, Cursor.RecordBitmap,
                  Cursor.IndexID)
        else
          begin
            // find by scan
            Conditions := TABSScanSearchConditionArray.Create;
           {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('Conditions.Create');{$ENDIF}
            PrepareConditions(Cursor, Conditions, KeyCondition, SearchExpression, GoForward);
           {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('Conditions prepared');{$ENDIF}
            try
              Conditions.CreateIndexScanConditionsFromExpressions(FIndexManager.IndexDefs, Cursor.Session.SessionID);
              OptimizeSearchConditions(Conditions, bNonCompatibleConditions);
              if (bNonCompatibleConditions) then
               Result := False
              else
               begin
                 ChooseScanConditions(
                                       Cursor.Session.SessionID,
                                       Conditions,
                                       Cursor.IndexID,
                                       Cursor.CurrentRecordID,
                                       GoForward,
                                       Cursor.FilterRecord,
                                       ScanConditionNo,
                                       ScanEndConditionNo
                                     );
                 Conditions.RemoveUnusedConditionsCreatedFromExpressions(ScanConditionNo,ScanEndConditionNo);
                 Conditions.ExtractChosenConditionsFromExpressions(ScanConditionNo,ScanEndConditionNo);
                 try
                    Result := FindRecordByScan(
                                             Cursor.Session.SessionID,
                                             Conditions,
                                             ScanConditionNo,
                                             ScanEndConditionNo,
                                             Cursor.IndexID,
                                             Cursor.FilterRecord,
                                             Cursor.Dataset,
                                             Cursor.CurrentRecordID,
                                             Restart,
                                             GoForward,
                                             ResultRecordID,
                                             RecordBitmap,
                                             ForceFillWholeBitmap);
                 finally
                   Conditions.ReturnExtractedConditionsToExpressions(ScanConditionNo,ScanEndConditionNo);
                 end;
                 if (ForceFillWholeBitmap) then
                   TABSRecordBitmap(Cursor.RecordBitmap).Active := True;
                   
                 // if bitmap was filled - apply distinct
                 if (Restart and TABSRecordBitmap(Cursor.RecordBitmap).Active) then
                   if (Cursor.IsDistinctApplied) then
                     FIndexManager.OpenIndex(Cursor.IndexID).
                        ApplyDistinctToRecordBitmap(Cursor.Session.SessionID,
                                    TABSRecordBitmap(Cursor.RecordBitmap),
                                    Cursor.DistinctFieldCount);
               end;
            finally
              Conditions.Free;
            end;
          end;
      end;
  finally
   {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.FindRecord end');{$ENDIF}
    Unlock;
  end;
end; // FindRecord


//------------------------------------------------------------------------------
// return true if record match specified condition
//------------------------------------------------------------------------------
function TABSTableData.IsRecordMatchCondition(
                                var Condition:        TABSScanSearchCondition;
                                RecordBuffer:         TABSRecordBuffer
                               ): Boolean;
var
    Res:            Integer;
begin
 Res := FIndexManager.OpenIndex(Condition.IndexID).
        CompareRecordBuffersByIndex(
        RecordBuffer,
        Condition.KeyRecordBuffer,
        Condition.KeyFieldCount);
 case Condition.Condition of
  scEqual: Result := (Res = 0);
  scGreater: Result := (Res > 0);
  scLower: Result := (Res < 0);
  scGreaterEqual: Result := (Res >= 0);
  scLowerEqual: Result := (Res <= 0)
 else
  Result := False
 end;
end; // IsRecordMatchCondition


//------------------------------------------------------------------------------
// need to check conditions or index conditions are enough
//------------------------------------------------------------------------------
function TABSTableData.NeedToCheckOtherConditions(
                              Conditions:           TABSScanSearchConditionArray;
                              ExcludedConditionNo:  Integer; // INVALID_ID4 if not specified
                              ExcludedConditionNo2: Integer; // INVALID_ID4 if not specified
                              FilterRecordPtr:      Pointer): Boolean;
var
  ActualConditionCount, i: Integer;
begin
  if (FilterRecordPtr <> nil) then
    Result := True
  else
    begin
      ActualConditionCount := 0;
      for i := 0 to Conditions.Count - 1 do
        if ((i <> ExcludedConditionNo) and (i <> ExcludedConditionNo2)) then
          if (not Conditions.Items[i].CreatedFromExpression) then
            if (Conditions.Items[i].Expression = nil) then
              Inc(ActualConditionCount)
            else
              if (not TABSExpression(Conditions.Items[i].Expression).IsEmpty) then
                Inc(ActualConditionCount);
      Result := (ActualConditionCount > 0);
    end;
end;// NeedToCheckOtherConditions


//------------------------------------------------------------------------------
// return true if record match specified conditions
//------------------------------------------------------------------------------
function TABSTableData.IsRecordMatchConditions(
                          Conditions:           TABSScanSearchConditionArray;
                          ExcludedConditionNo:  Integer; // INVALID_ID4 if not specified
                          ExcludedConditionNo2: Integer; // INVALID_ID4 if not specified
                          FilterRecordPtr:      Pointer;
                          Dataset:              Pointer;
                          RecordBuffer:         TABSRecordBuffer;
                          RecordID:             TABSRecordID
                                ): Boolean;
var
  FunctionAddress:  Pointer;
  i:                Integer;
  buf:              TAbsPByte;

{$ifdef WIN64}

function CallMethod2(Buffer: TABSRecordBuffer; Dataset: Pointer; funcAddr:Pointer): Boolean; register;
asm // rdx,r8,r9
  PUSH   RCX
  PUSH   RDX
  PUSH   R8
  PUSH   R9
  //function TABSDataSet.FilterRecord(Buffer: TABSRecordBuffer; Dataset: Pointer): Boolean;
  CALL   R9                   // Call Function (rcx,rdx)
  MOV    Result, AL                 // Save Result
  POP    R9
  POP    R8
  POP    RDX
  POP    RCX
end;
{$endif}



begin
  Result := True;
  if (Result) then
   begin
    for i := 0 to Conditions.Count - 1 do
     if (i <> ExcludedConditionNo) and (i <> ExcludedConditionNo2) then
      begin
       if (Conditions.Items[i].Expression <> nil) then
        begin
         if (not TABSExpression(Conditions.Items[i].Expression).IsEmpty) then
          Result := TABSExpression(Conditions.Items[i].Expression).GetResult;
        end // expression
       else
        begin
         Result := IsRecordMatchCondition(Conditions.Items[i],RecordBuffer);
        end; // key condition
       if (not Result) then
        break;
      end;
   end;
 if (Result) then
  if (Pointer(FilterRecordPtr) <> nil) then
   begin
    // set bookmark to allow BLOB fields access in OnFilterRecord
    buf := TABSDataset(Dataset).Handle.AllocateRecordBuffer;
    try
      Move(RecordBuffer^, buf^, FRecordManager.RecordBufferSize);
      Move(RecordID, TAbsPByte(buf + TABSDataset(Dataset).Handle.BookmarkOffset)^,
           sizeof(TABSRecordID));

      // Borland parameters transmitting bug fix
      // Get Function address from Virtual Method Table
      //FunctionAddress := Pointer(PCardinal(Pointer(Cardinal(@FilterRecordPtr)))^);
      FunctionAddress := Pointer(PNativeUInt(Pointer(NativeUInt(@FilterRecordPtr)))^);


{$ifndef WIN64}
      asm
        MOV    ECX, Dataset                // Dataset
        MOV    EDX, buf                    // Buffer
        MOV    EAX, FunctionAddress        // Address of Function
        CALL   EAX                         // Call Function
        MOV    Result, AL                  // Save Result
      end;
{$else}
      Result := CallMethod2(buf, Dataset, FunctionAddress);
{$endif}


    finally
      ClearBlobsCacheInRecordBuffer(buf);
      TABSDataset(Dataset).Handle.FreeRecordBuffer(buf);
    end;
   end;
end; // IsRecordMatchConditions


//------------------------------------------------------------------------------
// scan all records and check conditions
//------------------------------------------------------------------------------
function TABSTableData.FindRecordByScanWithoutCondition(
                          SessionID:          TABSSessionID;
                          Conditions:         TABSScanSearchConditionArray;
                          ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                          ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                          CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                          FilterRecordPtr:    Pointer;
                          Dataset:            Pointer;
                          CurrentRecordID:    TABSRecordID;
                          // find first or last record in order specified by index settings
                          Restart:            Boolean;
                          // go from first record to last or vice versa
                          GoForward:          Boolean;
                          // if record was found then return record id
                          ResultRecordID:     PABSRecordID;
                          // if specified - fill record bitmap
                          RecordBitmap:       TABSRecordBitmap;
                          ForceFillWholeBitmap: Boolean
                         ): Boolean;
var
  NavigationInfo:   TABSNavigationInfo;
  Acceptable:       Boolean;
  i:                Integer;
  IndexPosCache:    TABSIndexPositionCache;
begin
  if ((not (Self is TABSDiskTableData)) and (RecordBitmap <> nil) and (ForceFillWholeBitmap) and
      (Conditions.Count = 0) and (FilterRecordPtr = nil) and (CurrentIndexID = INVALID_OBJECT_ID)) then
   begin
    Result := False;
    RecordBitmap.SetAllBits;
   end
  else
   begin
      NavigationInfo.RecordID := CurrentRecordID;
      NavigationInfo.FirstPosition := (Restart and GoForward);
      NavigationInfo.LastPosition := (Restart and (not GoForward));
      if (GoForward) then
       NavigationInfo.GetRecordMode := grmNext
      else
       NavigationInfo.GetRecordMode := grmPrior;
      NavigationInfo.IndexID := CurrentIndexID;
      NavigationInfo.RecordBuffer := MemoryManager.GetMem(FRecordManager.RecordBufferSize);
      IndexPosCache := TABSIndexPositionCache.Create;
      try
        for i := 0 to Conditions.Count-1 do
         if (Conditions.Items[i].Expression <> nil) then
          TABSExpression(Conditions.Items[i].Expression).AssignCursorBuffer(
              NavigationInfo.RecordBuffer, False);
        // scan without active condition - check each record
        repeat
         InternalGetRecordBuffer(SessionID, NavigationInfo, IndexPosCache, RecordManager.TableState);
         Acceptable := False;
         if (NavigationInfo.GetRecordResult = grrOK) then
          begin
           Acceptable := IsRecordMatchConditions(
                            Conditions,
                            ScanConditionNo,ScanEndConditionNo,FilterRecordPtr,Dataset,
                            NavigationInfo.RecordBuffer, NavigationInfo.RecordID
                                                );
           if (Acceptable) and (RecordBitmap <> nil) then
             if (RecordBitmap.IndexBased) then
               RecordBitmap.ShowRecord(CurrentIndexID, TABSIndexPosition(IndexPosCache.Position))
             else
               RecordBitmap.ShowRecord(NavigationInfo.RecordID);
           if (ForceFillWholeBitmap) then
             Acceptable := False;
          end;
        until (Acceptable or (NavigationInfo.GetRecordResult <> grrOK));

        Result := (NavigationInfo.GetRecordResult = grrOK);
        if (Result) then
          if (ResultRecordID <> nil) then
           Move(NavigationInfo.RecordID,ResultRecordID^,sizeof(NavigationInfo.RecordID));
      finally
        IndexPosCache.Free;
        MemoryManager.FreeAndNillMem(NavigationInfo.RecordBuffer);
      end;
   end;
end; // FindRecordByScanWithoutCondition


//------------------------------------------------------------------------------
// find records by index and condition specified by ScanConditionNo
// condition index is the same as CurrentIndex
//------------------------------------------------------------------------------
function TABSTableData.FindRecordByScanWithConditionAndConditionIndex(
                          SessionID:          TABSSessionID;
                          Conditions:         TABSScanSearchConditionArray;
                          ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                          ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                          CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                          FilterRecordPtr:    Pointer;
                          Dataset:            Pointer;
                          CurrentRecordID:    TABSRecordID;
                          // find first or last record in order specified by index settings
                          Restart:            Boolean;
                          // go from first record to last or vice versa
                          GoForward:          Boolean;
                          // if record was found then return record id
                          ResultRecordID:     PABSRecordID;
                          // if specified - fill record bitmap
                          RecordBitmap:       TABSRecordBitmap;
                          ForceFillWholeBitmap: Boolean
                         ): Boolean;
var
  NavigationInfo:       TABSNavigationInfo;
  Acceptable:           Boolean;
  i:                    Integer;
  SearchInfo:           TABSSearchInfo;
begin
  Result := False;
  NavigationInfo.IndexID := CurrentIndexID;
  NavigationInfo.RecordID := CurrentRecordID;
  NavigationInfo.FirstPosition := False;
  NavigationInfo.LastPosition := False;
  NavigationInfo.GetRecordMode := grmCurrent;

  NavigationInfo.RecordBuffer := MemoryManager.GetMem(FRecordManager.RecordBufferSize);
  SearchInfo := FIndexManager.OpenIndex(CurrentIndexID).CreateSearchInfo;
  // find current position in index
  try
    for i := 0 to Conditions.Count-1 do
     if (Conditions.Items[i].Expression <> nil) then
      TABSExpression(Conditions.Items[i].Expression).AssignCursorBuffer(
          NavigationInfo.RecordBuffer, False);
    // load current record
    if (not Restart) then
      InternalGetRecordBuffer(SessionID, NavigationInfo, nil, -1);
    if ((Restart) or
        ((not Restart) and (NavigationInfo.GetRecordResult = grrOK))) then
     begin
      repeat
        if (ScanEndConditionNo <> INVALID_ID4) then
         // find record using the end condition
         Result := FIndexManager.OpenIndex(CurrentIndexID).FindRecord(
                     SessionID,
                     Restart,
                     GoForward,
                     Conditions.Items[ScanConditionNo],
                     Conditions.Items[ScanEndConditionNo],
                     NavigationInfo.RecordBuffer,
                     NavigationInfo.RecordID,
                     SearchInfo
                   )
        else
         // find record not using the end condition
         Result := FIndexManager.OpenIndex(CurrentIndexID).FindRecord(
                     SessionID,
                     Restart,
                     GoForward,
                     Conditions.Items[ScanConditionNo],
                     nil,
                     NavigationInfo.RecordBuffer,
                     NavigationInfo.RecordID,
                     SearchInfo
                   );
        Restart := False;
        Acceptable := False;
        if (Result) then
         begin
          if (NeedToCheckOtherConditions(Conditions, ScanConditionNo,
                                ScanEndConditionNo, FilterRecordPtr)) then
            begin
              InternalGetRecordBuffer(SessionID, NavigationInfo, nil, -1);
              Acceptable := IsRecordMatchConditions(
                          Conditions,
                          ScanConditionNo,ScanEndConditionNo,FilterRecordPtr,Dataset,
                          NavigationInfo.RecordBuffer, NavigationInfo.RecordID
                                              );
            end
          else
            Acceptable := True;
          if (RecordBitmap <> nil) then
            begin
              if (Acceptable) then
                RecordBitmap.ShowRecord(CurrentIndexID, PABSBTreeSearchInfo(SearchInfo)^.CurrentKeyPath);
              if (ForceFillWholeBitmap) then
                Acceptable := False;
            end;
         end;
      until ((not Result) or (Result and Acceptable));
     end;

    if (Result) then
      if (ResultRecordID <> nil) then
       Move(NavigationInfo.RecordID,ResultRecordID^,sizeof(NavigationInfo.RecordID));
  finally
    FIndexManager.OpenIndex(CurrentIndexID).FreeSearchInfo(SearchInfo);
    MemoryManager.FreeAndNillMem(NavigationInfo.RecordBuffer);
  end;
end; // FindRecordByScanWithConditionAndConditionIndex


//------------------------------------------------------------------------------
// find records by index and condition specified by ScanConditionNo
// there is no active index
//------------------------------------------------------------------------------
function TABSTableData.FindRecordByScanWithConditionAndWihtoutCurrentIndex(
                          SessionID:          TABSSessionID;
                          Conditions:         TABSScanSearchConditionArray;
                          ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                          ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                          CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                          FilterRecordPtr:    Pointer;
                          Dataset:            Pointer;
                          CurrentRecordID:    TABSRecordID;
                          // find first or last record in order specified by index settings
                          Restart:            Boolean;
                          // go from first record to last or vice versa
                          GoForward:          Boolean;
                          // if record was found then return record id
                          ResultRecordID:     PABSRecordID;
                          // if specified - fill record bitmap
                          RecordBitmap:       TABSRecordBitmap;
                          ForceFillWholeBitmap: Boolean
                         ): Boolean;
var
  NavigationInfo:       TABSNavigationInfo;
  Acceptable:           Boolean;
  i,Res:                Integer;
  ResRecordID:          TABSRecordID;
  RecordFound:          Boolean;
  FindRestart:          Boolean;
  ConditionIndexID:     TABSObjectID;
  SearchInfo:           TABSSearchInfo;
  MayReturnFirstFoundRecord: Boolean;
begin
  RecordFound := False;
  ConditionIndexID := Conditions.Items[ScanConditionNo].IndexID;
  // records with the same index key are sorted by RecordID?
  MayReturnFirstFoundRecord := ((Conditions.Items[ScanConditionNo].Condition = scEqual) and (not Conditions.Items[ScanConditionNo].PartialCompare) and
                                (FIndexManager.OpenIndex(ConditionIndexID).IndexDef.ColumnCount = 1));
  NavigationInfo.RecordID := CurrentRecordID;
  NavigationInfo.IndexID := INVALID_OBJECT_ID;
  NavigationInfo.GetRecordMode := grmCurrent;
  NavigationInfo.FirstPosition := False;
  NavigationInfo.LastPosition := False;
  NavigationInfo.RecordBuffer := MemoryManager.GetMem(FRecordManager.RecordBufferSize);
  SearchInfo := FIndexManager.OpenIndex(ConditionIndexID).CreateSearchInfo;
  try
    for i := 0 to Conditions.Count-1 do
     if (Conditions.Items[i].Expression <> nil) then
      TABSExpression(Conditions.Items[i].Expression).AssignCursorBuffer(
          NavigationInfo.RecordBuffer, False);
    // if FindNext and we can use fact that records sorted by RecordID -
    // find current position in index
    if ((not Restart) and
        (Conditions.Items[ScanConditionNo].Condition = scEqual) and
        (FIndexManager.OpenIndex(ConditionIndexID).IndexDef.ColumnCount = 1)) then
      begin
        InternalGetRecordBuffer(SessionID, NavigationInfo, nil, -1);
        FindRestart := False;
      end
    else
      FindRestart := True;

    // reset bitmap befor its filling?
    if ((RecordBitmap <> nil) and Restart) then
      InitRecordBitmap(RecordBitmap, INVALID_OBJECT_ID);

    repeat
      if (ScanEndConditionNo <> INVALID_ID4) then
        Result := FIndexManager.OpenIndex(ConditionIndexID).FindRecord(
                    SessionID,
                    FindRestart,
                    GoForward,
                    Conditions.Items[ScanConditionNo],
                    Conditions.Items[ScanEndConditionNo],
                    NavigationInfo.RecordBuffer,
                    NavigationInfo.RecordID,
                    SearchInfo
                  )
      else
        Result := FIndexManager.OpenIndex(ConditionIndexID).FindRecord(
                    SessionID,
                    FindRestart,
                    GoForward,
                    Conditions.Items[ScanConditionNo],
                    nil,
                    NavigationInfo.RecordBuffer,
                    NavigationInfo.RecordID,
                    SearchInfo
                  );
      if (Result) then
       if (NeedToCheckOtherConditions(Conditions, ScanConditionNo,
                                     ScanEndConditionNo, FilterRecordPtr)) then
        InternalGetRecordBuffer(SessionID, NavigationInfo, nil, -1);
      FindRestart := False;
      Acceptable := False;
      if (Result) then
       if (NeedToCheckOtherConditions(Conditions, ScanConditionNo,
                                ScanEndConditionNo, FilterRecordPtr)) then
         Acceptable := IsRecordMatchConditions(
                          Conditions, ScanConditionNo,ScanEndConditionNo,
                          FilterRecordPtr,Dataset,
                          NavigationInfo.RecordBuffer, NavigationInfo.RecordID)
       else
         Acceptable := True;
      if (Result and Acceptable) then
       begin
        if (RecordBitmap <> nil) then
          RecordBitmap.ShowRecord(NavigationInfo.RecordID);
        if (not ForceFillWholeBitmap) then
        if (Restart) then
         begin
          // find minimum or maximum RecordID that meets all conditions
          if (not RecordFound) then
           begin
            // first record found
            ResRecordID := NavigationInfo.RecordID;
            RecordFound := True;
            // if search for "=", and index on 1 field => records are already sorted by RecordID
            if (MayReturnFirstFoundRecord) then
              begin
                Result := True;
                break;
              end;
           end // first record found
          else
           begin
            // check current record for < or > result record
            Res := FRecordManager.CompareRecordID(NavigationInfo.RecordID, ResRecordID);
            if ((GoForward and (Res < 0)) or
                ((not GoForward) and (Res > 0))) then
              ResRecordID := NavigationInfo.RecordID;
           end; // not first record found
         end // Restart
        else
         begin
          // find next or prior record that mathces all conditions
          Res := FRecordManager.CompareRecordID(
                   NavigationInfo.RecordID,
                   CurrentRecordID);
          if ((GoForward) and (Res > 0)) then
           begin
            // find next record
            if (not RecordFound) then
             begin
              RecordFound := True;
              ResRecordID := NavigationInfo.RecordID;
              // if search for "=", then records are already sorted by RecordID
              if (MayReturnFirstFoundRecord) then
                begin
                  Result := True;
                  break;
                end;
             end // first record found
            else
             begin
              Res := FRecordManager.CompareRecordID(
                      NavigationInfo.RecordID,
                      ResRecordID
                     );
              if (Res < 0) then
                ResRecordID := NavigationInfo.RecordID;
             end; // not first record found
           end // find next record
          else
          if ((not GoForward) and (Res < 0)) then
           begin
            // find next record
            if (not RecordFound) then
             begin
              RecordFound := True;
              ResRecordID := NavigationInfo.RecordID;
             end // first record found
            else
             begin
              Res := FRecordManager.CompareRecordID(
                      NavigationInfo.RecordID,
                      ResRecordID
                     );
              if (Res > 0) then
                ResRecordID := NavigationInfo.RecordID;
             end; // not first record found
           end; // find prior record
         end; // Not restart
       end; // record meets conditions
    until (not Result);

    // bitmap is filled anyway?
    if (RecordBitmap <> nil) then
      if ((not ForceFillWholeBitmap) and (Restart) and (not MayReturnFirstFoundRecord)) then
        RecordBitmap.Active := True;

    if (ForceFillWholeBitmap) then
      Result := False
    else
      Result := RecordFound;
    if (Result) then
      if (ResultRecordID <> nil) then
       Move(ResRecordID,ResultRecordID^,sizeof(ResRecordID));

  finally
    FIndexManager.OpenIndex(ConditionIndexID).FreeSearchInfo(SearchInfo);
    MemoryManager.FreeAndNillMem(NavigationInfo.RecordBuffer);
  end;
end; // FindRecordByScanWithConditionAndWihtoutCurrentIndex


//------------------------------------------------------------------------------
// find records by index and condition specified by ScanConditionNo
// condition index is NOT the same as CurrentIndex
//------------------------------------------------------------------------------
function TABSTableData.FindRecordByScanWithConditionAndNonConditionIndex(
                          SessionID:          TABSSessionID;
                          Conditions:         TABSScanSearchConditionArray;
                          ScanConditionNo:    Integer; // INVALID_ID4 if not specified
                          ScanEndConditionNo: Integer; // INVALID_ID4 if not specified
                          CurrentIndexID:     TABSObjectID; // INVALID_OBJECT_ID if not specified
                          FilterRecordPtr:    Pointer;
                          Dataset:            Pointer;
                          CurrentRecordID:    TABSRecordID;
                          // find first or last record in order specified by index settings
                          Restart:            Boolean;
                          // go from first record to last or vice versa
                          GoForward:          Boolean;
                          // if record was found then return record id
                          ResultRecordID:     PABSRecordID;
                          // if specified - fill record bitmap
                          RecordBitmap:       TABSRecordBitmap;
                          ForceFillWholeBitmap: Boolean
                         ): Boolean;
var
  NavigationInfo:       TABSNavigationInfo;
  Acceptable:           Boolean;
  i,Res:                Integer;
  ResultRecPosInIndex:  TABSIndexPosition;
  ResRecordID:          TABSRecordID;
  ResRecPosInIndex:     TABSIndexPosition;
  CurRecPosInIndex:     TABSIndexPosition;
  RecordFound:          Boolean;
  FindRestart:          Boolean;
  ConditionIndexID:     TABSObjectID;
  SearchInfo:           TABSSearchInfo;
begin
  Result := False;
  RecordFound := False;
  ConditionIndexID := Conditions.Items[ScanConditionNo].IndexID;
  NavigationInfo.RecordID := CurrentRecordID;
  NavigationInfo.IndexID := INVALID_OBJECT_ID;
  NavigationInfo.GetRecordMode := grmCurrent;
  NavigationInfo.FirstPosition := False;
  NavigationInfo.LastPosition := False;
  ResRecPosInIndex := nil;
  ResultRecPosInIndex := nil;
  NavigationInfo.RecordBuffer := MemoryManager.GetMem(FRecordManager.RecordBufferSize);
  SearchInfo := FIndexManager.OpenIndex(ConditionIndexID).CreateSearchInfo;
  CurRecPosInIndex := FIndexManager.OpenIndex(CurrentIndexID).CreateIndexPosition;
  try
    for i := 0 to Conditions.Count-1 do
     if (Conditions.Items[i].Expression <> nil) then
      TABSExpression(Conditions.Items[i].Expression).AssignCursorBuffer(
          NavigationInfo.RecordBuffer, False);
    // find current position in index
    FindRestart := True;
    if (not Restart) then
     begin
       // load current record position in index
       NavigationInfo.RecordID := CurrentRecordID;
       NavigationInfo.IndexID := CurrentIndexID;
       InternalGetRecordBuffer(SessionID, NavigationInfo, nil, -1);
       if (NavigationInfo.GetRecordResult <> grrOK) then
        raise EABSException.Create(10387,ErrorLCannotFindRecordPositionInIndex,
                [CurrentIndexID,NavigationInfo.RecordID.PageNo,
                NavigationInfo.RecordID.PageItemNo]);
       if ((NavigationInfo.RecordID.PageNo <> CurrentRecordID.PageNo) or
            (NavigationInfo.RecordID.PageItemNo <> CurrentRecordID.PageItemNo)) then
         raise EABSException.Create(10415,ErrorLCannotFindRecordPositionInIndex,
                [CurrentIndexID,NavigationInfo.RecordID.PageNo,
                NavigationInfo.RecordID.PageItemNo]);
       // find record pos in index
       if (not FIndexManager.OpenIndex(CurrentIndexID).GetIndexPosition(
                SessionID,
                CurrentRecordID,
                NavigationInfo.RecordBuffer,
                CurRecPosInIndex
               )
          ) then
        raise EABSException.Create(10386,ErrorLCannotFindRecordPositionInIndex,
                [CurrentIndexID,NavigationInfo.RecordID.PageNo,
                NavigationInfo.RecordID.PageItemNo]);
        if ((NavigationInfo.RecordID.PageNo <> CurrentRecordID.PageNo) or
            (NavigationInfo.RecordID.PageItemNo <> CurrentRecordID.PageItemNo)) then
         raise EABSException.Create(10414,ErrorLCannotFindRecordPositionInIndex,
                [CurrentIndexID,NavigationInfo.RecordID.PageNo,
                NavigationInfo.RecordID.PageItemNo]);
     end; // not restart

    // reset bitmap before its filling?
    if ((RecordBitmap <> nil) and Restart) then
      InitRecordBitmap(RecordBitmap, CurrentIndexID);

    repeat
      if (ScanEndConditionNo <> INVALID_ID4) then
        Result := FIndexManager.OpenIndex(ConditionIndexID).FindRecord(
                    SessionID,
                    FindRestart,
                    GoForward,
                    Conditions.Items[ScanConditionNo],
                    Conditions.Items[ScanEndConditionNo],
                    NavigationInfo.RecordBuffer,
                    NavigationInfo.RecordID,
                    SearchInfo
                  )
      else
        Result := FIndexManager.OpenIndex(ConditionIndexID).FindRecord(
                    SessionID,
                    FindRestart,
                    GoForward,
                    Conditions.Items[ScanConditionNo],
                    nil,
                    NavigationInfo.RecordBuffer,
                    NavigationInfo.RecordID,
                    SearchInfo
                  );
      if (Result) then
       InternalGetRecordBuffer(SessionID, NavigationInfo, nil, -1);
      FindRestart := False;
      Acceptable := False;
      if (Result) then
        Acceptable := IsRecordMatchConditions(
                    Conditions, ScanConditionNo,ScanEndConditionNo,
                    FilterRecordPtr,Dataset,
                    NavigationInfo.RecordBuffer, NavigationInfo.RecordID);
      if (Result and Acceptable) then
       begin
          ResultRecPosInIndex := FIndexManager.OpenIndex(CurrentIndexID).CreateIndexPosition;
          try
            // find record pos in current index
            if (not FIndexManager.OpenIndex(CurrentIndexID).GetIndexPosition(
                       SessionID,
                       NavigationInfo.RecordID,
                       NavigationInfo.RecordBuffer,
                       ResultRecPosInIndex
                     )
                ) then
              raise EABSException.Create(10432,ErrorLCannotFindRecordPositionInIndex,
                      [CurrentIndexID,NavigationInfo.RecordID.PageNo,
                      NavigationInfo.RecordID.PageItemNo]);
            // show record in bitmap
            if (RecordBitmap <> nil) then
              RecordBitmap.ShowRecord(CurrentIndexID,ResultRecPosInIndex);

            if (not ForceFillWholeBitmap) then
              begin
              if (Restart) then
               begin
                // find minimum or maximum RecordID that meets all conditions
                if (not RecordFound) then
                 begin
                  // first record found
                  ResRecordID := NavigationInfo.RecordID;
                  FIndexManager.OpenIndex(CurrentIndexID).
                                    FreeIndexPosition(ResRecPosInIndex);
                  ResRecPosInIndex := ResultRecPosInIndex;
                  RecordFound := True;
                 end // first record found
                else
                 begin
                  // check current record position for < or > result record position in index
                  Res := FIndexManager.OpenIndex(CurrentIndexID).
                          CompareRecordPositionsInIndex(
                            ResultRecPosInIndex,
                            ResRecPosInIndex
                          );
                  if ((GoForward and (Res < 0)) or ((not GoForward) and (Res > 0))) then
                   begin
                     ResRecordID := NavigationInfo.RecordID;
                     FIndexManager.OpenIndex(CurrentIndexID).
                                    FreeIndexPosition(ResRecPosInIndex);
                     ResRecPosInIndex := ResultRecPosInIndex;
                   end;
                 end; // not first record found
               end // Restart
              else
               begin
                // find next or prior record that mathces all conditions
                Res := FIndexManager.OpenIndex(CurrentIndexID).
                          CompareRecordPositionsInIndex(
                            ResultRecPosInIndex,
                            CurRecPosInIndex
                          );
                if ((GoForward) and (Res > 0)) then
                 begin
                  if (not RecordFound) then
                   begin
                    // first record found
                    ResRecordID := NavigationInfo.RecordID;
                    FIndexManager.OpenIndex(CurrentIndexID).
                                    FreeIndexPosition(ResRecPosInIndex);
                    ResRecPosInIndex := ResultRecPosInIndex;
                    RecordFound := True;
                   end // first record found
                  else
                   // find next record
                   begin
                    Res := FIndexManager.OpenIndex(CurrentIndexID).
                          CompareRecordPositionsInIndex(
                            ResultRecPosInIndex,
                            ResRecPosInIndex
                          );
                    if (Res < 0) then
                     begin
                       ResRecordID := NavigationInfo.RecordID;
                       FIndexManager.OpenIndex(CurrentIndexID).
                                      FreeIndexPosition(ResRecPosInIndex);
                       ResRecPosInIndex := ResultRecPosInIndex;
                     end;
                   end; // not first record found
                 end // find next record
                else
                if ((not GoForward) and (Res < 0)) then
                 begin
                  if (not RecordFound) then
                   begin
                    // first record found
                    ResRecordID := NavigationInfo.RecordID;
                    FIndexManager.OpenIndex(CurrentIndexID).
                                    FreeIndexPosition(ResRecPosInIndex);
                    ResRecPosInIndex := ResultRecPosInIndex;
                    RecordFound := True;
                   end // first record found
                  else
                   begin
                    Res := FIndexManager.OpenIndex(CurrentIndexID).
                          CompareRecordPositionsInIndex(
                            ResultRecPosInIndex,
                            ResRecPosInIndex
                          );
                    if (Res > 0) then
                     begin
                       ResRecordID := NavigationInfo.RecordID;
                       FIndexManager.OpenIndex(CurrentIndexID).
                                      FreeIndexPosition(ResRecPosInIndex);
                       ResRecPosInIndex := ResultRecPosInIndex;
                     end;
                   end; // prior record found
                 end; // not first record found
               end; // Not restart
              end;// not ForceFillBitmap
          finally
            if (ResRecPosInIndex <> ResultRecPosInIndex) then
              FIndexManager.OpenIndex(CurrentIndexID).FreeIndexPosition(ResultRecPosInIndex);
          end;
       end; // record meets conditions
    until (not Result);

    // bitmap is filled anyway?
    if (RecordBitmap <> nil) then
      if ((not ForceFillWholeBitmap) and (Restart)) then
        RecordBitmap.Active := True;

    if (ForceFillWholeBitmap) then
      Result := False
    else
      Result := RecordFound;
    if (Result) then
     begin
      if (ResultRecordID <> nil) then
       Move(ResRecordID,ResultRecordID^,sizeof(ResRecordID));
     end;
  finally
    FIndexManager.OpenIndex(ConditionIndexID).FreeSearchInfo(SearchInfo);
    FIndexManager.OpenIndex(CurrentIndexID).FreeIndexPosition(CurRecPosInIndex);
    if (ResultRecPosInIndex <> ResRecPosInIndex) then
      FIndexManager.OpenIndex(CurrentIndexID).FreeIndexPosition(ResRecPosInIndex);
    FIndexManager.OpenIndex(CurrentIndexID).FreeIndexPosition(ResultRecPosInIndex);
    MemoryManager.FreeAndNillMem(NavigationInfo.RecordBuffer);
  end;
end; // FindRecordByScanWithConditionAndNonConditionIndex


//------------------------------------------------------------------------------
// return true if record was found
//------------------------------------------------------------------------------
function TABSTableData.FindRecordByScan(
                          SessionID:        TABSSessionID;
                          Conditions:       TABSScanSearchConditionArray;
                          ScanConditionNo:  Integer; // INVALID_ID4 if not specified
                              ScanEndConditionNo:   Integer; // INVALID_ID4 if not specified
                          CurrentIndexID:   TABSObjectID; // INVALID_OBJECT_ID if not specified
                          FilterRecordPtr:  Pointer;
                          Dataset:          Pointer;
                          CurrentRecordID:  TABSRecordID;
                          // find first or last record in order specified by index settings
                          Restart:          Boolean;
                          // go from first record to last or vice versa
                          GoForward:        Boolean;
                          // if record was found then return record id
                          ResultRecordID:   PABSRecordID;
                          // if specified - fill record bitmap
                          RecordBitmap:     TABSRecordBitmap;
                          ForceFillWholeBitmap: Boolean
                         ): Boolean;
begin
 if (ScanConditionNo = INVALID_ID4) then
  Result := FindRecordByScanWithoutCondition(
                          SessionID,
                          Conditions,
                          ScanConditionNo,
                          ScanEndConditionNo,
                          CurrentIndexID,
                          FilterRecordPtr,
                          Dataset,
                          CurrentRecordID,
                          Restart,
                          GoForward,
                          ResultRecordID,
                          RecordBitmap,
                          ForceFillWholeBitmap
            ) // no condition
 else
  begin
   if (ScanConditionNo >= Integer(Conditions.Count)) then
    raise EABSException.Create(10382,ErrorLInvalidScanConditionNo,
      [ScanConditionNo,Conditions.Count]);
   if (CurrentIndexID = INVALID_OBJECT_ID) then
    Result := FindRecordByScanWithConditionAndWihtoutCurrentIndex(
                            SessionID,
                            Conditions,
                            ScanConditionNo,
                            ScanEndConditionNo,
                            CurrentIndexID,
                            FilterRecordPtr,
                            Dataset,
                            CurrentRecordID,
                            Restart,
                            GoForward,
                            ResultRecordID,
                            RecordBitmap,
                            ForceFillWholeBitmap
              ) // condition and physical order
   else
    begin
     if (CurrentIndexID = Conditions.Items[ScanConditionNo].IndexID) then
      Result := FindRecordByScanWithConditionAndConditionIndex(
                              SessionID,
                              Conditions,
                              ScanConditionNo,
                              ScanEndConditionNo,
                              CurrentIndexID,
                              FilterRecordPtr,
                              Dataset,
                              CurrentRecordID,
                              Restart,
                              GoForward,
                              ResultRecordID,
                              RecordBitmap,
                              ForceFillWholeBitmap
                ) // condition and condition.indexID = CurrentIndexID
     else
      Result := FindRecordByScanWithConditionAndNonConditionIndex(
                              SessionID,
                              Conditions,
                              ScanConditionNo,
                              ScanEndConditionNo,
                              CurrentIndexID,
                              FilterRecordPtr,
                              Dataset,
                              CurrentRecordID,
                              Restart,
                              GoForward,
                              ResultRecordID,
                              RecordBitmap,
                              ForceFillWholeBitmap
                ) // condition and condition.indexID <> CurrentIndexID
    end;
  end; // condition
end; // FindRecordByScan


//------------------------------------------------------------------------------
// used by GetRecordBuffer - find or get record
//------------------------------------------------------------------------------
function TABSTableData.InternalFindOrGetRecordBuffer(
                              Cursor:         TABSCursor;
                              GetRecordMode:  TABSGetRecordMode;
                              // if specified - fill record bitmap
                              ForceFillWholeBitmap: Boolean=False
                            ): TABSGetRecordResult;
var Restart, GoForward,
    RecordFound:        Boolean;
    RecordID:           TABSRecordID;
    NavigationInfo:     TABSNavigationInfo;
begin
  {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.InternalFindOrGetRecordBuffer start');{$ENDIF}
  try
   if (GetRecordMode <> grmCurrent) and (Cursor.IsViewRestricted) then
    begin
     // find
     Restart := (Cursor.FirstPosition or Cursor.LastPosition);
     GoForward := (GetRecordMode = grmNext);

     RecordFound := FindRecord(Cursor,nil,nil,Restart,GoForward,
                               @RecordID, Cursor.RecordBitmap,ForceFillWholeBitmap);
     if (RecordFound) then
      begin
       NavigationInfo.RecordBuffer := Cursor.CurrentRecordBuffer;
       NavigationInfo.RecordID := RecordID;
       NavigationInfo.FirstPosition := False;
       NavigationInfo.LastPosition := False;
       NavigationInfo.GetRecordMode := grmCurrent;
       FRecordManager.GetRecordBuffer(Cursor.Session.SessionID, NavigationInfo);
       Result := NavigationInfo.GetRecordResult;
       Cursor.CurrentRecordID := NavigationInfo.RecordID;
       Cursor.FirstPosition := NavigationInfo.FirstPosition;
       Cursor.LastPosition := NavigationInfo.LastPosition;
      end
     else
      begin
       if (GetRecordMode = grmNext) then
        Result := grrEOF
       else
        Result := grrBOF;
      end;
    end
   else
    begin
       NavigationInfo.IndexID := Cursor.IndexID;
       NavigationInfo.RecordBuffer := Cursor.CurrentRecordBuffer;
       NavigationInfo.RecordID := Cursor.CurrentRecordID;
       NavigationInfo.FirstPosition := Cursor.FirstPosition;
       NavigationInfo.LastPosition := Cursor.LastPosition;
       NavigationInfo.GetRecordMode := GetRecordMode;

       InternalGetRecordBuffer(Cursor.Session.SessionID, NavigationInfo,
                               Cursor.IndexPositionCache,
                               FRecordManager.TableState);
       Result := NavigationInfo.GetRecordResult;
       // current record after insert/edit may become hidden
       if ((Result = grrOK) and (Cursor.IsViewRestricted) and
          (not IsRecordVisible(Cursor))) then
           Result := grrError
       else
         begin
           Cursor.CurrentRecordID := NavigationInfo.RecordID;
           Cursor.FirstPosition := NavigationInfo.FirstPosition;
           Cursor.LastPosition := NavigationInfo.LastPosition;
         end;
    end;
  finally
    {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.InternalFindOrGetRecordBuffer end');{$ENDIF}
  end;
end; // InternalFindOrGetRecordBuffer


//------------------------------------------------------------------------------
// get record using current index or physical order
//------------------------------------------------------------------------------
procedure TABSTableData.InternalGetRecordBuffer(
                                   SessionID:          TABSSessionID;
                                   var NavigationInfo: TABSNavigationInfo;
                                   IndexPositionCache: TABSIndexPositionCache;
                                   TableState:         Integer
                                      );
begin
  if (FRecordManager = nil) then
   raise EABSException.Create(10015,ErrorLNilPointer);
  if (FIndexManager = nil) then
   raise EABSException.Create(10380,ErrorLNilPointer);
  if (NavigationInfo.RecordBuffer = nil) then
   raise EABSException.Create(10016,ErrorLNilPointer);
  if ((NavigationInfo.IndexID <> INVALID_OBJECT_ID) and
    (NavigationInfo.GetRecordMode <> grmCurrent)) then
    FIndexManager.GetRecordBuffer(SessionID, NavigationInfo, IndexPositionCache,
                                  TableState)
  else
    FRecordManager.GetRecordBuffer(SessionID, NavigationInfo);
end; // InternalGetRecordBuffer


//------------------------------------------------------------------------------
// ClearBlobsCacheInRecordBuffer
//------------------------------------------------------------------------------
procedure TABSTableData.ClearBlobsCacheInRecordBuffer(Buffer: TABSRecordBuffer);
begin
//  do nothing in memory and temporary engines
end;// ClearBlobsCacheInRecordBuffer


//------------------------------------------------------------------------------
// get record buffer
//------------------------------------------------------------------------------
function TABSTableData.GetRecordBuffer(
              Cursor:         TABSCursor;
              GetRecordMode:  TABSGetRecordMode
              ): TABSGetRecordResult;
var IsDistinctApplied:  Boolean;
    Acceptable:         Boolean;
    Buffer:             TABSRecordBuffer;
    NavigationInfo:     TABSNavigationInfo;
begin
  {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.GetRecordBuffer start');{$ENDIF}
  try
    if (FRecordManager.RecordCount = 0) then
     begin
      Result := grrEOF;
      Exit;
     end;
    Result := grrError;
    // distinct
    IsDistinctApplied := ((Cursor.IsDistinctApplied) and
         ((GetRecordMode = grmPrior) or (GetRecordMode = grmNext)) and
         (not Cursor.FirstPosition) and (not Cursor.LastPosition));
    if (IsDistinctApplied) then
      Cursor.DistinctRecordBuffer := Cursor.AllocateRecordBuffer
    else
      Cursor.DistinctRecordBuffer := nil;
    try
    repeat
     Acceptable := True;
     // distinct
     if (IsDistinctApplied) then
      begin
       NavigationInfo.RecordBuffer := Cursor.DistinctRecordBuffer;
       NavigationInfo.RecordID := Cursor.CurrentRecordID;
       NavigationInfo.FirstPosition := Cursor.FirstPosition;
       NavigationInfo.LastPosition := Cursor.LastPosition;
       NavigationInfo.GetRecordMode := grmCurrent;
       FRecordManager.GetRecordBuffer(Cursor.Session.SessionID, NavigationInfo);
       if (NavigationInfo.GetRecordResult <> grrOK) then
        break;
      end;
     Result := InternalFindOrGetRecordBuffer(Cursor,GetRecordMode);
     if ((GetRecordMode = grmCurrent) and (not Acceptable)) then
       Result := grrError;
    until ((Acceptable) or (Result <> grrOK));

   finally
    if (IsDistinctApplied) then
     if (Cursor.DistinctRecordBuffer <> nil) then
      begin
        Buffer := Cursor.DistinctRecordBuffer;
        Cursor.FreeRecordBuffer(Buffer);
        Cursor.DistinctRecordBuffer := nil;
      end;
    end;
  finally
    {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSTableData.GetRecordBuffer end');{$ENDIF}
  end;
end; // GetRecordBuffer


//------------------------------------------------------------------------------
// locate
//------------------------------------------------------------------------------
function TABSTableData.Locate(Cursor: TABSCursor; SearchExpression: TABSExpression): Boolean;
var OldPos:      Pointer;
    RecordID:    TABSRecordID;
begin
 if (SearchExpression = nil) then
  raise EABSException.Create(10272,ErrorLNilPointer);
 Lock;
 Result := False;
 OldPos := Cursor.SavePosition;
 try
  Result := FindRecord(Cursor,SearchExpression,nil,True,True,@RecordID,nil);
  if (Result) then
   begin
    Cursor.FirstPosition := False;
    Cursor.LastPosition := False;
    Cursor.CurrentRecordID := RecordID;
    GetRecordBuffer(Cursor,grmCurrent);
   end;
 finally
  if (not Result) then
   Cursor.RestorePosition(OldPos);
  Cursor.FreePosition(OldPos);
  Unlock;
 end;
end;// Locate


//------------------------------------------------------------------------------
// find key
//------------------------------------------------------------------------------
function TABSTableData.FindKey(Cursor: TABSCursor; SearchCondition: TABSSearchCondition): Boolean;
var OldPos:             Pointer;
    KeyCondition:       TABSScanSearchCondition;
    RecordID:           TABSRecordID;
begin
 Result := False;
 Lock;
 OldPos := Cursor.SavePosition;
 KeyCondition := TABSScanSearchCondition.Create(True);
 try
  KeyCondition.Condition := SearchCondition;
  KeyCondition.KeyFieldCount := Cursor.KeyFieldCount;
  KeyCondition.KeyRecordBuffer := Cursor.KeyBuffer;
  KeyCondition.KeyRecordBufferSize := Cursor.KeyBufferSize;
  KeyCondition.IndexID := Cursor.IndexID;
  KeyCondition.Expression := nil;
  Result := FindRecord(Cursor,nil,KeyCondition,True,True,@RecordID,nil);
  if (Result) then
   begin
    Cursor.FirstPosition := False;
    Cursor.LastPosition := False;
    Cursor.CurrentRecordID := RecordID;
   end;
 finally
  if (not Result) then
    Cursor.RestorePosition(OldPos);
  Cursor.FreePosition(OldPos);
  KeyCondition.Free;
  Unlock;
 end;
end; // FindKey


//---------------------------------------------------------------------------
// clear BLOB field in record buffer
//---------------------------------------------------------------------------
procedure TABSTableData.ClearBLOBFieldInRecordBuffer(
          RecordBuffer: TABSRecordBuffer;
          FieldNo:    Integer);
begin
; // do nothing by default
end; // ClearBLOBFieldInRecordBuffer


//------------------------------------------------------------------------------
// clear all blob fields in record buffer - for cancel, delete operation
//------------------------------------------------------------------------------
procedure TABSTableData.ClearBLOBFieldsInRecordBuffer(
              RecordBuffer: TABSRecordBuffer);
var i: Integer;
begin
 if (FFieldManager.BlobFieldsPresent) then
   for i := 0 to FieldManager.FieldDefs.Count - 1 do
    if (IsBLOBFieldType(FieldManager.FieldDefs[i].BaseFieldType)) then
     begin
      ClearBLOBFieldInRecordBuffer(RecordBuffer,i);
     end;
end; // ClearBLOBFieldsInRecordBuffer


//------------------------------------------------------------------------------
// return record count
//------------------------------------------------------------------------------
function TABSTableData.GetRecordCount(Cursor: TABSCursor): TABSRecordNo;
begin
 Lock;
 try
    if (Cursor.IsViewRestricted) then
    begin
     // build or rebuild bitmap if necessary
     RefreshCursorRecordBitmap(Cursor);
     Result := TABSRecordBitmap(Cursor.RecordBitmap).GetRecordCount;
    end
   else
    Result := InternalGetRecordCount(Cursor);
 finally
  Unlock;
 end;
end; // GetRecordCount


//------------------------------------------------------------------------------
// set rec no
//------------------------------------------------------------------------------
procedure TABSTableData.SetRecNo(Cursor: TABSCursor; RecNo: TABSRecordNo);
var
  RecCount: TABSRecordNo;
  IndexPos: TABSIndexPosition;
begin
 Lock;
 try
   RecCount := GetRecordCount(Cursor);
   if (RecCount > 0) then
     begin
       if (RecNo >= RecCount) then
        RecNo := RecCount;
       Cursor.FirstPosition := False;
       Cursor.LastPosition := False;
       if (Cursor.IsIndexApplied) then
         begin
           // scan IndexPos-based records bitmap
           if (Cursor.IsViewRestricted) then
             begin
               IndexPos := FIndexManager.OpenIndex(Cursor.IndexID).CreateIndexPosition;
               try
                 TABSRecordBitmap(Cursor.RecordBitmap).GetIndexPosByRecNo(Cursor.IndexID, RecNo, IndexPos);
                 GetRecordIDByIndexPosition(Cursor.Session.SessionID, Cursor.IndexID, IndexPos, Cursor.CurrentRecordID);
               finally
                 FIndexManager.OpenIndex(Cursor.IndexID).FreeIndexPosition(IndexPos);
               end;
             end
           else
             Cursor.CurrentRecordID :=
                FIndexManager.OpenIndex(Cursor.IndexID).GetRecordIDByRecNo(Cursor.Session.SessionID, RecNo);
         end // index applied
       else
         if (Cursor.IsViewRestricted) then
           Cursor.CurrentRecordID := TABSRecordBitmap(Cursor.RecordBitmap).GetRecordIDByRecNo(RecNo)
         else
           InternalSetRecNo(Cursor,RecNo);
     end; // if RecCount > 0
 finally
  Unlock;
 end;
end; // SetRecNo


//------------------------------------------------------------------------------
// return rec no
//------------------------------------------------------------------------------
function TABSTableData.GetRecNo(Cursor: TABSCursor): TABSRecordNo;
var
  NavigationInfo: TABSNavigationInfo;
  IndexPos: TABSIndexPosition;
begin
 Lock;
 try
   if (Cursor.FirstPosition) or (Cursor.LastPosition) or (GetRecordCount(Cursor) = 0) then
    Result := -1
   else
    begin
     // build or rebuild bitmap if necessary
     RefreshCursorRecordBitmap(Cursor);
     if (Cursor.IsIndexApplied) then
      begin
         NavigationInfo.RecordBuffer := MemoryManager.AllocMem(GetRecordBufferSize);
         try
           NavigationInfo.FirstPosition := False;
           NavigationInfo.LastPosition := False;
           NavigationInfo.RecordID := Cursor.CurrentRecordID;
           NavigationInfo.GetRecordMode := grmCurrent;
           InternalGetRecordBuffer(Cursor.Session.SessionID, NavigationInfo, nil, -1);
           if (NavigationInfo.GetRecordResult <> grrOK) then
            Result := -1
           else
             begin
               // scan IndexPos-based records bitmap
               if (Cursor.IsViewRestricted) then
                 begin
                   IndexPos := FIndexManager.OpenIndex(Cursor.IndexID).CreateIndexPosition;
                   try
                     if (GetIndexPositionByRecordID(Cursor.Session.SessionID, Cursor.IndexID, Cursor.CurrentRecordID, IndexPos)) then
                       Result := TABSRecordBitmap(Cursor.RecordBitmap).GetRecNoByIndexPos(Cursor.IndexID, IndexPos)
                     else
                       Result := -1;
                   finally
                     FIndexManager.OpenIndex(Cursor.IndexID).FreeIndexPosition(IndexPos);
                   end;
                 end
               else
                 Result := FIndexManager.OpenIndex(Cursor.IndexID).GetRecNoByRecordID(
                                                        Cursor.Session.SessionID,
                                                        Cursor.CurrentRecordID,
                                                        NavigationInfo.RecordBuffer);
             end;
         finally
           MemoryManager.FreeAndNillMem(NavigationInfo.RecordBuffer);
         end;
      end // index applied
     else
       if (Cursor.IsViewRestricted) then
         Result := TABSRecordBitmap(Cursor.RecordBitmap).GetRecNoByRecordID(Cursor.CurrentRecordID)
       else
         Result := InternalGetRecNo(Cursor);
    end; // get recno
 finally
  Unlock;
 end;
end; // GetRecNo


//------------------------------------------------------------------------------
// return last auto inc value
//------------------------------------------------------------------------------
function TABSTableData.LastAutoincValue(FieldNo: Integer; Session: TABSBaseSession): Int64;
begin
 Result := 0;
end; // LastAutoincValue


//------------------------------------------------------------------------------
// Rollback
//------------------------------------------------------------------------------
procedure TABSTableData.Rollback(SessionID: TABSSessionID);
begin
  RebuildTemporaryIndexes(SessionID);
end;// Rollback


//------------------------------------------------------------------------------
// RebuildTemporaryIndexes
//------------------------------------------------------------------------------
procedure TABSTableData.RebuildTemporaryIndexes(SessionID: TABSSessionID);
var
  i: Integer;
  IsTempIndexChanged: Boolean;
begin
  if (FIndexManager <> nil) and (FCursorList <> nil) then
    for i:=0 to FCursorList.Count-1 do
      if (TABSCursor(FCursorList[i]).Session <> nil) and
         (TABSCursor(FCursorList[i]).Session.SessionID = SessionID) then
        FIndexManager.SynchronizeTemporaryIndexes(FCursorList[i], IsTempIndexChanged);
end;// RebuildTemporaryIndexes


////////////////////////////////////////////////////////////////////////////////
//
// TABSDatabaseData
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
constructor TABSDatabaseData.Create;
begin
 InitializeCriticalSection(FCSect);
 FSessionList := TList.Create;
 FTableDataList := TList.Create;
 FPageManager := nil;
 FTryCount := DefaultRetryCount;
 FDelayMS := DefaultDelayMS;
 FMultiUser := False;
end;// Create


//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSDatabaseData.Destroy;
var
  DBDatas: TList;
begin
  while (FTableDataList.Count > 0) do
    TABSTableData(FTableDataList.Items[0]).Free;
  FTableDataList.Free;
  FSessionList.Free;

  DBDatas := DBDataList.LockList;
  DBDatas.Remove(Self);
  DBDataList.UnlockList;

  DeleteCriticalSection(FCSect);
end;// Destroy


//------------------------------------------------------------------------------
// lock
//------------------------------------------------------------------------------
procedure TABSDatabaseData.Lock;
begin
  EnterCriticalSection(FCSect);
end;// Lock


//------------------------------------------------------------------------------
// unlock
//------------------------------------------------------------------------------
procedure TABSDatabaseData.Unlock;
begin
  LeaveCriticalSection(FCSect);
end;// Unlock


//------------------------------------------------------------------------------
// Connect local session
//------------------------------------------------------------------------------
procedure TABSDatabaseData.ConnectSession(Session: TABSBaseSession);
begin
  Lock;
  try
   FSessionList.Add(Session);
   Session.SessionID := 0; // for mem and temp engines
  finally
   Unlock;
  end;
end;// ConnectSession


//------------------------------------------------------------------------------
// disconnect local session
//------------------------------------------------------------------------------
procedure TABSDatabaseData.DisconnectSession(Session: TABSBaseSession);
begin
  Lock;
  try
   FSessionList.Remove(Session);
  finally
   Unlock;
  end;
end;// DisconnectSession


//------------------------------------------------------------------------------
// FreeIfNoSessionsConnected
//------------------------------------------------------------------------------
procedure TABSDatabaseData.FreeIfNoSessionsConnected;
begin
  if (FSessionList.Count = 0) then
    Free;
end;// FreeIfNoSessionsConnected


//------------------------------------------------------------------------------
// get list of tables
//------------------------------------------------------------------------------
procedure TABSDatabaseData.GetTablesList(List: TStrings);
var
  i: Integer;
begin
  Lock;
  try
   for i := 0 to FTableDataList.Count-1 do
    List.Add(TABSTableData(FTableDataList.Items[i]).TableName);
  finally
   Unlock;
  end;
end;// GetTablesList


//------------------------------------------------------------------------------
// TableExists
//------------------------------------------------------------------------------
function TABSDatabaseData.TableExists(TableName: string): Boolean;
var
  List: TStringList;
begin
  Lock;
  try
    List := TStringList.Create;
    try
      GetTablesList(List);
      Result := List.IndexOf(TableName) > -1;
    finally
      List.Free;
    end;
  finally
   Unlock;
  end;
end;// TableExists


//------------------------------------------------------------------------------
// CreateDatabase
//------------------------------------------------------------------------------
procedure TABSDatabaseData.CreateDatabase;
begin
end;// CreateDatabase


//------------------------------------------------------------------------------
// find table data
//------------------------------------------------------------------------------
function TABSDatabaseData.FindTableData(Cursor: TABSCursor): TABSTableData;
var
  i: Integer;
begin
  Lock;
  try
   Result := nil;
   for i := 0 to FTableDataList.Count-1 do
    if (AnsiUpperCase(TABSTableData(FTableDataList.Items[i]).TableName) =
        AnsiUpperCase(Cursor.TableName)) then
     begin
      Result := FTableDataList.Items[i];
      break;
     end;
  finally
   Unlock;
  end;
end;// FindTableData


//------------------------------------------------------------------------------
// find or create table data
//------------------------------------------------------------------------------
function TABSDatabaseData.FindOrCreateTableData(Cursor: TABSCursor): TABSTableData;
begin
  Lock;
  try
   Result := FindTableData(Cursor);
   if (Result = nil) then
     Result := CreateTableData(Cursor);
  finally
   Unlock;
  end;
end;// FindOrCreateTableData


//------------------------------------------------------------------------------
// Commit
//------------------------------------------------------------------------------
procedure TABSDatabaseData.Commit(SessionID: TABSSessionID; DoFlushBuffers: Boolean=True);
begin
  Lock;
  try
    if (PageManager <> nil) then
      PageManager.ApplyChanges(SessionID);
  finally
   Unlock;
  end;
end;// Commit


//------------------------------------------------------------------------------
// Rollback
//------------------------------------------------------------------------------
procedure TABSDatabaseData.Rollback(SessionID: TABSSessionID);
var
  i: Integer;
begin
  Lock;
  try
    if (PageManager <> nil) then
      PageManager.CancelChanges(SessionID);
    for i := 0 to FTableDataList.Count-1 do
      TABSTableData(FTableDataList[i]).Rollback(SessionID);
  finally
   Unlock;
  end;
end;// Rollback


//------------------------------------------------------------------------------
// FlushBuffers
//------------------------------------------------------------------------------
procedure TABSDatabaseData.FlushBuffers;
begin
end;// FlushBuffers



////////////////////////////////////////////////////////////////////////////////
//
// General functions and procedures
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// show error message
//------------------------------------------------------------------------------
procedure ShowErrorMessage(ErrorMessage: string);
begin
 {$IFNDEF NO_DIALOGS}
 MessageDlg(ErrorMessage,mtError,[mbOk],0);
 {$ENDIF}
end;


//------------------------------------------------------------------------------
// show warning message
//------------------------------------------------------------------------------
procedure ShowWarningMessage(WarningMessage: string);
begin
 {$IFNDEF NO_DIALOGS}
 MessageDlg(WarningMessage,mtWarning,[mbOk],0);
 {$ENDIF} 
end;


//------------------------------------------------------------------------------
// return false if field value is null
//------------------------------------------------------------------------------
function GetFieldData(
            // field number in FieldDefs
            FieldNo: Integer;
            // field definitions
            FieldDefs: TABSFieldDefs;
            // pointer to allocated buffer for storing field value
            Buffer: Pointer;
            // record buffer
            RecordBuffer: TABSRecordBuffer
            ): Boolean;
begin
 Result := False;
 // check if field data is NULL
 if (CheckNullFlag(FieldNo,RecordBuffer)) then
   // 6.07 addition
   if not ((NullStringAsBlankString) and (FieldDefs[FieldNo].AdvancedFieldType in [aftChar, aftString, aftWideChar, aftWideString, aftGuid, aftMemo, aftFormattedMemo, aftWideMemo])) then
     Exit; // field data is NULL

 Result := True;

 // function was used only for checking a null flag - no result buffer
 if (Buffer = nil) then
  Exit;

 if (FieldNo >= Integer(FieldDefs.Count)) then
   raise EABSException.Create(10000,ErrorLInvalidFieldNumber,[FieldNo,FieldDefs.Count]);

 // if blob field was specified then exit
 if (IsBLOBFieldType(FieldDefs[FieldNo].AdvancedFieldType)) then
  Exit;

 if (Buffer = nil) then
   raise EABSException.Create(10001,ErrorLNilPointer);

 if (RecordBuffer = nil) then
   raise EABSException.Create(10003,ErrorLNilPointer);

 try
  // Convert
  //  Single --> Double
  //  Extended --> Double
  case FieldDefs[FieldNo].AdvancedFieldType of
    aftSingle:     PDouble(Buffer)^ := PSingle(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^;
    aftExtended:   PDouble(Buffer)^:= PExtended(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^;
    aftCardinal:   PInt64(Buffer)^ := PCardinal(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^;
    aftShortint:   PSmallint(Buffer)^ := PShortint(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^;
    else
      begin
        // 6.07
        if CheckNullFlag(FieldNo,RecordBuffer) and ((NullStringAsBlankString) and (FieldDefs[FieldNo].AdvancedFieldType in [aftChar, aftString, aftWideChar, aftWideString, aftGuid, aftMemo, aftFormattedMemo, aftWideMemo])) then
          FillChar(Buffer^, FieldDefs.Items[FieldNo].MemoryDataSize, #0)
        else
          Move(TAbsPByte(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^,Buffer^,
                 FieldDefs.Items[FieldNo].MemoryDataSize);
      end;
  end;
 except
  raise EABSException.Create(10002,
          ErrorLErrorGettingFieldData,[FieldNo,
          FieldDefs.Items[FieldNo].MemoryOffset,
          FieldDefs.Items[FieldNo].MemoryDataSize]);
 end;
end; //GetFieldData


//------------------------------------------------------------------------------
// return false if field value is null
//------------------------------------------------------------------------------
procedure SetFieldData(
            // physical field No
            FieldNo: Integer;
            // field definitions
            FieldDefs: TABSFieldDefs;
            // pointer to allocated buffer with new field value
            Buffer: Pointer;
            // record buffer
            RecordBuffer: TABSRecordBuffer
            );
//var Size: Integer;
var
  Zero: Byte;
begin
 if (FieldNo > Integer(FieldDefs.Count)) then
   raise EABSException.Create(10004,ErrorLInvalidFieldNumber,[FieldNo,FieldDefs.Count]);

 if (Buffer = nil) then
  begin
   // set null flag only
   SetNullFlag(True,FieldNo,RecordBuffer);
  end // set null flag only
 else
  begin
   // empty string
   if (
        (IsStringFieldType(FieldDefs[FieldNo].AdvancedFieldType) and not IsWideStringFieldType(FieldDefs[FieldNo].AdvancedFieldType)) and
        (PByte(Buffer)^ = 0))
       or
        ((IsWideStringFieldType(FieldDefs[FieldNo].AdvancedFieldType)) and
        (PWord(Buffer)^ = 0)
      ) then
    // empty string
    SetNullFlag(True,FieldNo,RecordBuffer)
   else
    begin
     // not empty field
     SetNullFlag(False,FieldNo,RecordBuffer);

     // Convert
     //  Double --> Single or
     //  Double --> Extended
     case FieldDefs[FieldNo].AdvancedFieldType of
       aftSingle:     PSingle(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^ := pDouble(Buffer)^;
       aftExtended:   PExtended(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^:= pDouble(Buffer)^;
       aftCardinal:   PCardinal(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^:= Cardinal(PInt64(Buffer)^);
       aftShortint:   PShortint(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^:= Shortint(PSmallint(Buffer)^);
       else
          // copy data
          Move(Buffer^, TAbsPByte(RecordBuffer + FieldDefs.Items[FieldNo].MemoryOffset)^,
            FieldDefs.Items[FieldNo].MemoryDataSize);
     end;
    end;
  end; // set field data
end; //SetFieldData


//------------------------------------------------------------------------------
// return true if record buffers are binary identical
//------------------------------------------------------------------------------
function CompareRecordBuffers(RecordBuffer1,RecordBuffer2: TABSRecordBuffer;
                              RecordBufferSize: Integer): Boolean;
var i: Integer;
begin
 if (RecordBuffer1 = nil) then
  raise EABSException.Create(10122,ErrorLNilPointer);
 if (RecordBuffer2 = nil) then
  raise EABSException.Create(10123,ErrorLNilPointer);
 Result := True;
 for i := 0 to RecordBufferSize-1 do
  if (TAbsPByte(RecordBuffer1 + i)^ <> TAbsPByte(RecordBuffer2 + i)^) then
   begin
    Result := False;
    break;
   end;
end; // CompareRecordBuffers



end.
