unit ABSTypes;

interface

{$I ABSVer.inc}

uses
     SysUtils, Classes, Windows,

// AbsoluteDatabase units
     {$IFDEF DEBUG_LOG}
     ABSDebug,
     {$ENDIF}

     ABSMemory,
     ABSConst,
     ABSExcept;


type

//------------------------------------------------------------------------------
// general types
//------------------------------------------------------------------------------

 {$IFDEF D12H}
 TAbsPByte = PByte;
 {$ELSE}
 TAbsPByte = PAnsiChar;
 {$ENDIF}

 {$IFNDEF D16H}
 NativeInt = Integer;
 NativeUInt = Cardinal;
 UInt64 = Int64;
 PNativeUInt = ^Cardinal;
 {$ENDIF}

 {$IFNDEF D17H}
 TValueBuffer = Pointer;
 {$ENDIF}


 TAbsPChar = PAnsiChar;

 TABSErrorCode = Integer;
 TABSPageNo = Integer;
 PABSPageNo = ^TABSPageNo;
 TABSPageBuffer = TAbsPByte;
 TABSGetRecordMode = (grmCurrent, grmNext, grmPrior);
 TABSGetRecordResult = (grrOK, grrBOF, grrEOF, grrError);
 TABSBLOBOpenMode = (bomWrite, bomRead, bomReadWrite);
 TABSRecordBuffer = TAbsPByte;
 TABSState = Integer;
 TABSObjectID = Integer;
 TABSTableID = TABSObjectID;
 TABSObjectName = ShortString;
 TABSSessionID = Integer;
 TABSRecordNo = Int64;
 TABSPageItemID = packed record
  PageNo:        TABSPageNo; // page number or record number (disk engine or memory, temporary engine)
  PageItemNo:    Word;
 end;
 PABSPageItemID = ^TABSPageItemID;
 TABSRecordID = TABSPageItemID;
 PABSRecordID = ^TABSRecordID;

{ Paradox graphic BLOB header }
type
  TGraphicHeader = record
    Count: Word;                { Fixed at 1 }
    HType: Word;                { Fixed at $0100 }
    Size: Longint;              { Size not including header }
  end;

//------------------------------------------------------------------------------
// disk types
//------------------------------------------------------------------------------


 TABSPageRecordCount = Word;
 PABSPageRecordCount = ^TABSPageRecordCount;
 TABSDBFileType = (dbftUnknown,dbftTablesList,dbftActiveSessionsList);
 TABSLockType = (ltIS,ltS,ltSIRW,ltXIRW,ltRW,ltU,ltX); // U - record lock
 const LocksCompatible: array [ltIS..ltX, ltIS..ltX] of Boolean =
       (
       //IS    S      SIRW   XIRW   RW     U      X
{IS}   (True,  True,  True,  True,  True,  True,  False),
{S}    (True,  True,  True,  True,  False, True,  False),
{SIRW} (True,  True,  True,  False, True,  True,  False),
{XIRW} (True,  True,  False, False, False, True,  False),
{RW}   (True,  False, True,  False, False, True,  False),
{U}    (True,  True,  True,  True,  True,  True,  False),
{X}    (False, False, False, False, False, False, False)
       );
// SIRW-SIRW, SIRW-XIRW compatibility have some exceptions from this table

 type
 //TABSLockType = (ltIS = 0,ltS = 1,ltIRW = 2,ltRW = 3,ltU = 4,ltX = 5);
 TABSLockObjectType = (lotDatabase,lotTable,lotRecord);
 TABSTablePageType = (tptRowStart,tptRowContinue,tptVarchar,tptBLOB);
 
 PABSDBHeader = ^TABSDBHeader;
 TABSDBHeader = packed record
  Signature:        Array [0..15] of AnsiChar;
  HeaderSize:       Smallint;
  Version:          Double;
  PageSize:         Word;
  PageCountInExtent:Word;
  TotalPageCount:   TABSPageNo;
  LastUsedPageNo:   TABSPageNo;
  State:            Integer;
  WriteChangesState:Byte;
  Encrypted:        ByteBool;
  Reserved:         array [1..32] of Byte;
 end;

 TABSCryptoHeader = packed record
  CryptoHeaderSize:  Smallint;
  CryptoAlgorithm:   Byte;
  CryptoMode:        Byte;
  ControlBlock:      array [0..ABS_CONTROL_BLOCK_SIZE-1] of Byte;
  ControlBlockCRC:   Cardinal;
  Reserverd:         array [1..16] of Byte;
 end;

 TABSLockedBytes = packed record
  LockedByteSize:   Smallint;
  FSMByte:          Byte;
  TablesByte:       Byte;
  DBHeaderByte:     Byte;
  Reserved:         array [1..15] of Byte;
 end;

 TABSLastObjectID = Integer;

 TABSPageTypeID = Word;
 
 TABSDiskPageHeader = packed record
  Signature:        Array [0..3] of AnsiChar;
  State:            Integer;
  PageType:         TABSPageTypeID;
  NextPageNo:       TABSPageNo;
  CRC32:            Longword;
  CRCType:          Byte;
  HashType:         Byte;
  Cipherype:        Byte;
  MACType:          Byte;
  ObjectID:         TABSObjectID;
  RecordID:         TABSRecordID;
  Reserved:         Array [0..7] of Byte;
 end; // 40 bytes
 PABSDiskPageHeader = ^TABSDiskPageHeader;
 
  // ABSDatabaseFile Mode Types
  TABSShareMode = (smExclusive, smShareDenyNone, smShareDenyWrite);
  TABSAccessMode = (amReadOnly, amReadWrite);
 
 // Any list header
 PABSListHeader = ^TABSListHeader;
 TABSListHeader = packed record
   Count:       Integer;
   ItemSize:    Integer;
   NextPageNo:  TABSPageNo; // or INVALID_PAGE_NO
 end;// 12 byte

 // Header for internal file
 PABSInternalFileHeader = ^TABSInternalFileHeader;
 TABSInternalFileHeader = packed record
   FileHeaderSize:       Byte;
   FileSize:             Integer;
   DecompressedSize:     Integer;
   CompressionAlgorithm: Byte;
 end;

 // System Directory List Item
 TABSSystemDirectoryListItem = packed record
   FileID:      TABSDBFileType;
   FirstPageNo: TABSPageNo;
 end;// 6 byte

 
 // Table List Item
 PABSTableListItem = ^TABSTableListItem;
 TABSTableListItem = packed record
   TableName:                           ShortString;
   TableID:                             TABSTableID;
   MetaDataFilePageNo:                  TABSPageNo;
   MostUpdatedFilePageNo:               TABSPageNo;
   LocksFilePageNo:                     TABSPageNo;
 end;

 TABSTablePFSPageMapItem = packed record
   PageNo:          TABSPageNo;
   PageRecordCount: TABSPageRecordCount;
 end;

 TABSLockParams = record
    SessionID:    Integer;
    LockType:     TABSLockType;
    ObjectType:   TABSLockObjectType;
    RowID:        TABSPageItemID;
    TableID:      TABSObjectID;
 end;

//------------------------------------------------------------------------------
// SQL types
//------------------------------------------------------------------------------


 // join type
 TABSJoinType = (ajtCross, ajtInner, ajtLeftOuter,
                 ajtRightOuter, ajtFullOuter);

 // union type
 TABSUnionType = (autUnion, autIntersect, autExcept);

 // table | joined table | subquery
 TABSTableType = (attTable, attJoinedTable, attSubQuery);

 // table | joined table | subquery
 TABSQueryExprType = (qetSelect, qetUnion, qetExcept, qetIntersect);

 TABSBatchMoveType = (bmtAppend, bmtAppendUpdate, bmtCopy, bmtDelete, bmtUpdate, bmtSynchronize);


 // field types
 TABSBaseFieldType = (
                        bftUnknown,

                        bftChar,
                        bftWideChar,
                        bftVarchar,
                        bftWideVarchar,

                        bftSignedInt8,        // Shortint
                        bftSignedInt16,       // Smallint
                        bftSignedInt32,       // Integer
                        bftSignedInt64,       // Int64
                        bftUnsignedInt8,      // Byte
                        bftUnsignedInt16,     // Word
                        bftUnsignedInt32,     // Cardinal
                        //bftUnSignedInt64,

                        bftSingle,
                        bftDouble,
                        bftExtended,

                        bftDate,
                        bftTime,
                        bftDateTime,

                        bftBlob,
                        bftClob,
                        bftWideClob,

                        bftLogical,
                        bftCurrency,

                        bftBytes,
                        bftVarBytes,
                        //bftTimeStamp, == bftDateTime 
                        bftBFile
                     );


  TABSAdvancedFieldType = (
    aftUnknown,

    aftChar,            // = bftChar
    aftString,          // = bftVarChar

    aftWideChar,        // = bftChar
    aftWideString,      // = bftWideVarChar

    aftShortint,        // = bftSignedInt8
    aftSmallint,        // = bftSignedInt16
    aftInteger,         // = bftSignedInt32
    aftLargeint,        // = bftSignedInt64
    aftByte,            // = bftUnsignedInt8
    aftWord,            // = bftUnsignedInt16
    aftCardinal,        // = bftUnsignedInt32

    aftAutoInc,         // = bftSignedInt32
    aftAutoIncShortint, // = bftSignedInt8
    aftAutoIncSmallint, // = bftSignedInt16
    aftAutoIncInteger,  // = bftSignedInt32
    aftAutoIncLargeint, // = bftSignedInt64
    aftAutoIncByte,     // = bftUnsignedInt8
    aftAutoIncWord,     // = bftUnsignedInt16
    aftAutoIncCardinal, // = bftUnsignedInt32


    aftSingle,          // = bftSingle
    aftDouble,          // = bftDouble
    aftExtended,        // = bftExtended

    aftBoolean,         // = bftLogical

    aftCurrency,        // = bftCurrency

    aftDate,            // = bftDate
    aftTime,            // = bftTime
    aftDateTime,        // = bftDateTime
    aftTimeStamp,       // = bftDateTime

    aftBytes,           // = bftBytes
    aftVarBytes,        // = bftVarBytes

    aftBlob,            // = bftBlob
    aftGraphic,         // = bftBlob
    aftMemo,            // = bftClob
    aftFormattedMemo,   // = bftClob
    aftWideMemo,        // = bftWideClob
    aftGuid             // = bftChar(38)
//aftArray,
//    aftParadoxOle,
//    aftDBaseOle,
//    aftTypedBinary,
//    aftCursor,
//    aftReference,
//    aftDataSet,
//    aftVariant,
//    aftInterface,
//    aftIDispatch,
    );

 //TABSDefaultValueType = (dvtNull, dvtConst, dvtSequence, dvtFunction, dvtQuery);
 TABSConstraintType = (ctPK, ctFK, ctUnique, ctNotNull, ctCheck);
 TABSCompareResult = (cmprEqual, cmprLower, cmprGreater,
                      cmprLeftNull, cmprRightNull, cmprBothNull);

 // unary / binary Value operators
 TABSDataOperator = (
    doUNDEFINED,                       // error
    //George doCMP,                             // compare <0,=0,>0
    {Comparison}
    doEQ,                              // equal
    doNE,                              // NOT equal
    doGT,                              // greater than
    doLT,                              // less than
    doGE,                              // greater or equal
    doLE,                              // less or equal
    {Boolean}
    doNOT,                             // NOT
    doAND,                             // AND
    doOR,                              // OR
    doIN,                              // in (a,b,c, ...)
    doNOTIN,                           // not in (a,b,c, ...)
    doEXISTS,                          // exists (subquery)
    doBETWEEN,                         // between (a,b)
    doNOTBETWEEN,                      // not between (a,b)
    doLIKE,                            // like 'a?b%'
    doNOTLIKE,                         // not like
    doISNULL,                          // is null
    doISNOTNULL,                       // is not null
    doTRUE,                            // TRUE const
    doFALSE,                           // FALSE const
    {Arithmetic}
    doADD,                             // addition
    doSUB,                             // subtraction
    doMUL,                             // multiplication
    doDIV,                             // division
    {Functions}
    //doPOSITION,                        // Position (str1) in (str2)
    doCONCAT,                          // str1 || str2
    doUPPER,                           // Upper(str)
    doLOWER,                           // Lower(str)
    doTRIM,                            // TRIM
    doLTRIM,                           // LTRIM
    doRTRIM,                           // RTRIM
    doLENGTH,                          // LENGTH(str)
    doPOS,                             // POS(substr in|, str) { 1 - first char, 0 - not found }
    doSUBSTRING,                       // SUBSTRING(str from|, startindex [for|, length])
    {aggregated functions}
    doSUM,                             // SUM([distinct]expression)
    doAVG,                             // AVG([distinct]expression)
    doMIN,                             // min(expression)
    doMAX,                             // max(expression)
    doCOUNT,                           // COUNT([distinct]expression)
    doCOUNTALL,                        // count(*)
    {datetime functions}
    doSYSDATE,                         // SYSDATE - return current DateTime
    doCURRENT_DATE,                    // CURRENT_DATE
    doCURRENT_TIME,                    // CURRENT_TIME
    doTODATE,                          // TODATE(string, format)
    doTOSTRING,                        // TOSTRING(date, format)
    doEXTRACT,                         // EXTRACT (YEAR FROM datetime_field)
    {cast types}
    doCAST,                            // Cast(expression, type)
    {system}
    doLASTAUTOINC,
    {blob}
    doMIMETOBIN,                       // MimeToBin(MimeString)

    doROWNUM,                          // selected row number (iterated)
    {math}
    doABS,                             // ABS(number)
    doACOS,
    doASIN,
    doATAN,
    doCEIL,
    doCOS,
    doEXP,
    doFLOOR,
    doLOG,
    doPOWER,
    doRAND,
    doROUND,
    doSIGN,
    doSIN,
    doSQR,
    doSQRT
 );

//------------------------------------------------------------------------------
// record types
//------------------------------------------------------------------------------

 // used in ABSMemEngine for saving / loading table to stream
 TABSMemoryTableFileHeader = packed record
  Signature:            array [0..3] of AnsiChar ;
  Version:              Double;
  RecordCount:          TABSRecordNo;
  // compression params
  UncompressedSize:     Int64;
  NumBlocks:            Int64;
  BlockSize:            Integer;
  CompressionAlgorithm: Byte;
  CompressionMode:      Byte;
  NameLength:           Byte;
 end; // 43 bytes

 TABSIndexPosition = TObject;
 TABSSearchInfo = Pointer;

  // bookmark information type
  TABSBookmarkFlag = (abfCurrent, abfBOF, abfEOF, abfInserted);
  TABSBookmarkInfo = packed record
   BookmarkData: TABSRecordID;      // record ID
   BookmarkFlag: TABSBookmarkFlag;  // bookmark flag
  end;
  PABSBookmarkInfo = ^TABSBookmarkInfo;

 TABSSequenceValue = Int64;

 TABSTimeoutParams = record
  RetryCount:   Word;
  Timeout:      Integer;
 end;

 //------------------------- Search types --------------------------------------
  TABSKeyIndex = (kiLookup, kiRangeStart, kiRangeEnd, kiCurRangeStart,
    kiCurRangeEnd, kiSave);
  TABSSearchCondition = (scNone,scEqual,scLower,scGreater,scLowerEqual,scGreaterEqual);
  TABSKeySearchType = (kstFirst,kstLast,kstAny);

  PABSKeyBuffer = ^TABSKeyBuffer;
  TABSKeyBuffer = packed record
    Modified: Boolean;
    Exclusive: Boolean;
    FieldCount: Integer;
  end;

  TABSNavigationInfo = packed record
    // in
    GetRecordMode:    TABSGetRecordMode;
    IndexID:          TABSObjectID; // current index id (INVALID_OBJECT_ID if no active index)
    // in and out
    RecordID:         TABSRecordID;
    RecordBuffer:     TABSRecordBuffer;
    FirstPosition:    Boolean;
    LastPosition:     Boolean;
    // out
    GetRecordResult:  TABSGetRecordResult;
  end;

  TABSDateTimeExtractField = (dtefYear, dtefMonth, dtefDay, dtefHour, dtefMinute, dtefSecond);

////////////////////////////////////////////////////////////////////////////////
//
// TABSIndexPositionCache
//
////////////////////////////////////////////////////////////////////////////////

  TABSIndexPositionCache = class(TObject)
   public
    Position:   Pointer;      // last used position in index
    IndexID:    TABSObjectID; // last used index
    RecordID:   TABSRecordID; // last used RecordID
    TableState: Integer;      // table state actual on caching moment

    constructor Create;
    destructor Destroy; override;
  end;// TABSIndexPositionCache


////////////////////////////////////////////////////////////////////////////////
//
// TABSScanSearchCondition
//
////////////////////////////////////////////////////////////////////////////////

  TABSScanSearchCondition = class(TObject)
   public
    Expression:                Pointer; // if nil then KeyBuffer will be used
    // index scan key info
    Condition:                 TABSSearchCondition;
    KeyRecordBuffer:           TABSRecordBuffer; // if nil then Expression will be used
    KeyRecordBufferSize:       Integer;
    KeyFieldCount:             Integer;
    IndexID:                   TABSObjectID;
    CreatedFromExpression:     Boolean;
    ParentExpressions:         TList;
    ParentExpressionNodes:     TList;
    FExternalKeyRecordBuffer:  Boolean;
    PartialCompare:            Boolean;

    procedure ClearKeyRecordBuffer;

    constructor Create(ExternalKeyRecordBuffer: Boolean);
    destructor Destroy; override;
    procedure Assign(ScanSearchCondition: TABSScanSearchCondition);
  end;

  TABSExtractedConditionInfo = packed record
    KeyRecordBuffer:           TABSRecordBuffer;
    ExtractedExpressionNode:   Pointer;
    Expression:                Pointer; // if nil then KeyBuffer will be used
  end;
  PABSExtractedConditionInfo = ^TABSExtractedConditionInfo;

////////////////////////////////////////////////////////////////////////////////
//
// TABSScanSearchConditionArray
//
////////////////////////////////////////////////////////////////////////////////

  TABSScanSearchConditionArray = class(TObject)
   public
    Items:    array of TABSScanSearchCondition;
    Count:    Integer;

    constructor Create;
    destructor Destroy; override;
    procedure Delete(ItemNo: Integer);
    procedure AddExpression(Expression: Pointer);
    procedure AddCondition(ScanSearchCondition: TABSScanSearchCondition);
   private
    procedure TryToExtendMultiFieldIndexCondition(ConditionNo: Integer; IndexDef: Pointer; Expressions: TList);
   public
    procedure CreateIndexScanConditionsFromExpressions(IndexDefs: Pointer; SessionID: TABSSessionID);
    procedure RemoveUnusedConditionsCreatedFromExpressions(var ScanConditionNo, ScanEndConditionNo: Integer);
    procedure ExtractChosenConditionsFromExpressions(ScanConditionNo, ScanEndConditionNo: Integer);
    procedure ReturnExtractedConditionsToExpressions(ScanConditionNo, ScanEndConditionNo: Integer);
  end;

 // ORDER BY clause element
 TABSSortSpecification = record
   TableName: AnsiString;   // table or its pseudonym
   ColumnName: AnsiString;  // field name or pseudonym
   Descending: Boolean; // ASC | DESC
 end;
  

//----------------------- Field Types ------------------------------------------

 PABSDate = ^TABSDate;
 TABSDate = Integer;  // Number of days from Christmas ( -5 883 516 .. 5 883 516 )

 PABSTime = ^TABSTime;
 TABSTime = Integer; // Number of milliseconds from day begin ( 0 .. 24*60*60*1000 )

 PABSDateTime = ^TABSDateTime;
 TABSDateTime = packed record  // Total 8 byte
   Date: TABSDate;  // Number of days from Christmas ( -5 883 516 .. 5 883 516 )
   Time: TABSTime; // Number of milliseconds from day begin ( 0 .. 24*60*60*1000 )
 end;//TABSDateTime                                                = 86 400 000

 PABSLogical = ^TABSLogical;
 //TABSLogical = ByteBool;
 TABSLogical = WordBool; //TDataset using WordBool

 PABSCurrency = ^TABSCurrency;
// TABSCurrency = Currency;
 TABSCurrency = Double;  // Overflow fix

 PABSGuid = ^TABSGuid;
 TABSGuid = TGuid;

//------------------------------------------------------------------------------
// Delphi 4,5 types
//------------------------------------------------------------------------------

{$IFNDEF D6H}
 PWord = ^Word;
 PInteger = ^Integer;
 PByte = ^Byte;
 PCardinal = ^Cardinal;
{$ENDIF}

TABSIntegerArray = class;

TABSPagesArray = TABSIntegerArray;


////////////////////////////////////////////////////////////////////////////////
//
// TABSIntegerArray
//
////////////////////////////////////////////////////////////////////////////////

 TABSIntegerArray = class(TObject)
   public
     Items:          array of Integer;
     ItemCount:      Integer;
     AllocBy:        Integer;
     deAllocBy:      Integer;
     MaxAllocBy:     Integer;
     AllocItemCount: Integer;

     constructor Create(
      size: Integer = 0;
      DefaultAllocBy: Integer = 10;
      MaximumAllocBy: Integer = 100
     );
     destructor Destroy; override;
     procedure SetSize(newSize: Integer);
     procedure Assign(v: TABSIntegerArray);
     procedure Append(value: Integer);
     procedure Insert(ItemNo: Integer; value: Integer);
     procedure Delete(ItemNo: Integer);
     procedure MoveTo(itemNo, newItemNo: Integer);
     procedure CopyTo(
                      var ar: array of Integer;
                      itemNo, iCount: Integer
                      );
     function IsValueExists(value: Integer): Boolean;
     function IndexOf(value: Integer): Integer;
 end; // TABSIntegerArray


////////////////////////////////////////////////////////////////////////////////
//
// TABSPtrArray
//
////////////////////////////////////////////////////////////////////////////////
(*
 TABSPtrArray = class(TObject)
   private
    FItems:           array of Pointer;
    FSizes:           array of Integer;
    FOverallSize:     Integer;
    FItemCount:       Integer;
    FAllocBy:         Integer;
    FDeAllocBy:       Integer;
    FMaxAllocBy:      Integer;
    FAllocItemCount:  Integer;
    FMemoryManager:   TABSMemoryManager;
   public
//     constructor Create(
//                        Size, MaxAllocBy, AllocBy, DeAllocaBy: Integer = 0;
//                        MemoryManager: TABSMemoryManager = nil
//                         );
//     destructor Destroy; override;
     procedure SetSize(NewSize: Integer); virtual; abstract;
     procedure Append(
                      Buffer:     Pointer;
                      BufferSize: Integer;
                      CopyFlag:   Boolean = False // to copy buffer contents or not
                      ); virtual; abstract;
 end; // TABSPtrArray
*)



 TABSSortedStringPtrArray = class(TList);



////////////////////////////////////////////////////////////////////////////////
//
// Compressed Stream Block Headers Array and related stuff
//
////////////////////////////////////////////////////////////////////////////////

 TABSBLOBDescriptor = packed record
  BlockSize:             Integer; // block size
  NumBlocks:             Int64;  // number of blocks
  UncompressedSize:      Int64; // size of uncompressed data
  StartPosition:         Int64; // position in stream where first block header is stored
  CompressionAlgorithm:  Byte; // compression algorithm
  CompressionMode:       Byte; // compression mode (0-9)
  Reserved:              array [0..1] of Byte;
 end; // TABSBLOBDescriptor - 32 bytes
 PABSBLOBDescriptor = ^TABSBLOBDescriptor;

 // blob descriptor that stores for each blob value
 TABSPartialBLOBDescriptor = packed record
  NumBlocks:             Integer; // number of blocks
  UncompressedSize:      Int64; // size of uncompressed data
 end; // TABSPartialBLOBDescriptor - 8 bytes
 PABSPartialBLOBDescriptor = ^TABSPartialBLOBDescriptor;

 // blob descriptor that stores for each blob value
 TABSPartialTemporaryBLOBDescriptor = packed record
  NumBlocks:             Integer; // number of blocks
  UncompressedSize:      Int64; // size of uncompressed data
  CompressedSize:        Int64; // size of compressed data
 end; // TABSPartialTemporaryBLOBDescriptor - 8 bytes
 PABSPartialTemporaryBLOBDescriptor = ^TABSPartialTemporaryBLOBDescriptor;

 TABSCompressedStreamBlockHeader = packed record
       CompressedSize:      Integer; // packed block size
       UncompressedSize:    Integer; // unpacked block size
       Crc32:               Cardinal; // check sum for this block page
       OffsetToNextHeader:  Int64;  // offset from beginning of the compressed stream
                                    // to next block header
 end; // 20

 TABSCompressedStreamBlockHeadersArray = class
    private
     AllocBy:             Int64;
     DeAllocBy:           Int64;
     MaxAllocBy:          Int64;
     AllocItemCount:      Int64;
    public
     Items:               array of TABSCompressedStreamBlockHeader;
     Positions:           array of Int64; // block positions
     ItemCount:           Int64; // all files quantity (including deleted files)
    public
     constructor Create;
     destructor Destroy; override;
     procedure SetSize(NewSize: Int64);
     procedure AppendItem(Value: TABSCompressedStreamBlockHeader; Pos: Int64);
     function FindPosition(Pos: Int64) : Integer;
   end; // TABSCompressedStreamBlockHeadersArray


{
const
 // size in bytes of the compressed stream header (16)
 ABSCompressedStreamBlockHeaderSize = sizeof(TABSCompressedStreamHeader);
 // size in bytes of the compressed stream block header (16)
 ABSCompressedStreamBlockHeaderSize = sizeof(TABSCompressedStreamBlockHeader);
}


 TABSDiskBLOBCache = packed record
  NumBlocks:             Integer; // number of blocks
  UncompressedSize:      Int64; // size of uncompressed data
  Modified:              Boolean;
  BlobData:              TAbsPByte;
 end;// TABSDiskBLOBCache
 PABSDiskBLOBCache = ^TABSDiskBLOBCache;
 PPABSDiskBLOBCache = ^PABSDiskBLOBCache;

 TABSDiskBLOBHeader = packed record
  BlobID:                Word;
  NumBlocks:             Integer; // number of blocks
  CompressedSize:        Int64; // size of compressed data
  UncompressedSize:      Int64; // size of uncompressed data
 end;// TABSDiskBLOBHeader
 PABSDiskBLOBHeader = ^TABSDiskBLOBHeader;


 TABSPageListLink = packed record
  StartPageNo: TABSPageNo;
  StartOffset: Word;
  ItemCount: TABSPageNo;
 end;


////////////////////////////////////////////////////////////////////////////////
//
// TABSBitsArray
//
////////////////////////////////////////////////////////////////////////////////


  TABSBitsArray = class (TObject)
   private
    FBits:              TAbsPByte;
    FBitCount:          Integer; // total number of bits
    FNonZeroBitCount:   Integer; // number of bits equal to 1
    FBitsTable:         array [0..255] of byte;
   protected
    function GetNonZeroBitCount: Integer;
    procedure SetSize(NewSize: Integer);
   public
    procedure LoadFromStream(Stream: TStream);
    procedure SaveToStream(Stream: TStream);
    constructor Create;
    destructor Destroy; override;
    function GetBit(BitNo: Integer): Boolean;
    procedure SetBit(BitNo: Integer; Value: Boolean);
    // returns number of bit = 1 in FBits array by bit position
    function GetBitNoByBitPosition(BitPosition: Integer): Integer;
    // returns position of bit = 1 by bit no in FBits array
    function GetBitPositionByBitNo(BitNo: Integer): Integer;
    procedure SetAllBits;
    function FindBit(Value: Boolean; var BitNo: Integer): Boolean;
    function Find(Restart,GoForward: Boolean; CurBitNo: Integer; var FoundBitNo: Integer): Boolean;
   public
    property Size: Integer read FBitCount write SetSize;
    property NonZeroBitCount: Integer read FNonZeroBitCount;
  end;


////////////////////////////////////////////////////////////////////////////////
//
// Bits functions
//
////////////////////////////////////////////////////////////////////////////////


  // return true if null flag is set (bit = 1)
  function CheckNullFlag(
          BitNo:       Integer; // number of bit to check
          NullFlags:   TAbsPByte // pointer to bits of null flags
                      ): Boolean;

  // set or clear a null flag
  procedure SetNullFlag(
          ToSet:       Boolean; // if true - set bit = 1, otherwise set bit = 0
          BitNo:       Integer; // number of bit to check
          NullFlags:   TAbsPByte // pointer to bits of null flags
                     );


////////////////////////////////////////////////////////////////////////////////
//
// Other functions
//
////////////////////////////////////////////////////////////////////////////////


  // returns temporary name
  function GetTemporaryName(Prefix: string): string;

  function BracketFieldName(name: AnsiString): AnsiString;

implementation

uses Math, ABSExpressions, ABSCompression, ABSBTree, ABSBase;


////////////////////////////////////////////////////////////////////////////////
//
// TABSIndexPositionCache
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Create
//------------------------------------------------------------------------------
constructor TABSIndexPositionCache.Create;
begin
  Position := TABSKeyPath.Create;
  IndexID := INVALID_OBJECT_ID;
  TableState := -1;
end;// Create


//------------------------------------------------------------------------------
// Destroy
//------------------------------------------------------------------------------
destructor TABSIndexPositionCache.Destroy;
begin
  TABSKeyPath(Position).Free;
end;// Destroy




////////////////////////////////////////////////////////////////////////////////
//
// TABSScanSearchCondition
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// ClearKeyRecordBuffer
//------------------------------------------------------------------------------
procedure TABSScanSearchCondition.ClearKeyRecordBuffer;
begin
  if (not FExternalKeyRecordBuffer) then
    if (KeyRecordBuffer <> nil) then
      MemoryManager.FreeAndNillMem(KeyRecordBuffer);
end;// ClearKeyRecordBuffer


//------------------------------------------------------------------------------
// Create
//------------------------------------------------------------------------------
constructor TABSScanSearchCondition.Create(ExternalKeyRecordBuffer: Boolean);
begin
  FExternalKeyRecordBuffer := ExternalKeyRecordBuffer;
  KeyRecordBuffer := nil;
  KeyRecordBufferSize := 0;
  IndexID := INVALID_OBJECT_ID;
  Expression := nil;
  CreatedFromExpression := False;
  ParentExpressions := TList.Create;
  ParentExpressionNodes := TList.Create;
  PartialCompare := False;
end;// Create


//------------------------------------------------------------------------------
// Destroy
//------------------------------------------------------------------------------
destructor TABSScanSearchCondition.Destroy;
begin
  ClearKeyRecordBuffer;
  ParentExpressions.Free;
  ParentExpressionNodes.Free;
  inherited;
end;// Destroy


//------------------------------------------------------------------------------
// Assign
//------------------------------------------------------------------------------
procedure TABSScanSearchCondition.Assign(ScanSearchCondition: TABSScanSearchCondition);
var
  i: Integer;
begin
  Condition := ScanSearchCondition.Condition;
  ClearKeyRecordBuffer;
  if (ScanSearchCondition.KeyRecordBuffer <> nil) then
    begin
      KeyRecordBuffer := MemoryManager.AllocMem(ScanSearchCondition.KeyRecordBufferSize);
      Move(ScanSearchCondition.KeyRecordBuffer^, KeyRecordBuffer^, ScanSearchCondition.KeyRecordBufferSize);
    end
  else
    KeyRecordBuffer := nil;
  FExternalKeyRecordBuffer := False;
  KeyRecordBufferSize := ScanSearchCondition.KeyRecordBufferSize;
  KeyFieldCount := ScanSearchCondition.KeyFieldCount;
  IndexID := ScanSearchCondition.IndexID;
  Expression := ScanSearchCondition.Expression;
  CreatedFromExpression := ScanSearchCondition.CreatedFromExpression;
  PartialCompare := ScanSearchCondition.PartialCompare;

  // assign
  ParentExpressions.Clear;
  for i:=0 to ScanSearchCondition.ParentExpressions.Count-1 do
    ParentExpressions.Add(ScanSearchCondition.ParentExpressions[i]);
  // assign
  ParentExpressionNodes.Clear;
  for i:=0 to ScanSearchCondition.ParentExpressionNodes.Count-1 do
    ParentExpressionNodes.Add(ScanSearchCondition.ParentExpressionNodes[i]);
end;// Assign



////////////////////////////////////////////////////////////////////////////////
//
// TABSScanSearchConditionArray
//
////////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
// Create
//------------------------------------------------------------------------------
constructor TABSScanSearchConditionArray.Create;
begin
  Items := nil;
  Count := 0;
end;// Create


//------------------------------------------------------------------------------
// Destroy
//------------------------------------------------------------------------------
destructor TABSScanSearchConditionArray.Destroy;
var
  i: Integer;
begin
  for i := 0 to Count-1 do
    Items[i].Free;
  SetLength(Items, 0);
end;// Destroy


//------------------------------------------------------------------------------
// Delete
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.Delete(ItemNo: Integer);
begin
 if (ItemNo < 0) or (ItemNo >= Count) then
  raise EABSException.Create(10413,ErrorLInvalidItemNumber);
 if (Items[ItemNo] = nil) then
  raise EABSException.Create(10414,ErrorLNilPointer);
 Items[ItemNo].Free;
 if (ItemNo < Count - 1) then
  Move(Items[ItemNo+ 1 ],Items[ItemNo],
      (Count - ItemNo-1) * SizeOf(TABSScanSearchCondition));
 Dec(Count);
 SetLength(Items,Count);
end; // Delete


//------------------------------------------------------------------------------
// AddExpression
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.AddExpression(Expression: Pointer);
begin
  SetLength(Items, Count+1);
  Items[Count] := TABSScanSearchCondition.Create(False);
  Items[Count].Expression := Expression;
  Items[Count].Condition := scNone;
  Items[Count].KeyRecordBuffer := nil;
  Items[Count].KeyFieldCount := 0;
  Items[Count].IndexID := INVALID_OBJECT_ID;
  Inc(Count);
end;// AddExpression


//------------------------------------------------------------------------------
// AddCondition
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.AddCondition(
                  ScanSearchCondition:  TABSScanSearchCondition
                             );
begin
  if ((ScanSearchCondition.Expression = nil) and
      (ScanSearchCondition.IndexID = INVALID_ID4)) then
   raise EABSException.Create(10388,ErrorLInvalidScanConditionNoIndex,
      [ScanSearchCondition.IndexID,Count]);
  SetLength(Items, Count+1);
  Items[Count] := TABSScanSearchCondition.Create(False);
  Items[Count].Assign(ScanSearchCondition);
  Inc(Count);
end;// AddCondition


//------------------------------------------------------------------------------
// TryToExtendMultiFieldIndexCondition
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.TryToExtendMultiFieldIndexCondition(
              ConditionNo: Integer; IndexDef: Pointer; Expressions: TList);
var
  bExtendSucceeded: Boolean;
  i: integer;
begin
  if (TABSIndexDef(IndexDef).ColumnCount > 1) then
    begin
      bExtendSucceeded := False;
      repeat
        for i := 0 to Expressions.Count-1 do
          begin
            bExtendSucceeded := TABSExpression(Expressions[i]).ExtendMultiFieldIndexCondition(Items[ConditionNo], IndexDef);
            if (bExtendSucceeded) then
              break;
          end;
      until (not bExtendSucceeded);
    end;
end;// TryToExtendMultiFieldIndexCondition


//------------------------------------------------------------------------------
// CreateIndexScanConditionsFromExpressions
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.CreateIndexScanConditionsFromExpressions(IndexDefs: Pointer; SessionID: TABSSessionID);
var
  i: Integer;
  Expressions: TList;
begin
  // create all possible single-field index scan conditions
  for i := 0 to Count-1 do
    if (Items[i].Expression <> nil) then
      TABSExpression(Items[i].Expression).CreateIndexScanConditions(IndexDefs, Self, SessionID);

  Expressions := TList.Create;
  try
    // collect all expressions
    for i := 0 to Count-1 do
      if (Items[i].Expression <> nil) then
        Expressions.Add(Items[i].Expression);

    if (Expressions.Count > 0) then
      // try to extend multi-field index conditions
      for i := 0 to Count-1 do
        if (Items[i].Expression = nil) then
          TryToExtendMultiFieldIndexCondition(i, TABSIndexDefs(IndexDefs).GetDefByObjectId(Items[i].IndexID), Expressions);
  finally
    Expressions.Free;
  end;
end;// CreateIndexScanConditionsFromExpressions


//------------------------------------------------------------------------------
// RemoveUnusedConditionsCreatedFromExpressions
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.RemoveUnusedConditionsCreatedFromExpressions(
                               var ScanConditionNo,ScanEndConditionNo: Integer);
var
  i: integer;
begin
  i := 0;
  while (i < Count-1) do
    begin
      if not (i in [ScanConditionNo, ScanEndConditionNo]) then
        if (Items[i].CreatedFromExpression) then
          begin
            Delete(i);
            if (ScanConditionNo > i) then
              Dec(ScanConditionNo);
            if (ScanEndConditionNo > i) then
              Dec(ScanEndConditionNo);
            i := 0;
            continue;
          end;
      Inc(i);
    end;
end;// RemoveUnusedConditionsCreatedFromExpressions


//------------------------------------------------------------------------------
// ExtractChosenConditionsFromExpressions
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.ExtractChosenConditionsFromExpressions(ScanConditionNo, ScanEndConditionNo: Integer);
var
  i: Integer;
begin
  for i := 0 to Count-1 do
    if (Items[i].Expression <> nil) then
      TABSExpression(Items[i].Expression).RemoveExtractedNodes(Self,ScanConditionNo,ScanEndConditionNo);
end;// ExtractChosenConditionsFromExpressions


//------------------------------------------------------------------------------
// ReturnExtractedConditionsToExpressions
//------------------------------------------------------------------------------
procedure TABSScanSearchConditionArray.ReturnExtractedConditionsToExpressions(ScanConditionNo, ScanEndConditionNo: Integer);
var
  i,j: Integer;
begin
  for i := 0 to Count-1 do
    if (i in [ScanConditionNo, ScanEndConditionNo]) then
      for j := 0 to Items[i].ParentExpressions.Count-1 do
        TABSExpression(Items[i].ParentExpressions[j]).AddNode(Items[i].ParentExpressionNodes[j]);
end;// ReturnExtractedConditionsToExpressions



////////////////////////////////////////////////////////////////////////////////
//
// TABSIntegerArray
//
////////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
// Construct array of specified size
//------------------------------------------------------------------------------
constructor TABSIntegerArray.Create(
  size: Integer;
  DefaultAllocBy: Integer;
  MaximumAllocBy: Integer
  );
begin
 AllocBy := DefaultAllocBy; // default alloc
 deAllocBy := DefaultAllocBy; // default dealloc
 MaxAllocBy := MaximumAllocBy; // max alloc
 AllocItemCount := 0;
 SetSize(size);
end;//TABSIntegerArray.Create


//------------------------------------------------------------------------------
// Destruct array (free mem)
//------------------------------------------------------------------------------
destructor TABSIntegerArray.Destroy;
begin
 Items := nil;
 inherited Destroy;
end;//TABSIntegerArray.Destroy;


//------------------------------------------------------------------------------
// Set length of array to specified size
//------------------------------------------------------------------------------
procedure TABSIntegerArray.Assign(v: TABSIntegerArray);
var
  i: Integer;
begin
  SetSize(v.ItemCount);
  for i := 0 to ItemCount-1 do
    items[i] := v.items[i];
end;// Assign


//------------------------------------------------------------------------------
// Set length of array to specified size
//------------------------------------------------------------------------------
procedure TABSIntegerArray.SetSize(newSize: Integer);
begin
 if (newSize = 0) then
  begin
   ItemCount := 0;
   allocItemCount := 0;
   Items := nil;
   Exit;
  end;

 if (newSize > allocItemCount) then
  begin
     AllocBy := AllocBy * 2;
     if (AllocBy > MaxAllocBy) then
      AllocBy := MaxAllocBy;
     if (allocItemCount + AllocBy > newSize) then
      allocItemCount := allocItemCount + AllocBy
     else
      allocItemCount := newSize;
     SetLength(Items,allocItemCount);
  end
 else
  if (newSize < ItemCount) then
   if (allocItemCount-newSize > deAllocBy) then
    begin
     deAllocBy := deAllocBy * 2;
     if (deAllocBy > MaxAllocBy) then
      deAllocBy := MaxAllocBy;
     SetLength(Items,newSize);
     allocItemCount := newSize;
    end;

 ItemCount := newSize;
end;//TABSIntegerArray.SetSize


//------------------------------------------------------------------------------
// inserts an element to the end of items array
//------------------------------------------------------------------------------
procedure TABSIntegerArray.Append(value: Integer);
begin
 SetSize(itemCount + 1);
 Items[itemCount-1] := value;
end;//TABSIntegerArray.Append


//------------------------------------------------------------------------------
// Insert an element into specified position
//------------------------------------------------------------------------------
procedure TABSIntegerArray.Insert(itemNo: Integer; value: Integer);
begin
 inc(ItemCount);
 SetSize(ItemCount);
 if (itemCount <= 1) then
  items[0] := value
 else
 if (itemNo >= itemCount-1)
  then
   items[itemCount-1] := value
  else
   begin
    Move(items[itemNo],items[itemNo+1],
        (itemCount - itemNo-1) * sizeOf(Integer));
    items[itemNo] := value;
   end;
end;//TABSIntegerArray.Insert


//------------------------------------------------------------------------------
// Delete an element at specified position
//------------------------------------------------------------------------------
procedure TABSIntegerArray.Delete(itemNo: Integer);
begin
 if (itemNo < itemCount-1) then
  Move(items[itemNo+1],items[itemNo],
      (itemCount - itemNo-1) * sizeOf(Integer));
 dec(ItemCount);
 SetSize(ItemCount);
end;//TABSIntegerArray.Delete


//------------------------------------------------------------------------------
// moves element to new position
//------------------------------------------------------------------------------
procedure TABSIntegerArray.MoveTo(itemNo, newItemNo: Integer);
var value : Integer;
begin
 if (itemNo = newItemNo) then
  Exit;
 if (itemNo - newItemNo = 1) or (newItemNo-itemNo = 1) then
  begin
   value := items[itemNo];
   items[itemNo] := items[newItemNo];
   items[newItemNo] := value;
   Exit;
  end;
 if (itemNo > newItemNo) then
  begin
   value := items[itemNo];
   Move(TAbsPByte(items[newItemNo])^,TAbsPByte(items[newItemNo+1])^,
        (itemNo-newItemNo) * sizeof(Integer));
   items[newItemNo] := value;
  end
 else
  begin
     value := items[ItemNo];
     Move(TAbsPByte(items[ItemNo+1])^,TAbsPByte(items[ItemNo])^,
        (newItemNo-ItemNo-1) * sizeof(Integer));
     items[newItemNo-1] := value;
  end;
end;// MoveTo(itemNo, newItemNo : Integer);


//------------------------------------------------------------------------------
// copies itemCount elements to ar from ItmeNo
//------------------------------------------------------------------------------
procedure TABSIntegerArray.CopyTo(
                      var ar: array of Integer;
                      itemNo, iCount: Integer
                             );
begin
 if (itemCount > 0) then
  Move (TAbsPByte(items[itemNo])^,TAbsPByte(ar[0])^,sizeOf(Integer)*iCount);
end;// CopyTo(ar : array of Integer; itemNo,itemCount : Integer);


//------------------------------------------------------------------------------
// returns true if value exists in Items array
//------------------------------------------------------------------------------
function TABSIntegerArray.IsValueExists(value: Integer): Boolean;
var i: Integer;
begin
 Result := false;
 for i := 0 to ItemCount-1 do
  if Items[i] = value then
   begin
    Result := true;
    break;
   end;
end; // IsValueExists

function TABSIntegerArray.IndexOf(value: Integer): Integer;
var i: Integer;
begin
 Result := -1;
 for i := 0 to ItemCount-1 do
  if Items[i] = value then
   begin
    Result := i;
    break;
   end;
end;



////////////////////////////////////////////////////////////////////////////////
//
// TABSCompressedStreamBlockHeadersArray
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Construct array of specified size
//------------------------------------------------------------------------------
constructor TABSCompressedStreamBlockHeadersArray.Create;
begin
 AllocBy := 10; // default alloc
 DeAllocBy := 10; // default alloc
 MaxAllocBy := 10000; // max alloc
 AllocItemCount := 0;
 ItemCount := 0;
 SetSize(0);
end; // Create


//------------------------------------------------------------------------------
// Destruct array (free mem)
//------------------------------------------------------------------------------
destructor TABSCompressedStreamBlockHeadersArray.Destroy;
begin
 SetSize(0);
 inherited Destroy;
end;//Destroy;


//------------------------------------------------------------------------------
// Set length of array to specified size
//------------------------------------------------------------------------------
procedure TABSCompressedStreamBlockHeadersArray.SetSize(NewSize: Int64);
begin
 if (NewSize = 0) then
  begin
   ItemCount := 0;
   allocItemCount := 0;
   Items := nil;
   Positions := nil;
   Exit;
  end;

 if (NewSize > allocItemCount) then
  begin
     AllocBy := AllocBy * 2;
     if (AllocBy > MaxAllocBy) then
      AllocBy := MaxAllocBy;
     if (allocItemCount + AllocBy > NewSize) then
      allocItemCount := allocItemCount + AllocBy
     else
      allocItemCount := NewSize;
     SetLength(Items,allocItemCount);
     SetLength(Positions,allocItemCount);
  end
 else
  if (NewSize < ItemCount) then
   if (allocItemCount-NewSize > deAllocBy) then
    begin
     deAllocBy := deAllocBy * 2;
     if (deAllocBy > MaxAllocBy) then
      deAllocBy := MaxAllocBy;
     SetLength(Items,NewSize);
     SetLength(Positions,NewSize);
     allocItemCount := NewSize;
    end;
 ItemCount := NewSize;
end;// SetSize


//------------------------------------------------------------------------------
// Finds block containing specified position in user data
//------------------------------------------------------------------------------
function TABSCompressedStreamBlockHeadersArray.FindPosition(Pos: Int64) : Integer;
var i,dx,f,
    oldRes,res: Int64;

 function Compare: Integer;
 begin
  //---------------------------- start of compare -----------------------------------
       // by parent
       if (Positions[i] = pos) then
        Result := 0
       else
        if (Positions[i] < pos) then
         Result := 1
        else
         Result := -1;
  //---------------------------- end of compare -----------------------------------
 end;

begin

 i := ItemCount shr 1;
 dx := i;
 Result := 0;
 if (ItemCount <= 0) then
  begin
   Result := 0;
   Exit;
  end;
  f := 0;
  res := 2;
  while (true) do
   begin
    dx := dx shr 1;
    if (dx < 1) then dx := 1;
     oldRes := res;
     // compare, ascending
     res := Compare;
    if (res < 0) then
     begin
      //  element, specified by value should be higher then current element (+->0)
      i := i - dx;
     end
    else
    if (res > 0) then
     begin
      //  element, specified by value should be lower then current element (+->0)
      i := i + dx;
     end
    else
     begin
      // values are equal
      Result := i;
      break;
     end;
    if  (i < 0) and (dx = 1) then
     begin
      // equal not found
      Result := 0;
      break;
     end;
    if  (i > ItemCount-1) and (dx = 1) then
     begin
      // equal not found
      Result := ItemCount;
      break;
     end;

    if  (i > ItemCount-1) then
     i := ItemCount-1;
    if  i < 0 then
     i := 0;

    if (dx = 1) and (f > 1) then
     begin
      // dx minimum
      // compare, ascending
      res := Compare;
      if (res < 0) and (oldRes > 0) then
       Result := i;
      if (res > 0) and (oldRes < 0) then
       Result := i+1;
      if (res = oldRes) then
       continue;
      break;
     end;// last step
    if (res <> oldRes) and (dx = 1) and (oldRes <> 2) then
     inc(f);
  end;//while dx
 if (Result >= ItemCount) then
     Result := ItemCount-1;
 if (Result > 0) then
  if (Positions[Result] > pos) then
   dec(Result);
 if (Result < 0) then
   Result := 0;
end; //FindPosition


//------------------------------------------------------------------------------
// Insert an element into specified position
//------------------------------------------------------------------------------
procedure TABSCompressedStreamBlockHeadersArray.AppendItem(Value: TABSCompressedStreamBlockHeader; Pos: Int64);
begin
 Inc(ItemCount);
 SetSize(ItemCount);
 Items[ItemCount-1] := value;
 Positions[ItemCount-1] := pos;
end; // AppendItem


////////////////////////////////////////////////////////////////////////////////
//
// TABSBitsArray
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// return number of bits = 1
//------------------------------------------------------------------------------
function TABSBitsArray.GetNonZeroBitCount: Integer;
var i: Integer;
begin
 Result := 0;
 if (FBitCount > 0) then
  for i := 0 to FBitCount - 1 do
   if (GetBit(i)) then
    Inc(Result);
end; // GetNonZeroBitCount


//------------------------------------------------------------------------------
// set new size
//------------------------------------------------------------------------------
procedure TABSBitsArray.SetSize(NewSize: Integer);
var
  SizeInBytes: Integer;
begin
  SizeInBytes := (NewSize div 8) + Integer((NewSize mod 8) > 0);
  if (NewSize = 0) then
   begin
    if (FBits <> nil) then
     MemoryManager.FreeAndNillMem(FBits);
    FNonZeroBitCount := 0;
   end // empty array
  else
   begin
    if (FBits = nil) then
     FBits := MemoryManager.AllocMem(SizeInBytes)
    else
     MemoryManager.ReallocMemAndClearTail(FBits, SizeInBytes);
   end;// not empty array
  if (NewSize < FBitCount) then
   begin
    if (NewSize = 0) then
     FNonZeroBitCount := 0
    else
     begin
      FBitCount := NewSize;
      FNonZeroBitCount := GetNonZeroBitCount;
     end;
   end;
  FBitCount := NewSize;
end;// SetSize


//------------------------------------------------------------------------------
// load from stream
//------------------------------------------------------------------------------
procedure TABSBitsArray.LoadFromStream(Stream: TStream);
var aBitsSize: Integer;
begin
 LoadDataFromStream(FBitCount,Sizeof(FBitCount),Stream,10401);
 LoadDataFromStream(FNonZeroBitCount,Sizeof(FNonZeroBitCount),Stream,10426);
 SetSize(FBitCount);
 if (FBitCount > 0) then
  begin
   aBitsSize := FBitCount div 8;
   if ((FBitCount mod 8) > 0) then
    Inc(aBitsSize);
   LoadDataFromStream(FBits^,aBitsSize,Stream,10402);
  end;
end; // LoadFromStream


//------------------------------------------------------------------------------
// save to stream
//------------------------------------------------------------------------------
procedure TABSBitsArray.SaveToStream(Stream: TStream);
var aBitsSize: Integer;
begin
 SaveDataToStream(FBitCount,Sizeof(FBitCount),Stream,10398);
 SaveDataToStream(FNonZeroBitCount,Sizeof(FNonZeroBitCount),Stream,10427);
 if (FBitCount > 0) then
  begin
   if (FBits = nil) then
    raise EABSException.Create(10399,ErrorLNilPointer);
   aBitsSize := FBitCount div 8;
   if ((FBitCount mod 8) > 0) then
    Inc(aBitsSize);
   SaveDataToStream(FBits^,aBitsSize,Stream,10400);
  end;
end; // SaveToStream


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
constructor TABSBitsArray.Create;
var
    i,c: Byte;
begin
 FBits := nil;
 SetSize(0);
 for i := 0 to 255 do
  begin
   c := 0;
   if ((i and 1) <> 0) then Inc(c);
   if ((i and 2) <> 0) then Inc(c);
   if ((i and 4) <> 0) then Inc(c);
   if ((i and 8) <> 0) then Inc(c);
   if ((i and 16) <> 0) then Inc(c);
   if ((i and 32) <> 0) then Inc(c);
   if ((i and 64) <> 0) then Inc(c);
   if ((i and 128) <> 0) then Inc(c);
   FBitsTable[i] := c;
  end;
end;// Create


//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSBitsArray.Destroy;
begin
 if (FBits <> nil) then
  MemoryManager.FreeAndNillMem(FBits);
end;// Destroy


//------------------------------------------------------------------------------
// get bit value
//------------------------------------------------------------------------------
function TABSBitsArray.GetBit(BitNo: Integer): Boolean;
begin
  if (BitNo >= FBitCount) then
   raise EABSException.Create(10394,ErrorLInvalidBitNo,[BitNo,FBitCount]);
  Result := CheckNullFlag(BitNo, FBits);
end;// GetBit


//------------------------------------------------------------------------------
// set bit value
//------------------------------------------------------------------------------
procedure TABSBitsArray.SetBit(BitNo: Integer; Value: Boolean);
var Bit: Boolean;
begin
  if (BitNo >= FBitCount) then
   raise EABSException.Create(10395,ErrorLInvalidBitNo,[BitNo,FBitCount]);
  Bit := CheckNullFlag(BitNo,FBits);
  if (Bit <> Value) then
   begin
    if (Value) then
     Inc(FNonZeroBitCount)
    else
     Dec(FNonZeroBitCount);
    SetNullFlag(Value, BitNo, FBits);
   end;
end;// SetBit


//------------------------------------------------------------------------------
// returns number of bit = 1 in FBits array by bit position
//------------------------------------------------------------------------------
function TABSBitsArray.GetBitNoByBitPosition(BitPosition: Integer): Integer;
var i,n:    Integer;
    b,k,l:        Byte;
begin
 if (BitPosition >= FNonZeroBitCount) then
  raise EABSException.Create(10428,ErrorLInvalidBitNo,
    [BitPosition,FNonZeroBitCount]);

 if (FBitCount = FNonZeroBitCount) then
  begin
   Result := BitPosition;
   Exit;
  end;

 i := 0; // byte number
 n := 0; // bits count
 while (n+FBitsTable[PByte(FBits + i)^] <= BitPosition) do
  begin
   Inc(n,FBitsTable[PByte(FBits + i)^]);
   Inc(i);
  end;
 Result := i * 8;
 b := PByte(FBits + i)^;
 l := 7;
 if (i = (FBitCount div 8)) then
  l := (FBitCount mod 8)-1;
 for k := 0 to l do
  begin
   if ((b and (1 shl k)) <> 0) then Inc(n);
   if (n > BitPosition) then Break;
   Inc(Result);
  end;
end; // GetBitNoByBitPosition


//------------------------------------------------------------------------------
// returns position of bit = 1 by bit no in FBits array
//------------------------------------------------------------------------------
function TABSBitsArray.GetBitPositionByBitNo(BitNo: Integer): Integer;
var n,i,j:    Integer;
    b,k:      Byte;
begin

 if (BitNo >= FBitCount) then
  raise EABSException.Create(10429,ErrorLInvalidBitNo, [BitNo,FBitCount]);
 if (FBitCount = FNonZeroBitCount) then
  begin
   Result := BitNo;
   Exit;
  end;
 // number of byte with flags
 i := Integer(BitNo) div 8;
 Result := -1; // bits count
 for j := 0 to i-1 do
   Result := Result + FBitsTable[PByte(FBits + j)^];
 // scan last byte
 b := PByte(FBits + i)^;
 n := Integer(BitNo) mod 8;
 for k := 0 to n do
   if ((b and (1 shl k)) <> 0) then Inc(Result);
end; // GetBitPositionByBitNo


//------------------------------------------------------------------------------
// set all bits to 1
//------------------------------------------------------------------------------
procedure TABSBitsArray.SetAllBits;
var
    SizeInBytes: Integer;
begin
 if (FBitCount > 0) then
  begin
    SizeInBytes := (FBitCount div 8) + Integer((FBitCount mod 8) > 0);
    FillChar(FBits^,SizeInBytes,$FF);
  end;
  FNonZeroBitCount := FBitCount;
end; // SetAllBits


//------------------------------------------------------------------------------
// find any bit with specified value
//------------------------------------------------------------------------------
function TABSBitsArray.FindBit(Value: Boolean; var BitNo: Integer): Boolean;
var
  i: Integer;
begin
 {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSBitsArray.Find1 start');{$ENDIF}
 try
  Result := False;
  if (FBitCount > 0) then
   for i := 0 to FBitCount-1 do
    if (GetBit(i) = Value) then
     begin
       Result := True;
       BitNo := i;
       break;
     end
  finally
   {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSBitsArray.Find1 end');{$ENDIF}
  end;
end;// FindBit


//------------------------------------------------------------------------------
// Find
//------------------------------------------------------------------------------
function TABSBitsArray.Find(Restart,GoForward: Boolean; CurBitNo: Integer; var FoundBitNo: Integer): Boolean;
var
  i, step: Integer;
begin
 {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSBitsArray.Find2 start');{$ENDIF}
 try
  { TODO : implement fast bit search }
    Result := False;
    if (GoForward) then
      step := 1
    else
      step := -1;

    if (Restart) then
      if (GoForward) then
        i := 0
      else
        i := FBitCount - 1
    else
      i := CurBitNo + step;

    while (i >= 0) and (i < FBitCount) do
      begin
        if (GetBit(i)) then
          begin
            FoundBitNo := i;
            Result := True;
            break;
          end;
        Inc(i, step);
      end;
  finally
   {$IFDEF DEBUG_TRACE_ALL_ENGINE}aaWriteToLog('TABSBitsArray.Find2 end');{$ENDIF}
  end;
end;// Find



////////////////////////////////////////////////////////////////////////////////
//
// Bits functions
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// return true if null flag is set (bit = 1)
//------------------------------------------------------------------------------
function CheckNullFlag(
          BitNo:       Integer; // number of bit to check
          NullFlags:   TAbsPByte // pointer to bits of null flags
                      ): Boolean;
begin
 Result := ((PByte(NullFlags+(BitNo div 8))^) and (1 shl (BitNo mod 8)) <> 0);
end; // CheckNullFlag


//------------------------------------------------------------------------------
// set or clear a null flag
//------------------------------------------------------------------------------
procedure SetNullFlag(
          ToSet:       Boolean; // if true - set bit = 1, otherwise set bit = 0
          BitNo:       Integer; // number of bit to check
          NullFlags:   TAbsPByte // pointer to bits of null flags
                     );
begin
  NullFlags := TAbsPByte(NullFlags + (BitNo div 8));
  if (ToSet) then
    PByte(NullFlags)^ := PByte(NullFlags)^ or (1 shl (BitNo mod 8))
  else
    PByte(NullFlags)^ := PByte(NullFlags)^ and (not (1 shl (BitNo mod 8)));
end; // SetNullFlag

{$IFNDEF D6H}

type

TGUID = record
  D1: LongWord;
  D2: Word;
  D3: Word;
  D4: array[0..7] of Byte;
end;

function CoCreateGuid(out guid: TGUID): HResult; stdcall; external 'ole32.dll' name 'CoCreateGuid';
function StringFromCLSID(const clsid: TGUID; out psz: PWideChar): HResult; stdcall;
  external 'ole32.dll' name 'StringFromCLSID';
procedure CoTaskMemFree(pv: Pointer); stdcall;
  external 'ole32.dll' name 'CoTaskMemFree';


function CreateGUID(out Guid: TGUID): HResult;
begin
  Result := CoCreateGuid(Guid);
end;

function GUIDToString(const GUID: TGUID): AnsiString;
var
  P: PWideChar;
begin
  if not Succeeded(StringFromCLSID(GUID, P)) then
    raise Exception.Create('StringFromCLSID failed');
  Result := P;
  CoTaskMemFree(P);
end;


{$ENDIF}



function GetTemporaryName(Prefix: string): string;
var
  G: TGUID;
begin
  CreateGUID(G);
  Result := Prefix + GUIDToString(G);
end; // GetTemporaryName

function BracketFieldName(name: AnsiString): AnsiString;
var
 b: Boolean;
 s: AnsiString;
 i: Integer;
begin
 b := (Pos(' ', name) > 0);
 if not b then
  begin
   s := UpperCase(name);
   for i := 0 to ABSMaxSQLReservedWords do
    if (s = ABSSQLReservedWords[i]) then
     begin
      b := True;
      Break;
     end;
  end;
 if b then
  Result := '['+name+']'
 else
  Result := name;
end;



end.
