
//////////////////////////////////////////////////
//  DB Access Components
//  Copyright © 1998-2007 Core Lab. All right reserved.
//  Mem Data
//  Created:            20.02.98
//////////////////////////////////////////////////

{$IFNDEF CLR}

{$I Dac.inc}

unit MemData;
{$ENDIF}
interface
uses
  Classes, CRParser, MemUtils, SyncObjs,
{$IFDEF MSWINDOWS}
  Windows,
{$ENDIF}
{$IFDEF VER6P}
  FMTBcd, Variants,
{$ENDIF}
{$IFDEF CLR}
  System.Runtime.InteropServices, System.Text;
{$ELSE}
  CLRClasses;
{$ENDIF}

const
  btSign = $DD;   // DEBUG
  flUsed = $EE;
  flFree = $DD;

  FlatBufferLimit = 32;

{ internal data types }

{ ! can't modify this consts }

  dtUnknown     = 0;
  dtString      = 1;

  dtInt8        = 2;
  dtInt16       = 3;
  dtSmallint    = dtInt16;
  dtInt32       = 4;

  dtInteger     = dtInt32;
  dtFloat       = 5;
  dtDate        = 6; // Date only
  dtTime        = 7; // Time only
  dtDateTime    = 8; // Date and time
  dtUInt16      = 9;
  dtWord        = dtUInt16;
  dtBoolean     = 10;
  dtInt64       = 11;
  dtLargeint    = dtInt64;
  dtCurrency    = 12;
  dtBlob        = 13;
  dtMemo        = 14;
  dtObject      = 15;
  dtReference   = 16;
  dtArray       = 17;
  dtTable       = 18;

{$IFDEF VER5P}
  dtVariant     = 19;
{$ENDIF}
  dtExtString   = 20;
  dtBytes       = 21;
  dtVarBytes    = 22; /// Cannot be deleted because "Fixed" flag not avaible on component level (MSAccess) GetFieldType(DataType: word): TFieldType
  dtExtVarBytes = 23;

  dtUInt32      = 24;
  dtLongword    = dtUInt32;

  dtWideString  = 25;
  dtExtWideString = 26;

  dtBCD  = 27;
{$IFDEF VER6P}
  dtFMTBCD = 28;
{$ENDIF}
  dtGuid = 29;
  dtWideMemo = 30; //This type corectly supported only in BDS 2006 and higher

{ StringHeap const }

const
  BlockSize = 16384;
  SmallSize = 2000;
  Align = 8;
  RefNull = 101;

{$IFNDEF CLR}
{$IFDEF VER6P}
  SizeOfTBcd = SizeOf(TBcd);
{$ENDIF}
{$ENDIF}

type
  TDataType = word;

  TDANumericType = (ntFloat, ntBCD{$IFDEF VER6P}, ntFmtBCD{$ENDIF});

  //Note that TConnLostCause should be ordered by FailOver priority
  //e.g. there are multyple DataSet.ApplyUpdates during Connection.ApplyUpdates so Connection.ApplyUpdates is more
  //prioritized operation than DataSet.ApplyUpdates and should be reexecuted instead of DataSet.ApplyUpdates in case of
  //failover
  TConnLostCause = (clUnknown,  //Connection lost reason - unknown
    clExecute,                  //Connection Lost detected during SQL execution (Reconnect with exception possible)
    clOpen,                     //Connection Lost detected during query opening (Reconnect/Reexecute possible)
    clRefresh,                  //Connection Lost detected during query opening (Reconnect/Reexecute possible)
    clApply,                    //Connection Lost detected during DataSet.ApplyUpdates (Reconnect/Reexecute possible)
    clServiceQuery,             //Connection Lost detected during service information request (Reconnect/Reexecute possible)
    clTransStart,               //Connection Lost detected during transaction start (Reconnect/Reexecute possible)
                                //In IBDAC one connection could start several transactions during ApplyUpdates that's why
                                //clTransStart has less priority then clConnectionApply
    clConnectionApply,          //Connection Lost detected during Connection.ApplyUpdates (Reconnect/Reexecute possible)
    clConnect                   //Connection Lost detected during connection establishing (Reconnect possible)
    );

{ TBlockManager }

{$IFDEF CLR}
  PBlockHeader = packed record
  private
    Ptr: IntPtr;

    function GetItemCount: word;
    procedure SetItemCount(Value: word);
    function GetUsedItems: word;
    procedure SetUsedItems(Value: word);
    function GetPrev: PBlockHeader;
    procedure SetPrev(Value: PBlockHeader);
    function GetNext: PBlockHeader;
    procedure SetNext(Value: PBlockHeader);

  public
    property ItemCount: word read GetItemCount write SetItemCount;
    property UsedItems: word read GetUsedItems write SetUsedItems;
    property Prev: PBlockHeader read GetPrev write SetPrev;
    property Next: PBlockHeader read GetNext write SetNext;

    class operator Implicit(AValue: IntPtr): PBlockHeader;
    class operator Implicit(AValue: PBlockHeader): IntPtr;
    class operator Implicit(AValue: PBlockHeader): integer;
    class operator Equal(ALeft, ARight: PBlockHeader): boolean;
  end;
{$ELSE}
  PBlockHeader = ^TBlockHeader;
{$ENDIF}
  TBlockHeader = packed record
    ItemCount: word;
    UsedItems: word;
    Prev: PBlockHeader;
    Next: PBlockHeader;
    Test: byte;       // DEBUG
  end;

  TItemStatus = (isUnmodified, isUpdated, isAppended, isDeleted);
  TItemTypes = set of TItemStatus;
  TUpdateRecAction = (urFail, urAbort, urSkip, urRetry, urApplied, urNone, urSuspended);
  TItemFilterState = (fsNotChecked, fsNotOmitted, fsOmitted);

{$IFDEF CLR}
  PItemHeader = packed record
  private
    Ptr: IntPtr;

    function GetBlock: PBlockHeader;
    procedure SetBlock(Value: PBlockHeader);
    function GetPrev: PItemHeader;
    procedure SetPrev(Value: PItemHeader);
    function GetNext: PItemHeader;
    procedure SetNext(Value: PItemHeader);
    function GetRollback: PItemHeader;
    procedure SetRollback(Value: PItemHeader);
    function GetStatus: TItemStatus;
    procedure SetStatus(Value: TItemStatus);
    function GetUpdateResult: TUpdateRecAction;
    procedure SetUpdateResult(Value: TUpdateRecAction);
    function GetOrder: longint;
    procedure SetOrder(Value: longint);
    function GetFlag: byte;
    procedure SetFlag(Value: byte);
    function GetFilterResult: TItemFilterState;
    procedure SetFilterResult(Value: TItemFilterState);
  public
    property Block: PBlockHeader read GetBlock write SetBlock;
    property Prev: PItemHeader read GetPrev write SetPrev;
    property Next: PItemHeader read GetNext write SetNext;
    property Rollback: PItemHeader read GetRollback write SetRollback;
    property Status: TItemStatus read GetStatus write SetStatus;
    property UpdateResult: TUpdateRecAction read GetUpdateResult write SetUpdateResult;
    property Order: longint read GetOrder write SetOrder;
    property Flag: byte read GetFlag write SetFlag;
    property FilterResult: TItemFilterState read GetFilterResult write SetFilterResult;

    class operator Implicit(AValue: IntPtr): PItemHeader;
    class operator Implicit(AValue: PItemHeader): IntPtr;
    class operator Implicit(AValue: PItemHeader): integer;
    class operator Equal(ALeft, ARight: PItemHeader): boolean;
  end;
{$ELSE}
  PItemHeader = ^TItemHeader;
{$ENDIF}
  TItemHeader = packed record
    Block: PBlockHeader;
    Prev: PItemHeader;
    Next: PItemHeader;
    Rollback: PItemHeader;
    Status: TItemStatus;
    UpdateResult: TUpdateRecAction;
    Order: longint;
    Flag: byte;
    FilterResult: TItemFilterState;
    AlignByte: byte; // (SizeOf(TBlockHeader) + SizeOf(TItemHeader)) mod 2 = 0
  end;               // (RecordSize + SizeOf(TItemHeader)) mod 2 = 0

  TBlockManager = class
  private
  {$IFDEF CLR}
    FHeap: THandle;
  {$ENDIF}

  public
    FirstFree: PItemHeader;
    FirstBlock: PBlockHeader;
    RecordSize: longint;
    DefaultItemCount: word;

    constructor Create;
    destructor Destroy; override;

    procedure AllocBlock(var Block: PBlockHeader; ItemCount: word);
    procedure FreeBlock(Block: PBlockHeader);

    procedure AddFreeBlock;
    procedure FreeAllBlock;

    procedure AllocItem(var Item: PItemHeader);
    procedure FreeItem(Item: PItemHeader);
    procedure InitItem(Item: PItemHeader);

    procedure PutRecord(Item: PItemHeader; Rec: IntPtr);
    procedure GetRecord(Item: PItemHeader; Rec: IntPtr);
    function GetRecordPtr(Item: PItemHeader): IntPtr;

    procedure CopyRecord(ItemSrc: PItemHeader; ItemDest: PItemHeader);
  end;

{ TStringHeap }

{$IFDEF CLR}
  PBlock = packed record
  private
    Ptr: IntPtr;

    function GetNext: PBlock;
    procedure SetNext(Value: PBlock);
  public
    property Next: PBlock read GetNext write SetNext;

    class operator Implicit(AValue: IntPtr): PBlock;
    class operator Implicit(AValue: PBlock): IntPtr;
  end;
{$ELSE}
  PBlock = ^TBlock;
{$ENDIF}
  TStrData = array [0..BlockSize - 5 {SizeOf(PBlock) - 1}] of char;
  TBlock = packed record
    Next: PBlock;
    Data: TStrData;
  end;

  TSmallTab = array [1..SmallSize div Align] of IntPtr;

const
  SizeOf_TStrData = BlockSize - 4;
  SizeOf_TBlock = SizeOf_TStrData + 4;
  SizeOf_TSmallTab = SmallSize div Align * 4;

type
  TStringHeap = class
  private
    FSmallTab: TSmallTab;
    FFree: integer;
    FRoot: PBlock;
    FEmpty: boolean;
    FSysGetMem: boolean;
  {$IFDEF WIN32}
    FUseSysMemSize: boolean;
  {$ENDIF}
    FThreadSafety: boolean;
    FThreadSafetyCS: TCriticalSection;
    procedure SetThreadSafety(const Value: boolean);
    function UseSmallTabs(divSize: integer): boolean;
  public
    constructor Create;
    destructor Destroy; override;
    function NewBuf(Size: integer): IntPtr;
    function AllocStr(Str: IntPtr; Trim: boolean = false; Len: integer = -1): IntPtr;
    function AllocWideStr(Str: IntPtr; Trim: boolean = false; Len: integer = -1): IntPtr;
    function ReAllocStr(Str: IntPtr; Trim: boolean = false): IntPtr;
    function ReAllocWideStr(Str: IntPtr; Trim: boolean = false): IntPtr;
    procedure DisposeBuf(Buf: IntPtr);
    procedure AddRef(Buf: IntPtr);
    procedure Clear;
    property Empty: boolean read FEmpty;
    property SysGetMem: boolean read FSysGetMem;
    property ThreadSafety: boolean read FThreadSafety write SetThreadSafety;
  end;

{ TFieldDesc }

  TFieldTypeSet = set of byte;

  TDateFormat = (dfMSecs, dfDateTime, dfTime, dfDate);

  TFieldDescKind = (fdkData, fdkCached, fdkCalculated);

  TObjectType = class;

  TFieldDesc = class
  protected
    FName: string;       // unique name in TData
    FActualName: string; // original name from source
    FDataType: word;
    FSubDataType: word;
    FLength: word;       // precision for number
    FScale: word;
    FFieldNo: word;
    FActualFieldNo: word;
    FSize: word;         // size in rec buffer
    FOffset: longint;    // offset in rec buffer
    FDataOffset: longint;// offset in storage structure
    FRequired: boolean;
    FReadOnly: boolean;
    FIsKey: boolean;
    FFixed: boolean;     // indicates that the string field has a fixed size
    FHidden: boolean;
    FObjectType: TObjectType;
    FParentField: TFieldDesc;
    FHiddenObject: boolean;  // for hide Object field (child field is visible)
    FHandle: IntPtr;    // pointer to field specific data
    FReserved: boolean;  // reserved flag for perfomance optimization
    FFieldDescKind: TFieldDescKind;

    procedure SetObjectType(Value: TObjectType);

  public
    constructor Create; virtual;
    destructor Destroy; override;

    function HasParent: boolean;

    procedure Assign(FieldDesc: TFieldDesc);

    property Name: string read FName write FName;
    property ActualName: string read FActualName write FActualName ;
    property DataType: word read FDataType write FDataType;
    property SubDataType: word read FSubDataType write FSubDataType;
    property Length: word read FLength write FLength;
    property Scale: word read FScale write FScale;
    property FieldNo: word read FFieldNo write FFieldNo;
    property ActualFieldNo: word read FActualFieldNo write FActualFieldNo; // for define
    property Size: word read FSize write FSize;
    property Offset: longint read FOffset write FOffset;
    property DataOffset: longint read FDataOffset write FDataOffset;
    property Required: boolean read FRequired write FRequired;
    property ReadOnly: boolean read FReadOnly write FReadOnly;
    property IsKey: boolean read FIsKey write FIsKey;
    property Fixed: boolean read FFixed write FFixed;
    property Hidden: boolean read FHidden write FHidden;
    property ObjectType: TObjectType read FObjectType write SetObjectType;
    property ParentField: TFieldDesc read FParentField write FParentField;
    property HiddenObject: boolean read FHiddenObject write FHiddenObject; // IncludeObject
    property Handle: IntPtr read FHandle write FHandle;
    property FieldDescKind: TFieldDescKind read FFieldDescKind write FFieldDescKind;
  end;

  TFieldDescClass = class of TFieldDesc;

  TFieldDescs = class (TDAList)
  private
    function GetItems(Index: integer): TFieldDesc;

  public
    destructor Destroy; override;

    procedure Clear; override;

    function FindField(Name: string): TFieldDesc;
    function FieldByName(Name: string): TFieldDesc;

    property Items[Index: integer]: TFieldDesc read GetItems; default;
  end;

{ TSharedObject }

  TSharedObject = class
  protected
    FRefCount: integer;
    FGCHandle: IntPtr;

    function GetGCHandle: IntPtr;

  public
    constructor Create;
    destructor Destroy; override;
    procedure Free;

    procedure CheckValid;

    procedure AddRef;
    procedure Release;
  {$IFNDEF CLR}
    function GetHashCode: integer;
  {$ENDIF}

    property RefCount: integer read FRefCount;
    property GCHandle: IntPtr read GetGCHandle;
  end;

{ TObjectType }

  TAttribute = class
  private
    FName: string;
    FDataType: word;
    FLength: word;
    FScale: word;
    FSize: word;       // size of got data
    FDataSize: word;   // size of stored data
    FOffset: word;     // stored offset
    FIndicatorOffset: word;  // indicator offset
    FAttributeNo: word;
    FObjectType: TObjectType;
    FOwner: TObjectType;
    FFixed: boolean;

    procedure SetObjectType(Value: TObjectType);

  public
    constructor Create;
    destructor Destroy; override;

    property Name: string read FName write FName;
    property DataType: word read FDataType write FDataType;
    property Fixed: boolean read FFixed write FFixed;
    property Length: word read FLength write FLength;
    property Scale: word read FScale write FScale;
    property Size: word read FSize write FSize;
    property DataSize: word read FDataSize write FDataSize;
    property Offset: word read FOffset write FOffset;
    property IndicatorOffset: word read FIndicatorOffset write FIndicatorOffset;
    property AttributeNo: word read FAttributeNo write FAttributeNo;
    property ObjectType: TObjectType read FObjectType write SetObjectType;
    property Owner: TObjectType read FOwner write FOwner;
  end;

  TObjectType = class (TSharedObject)
  private
    function GetAttributes(Index: integer): TAttribute;
    function GetAttributeCount: integer;

  protected
    FName: string;
    FDataType: word;
    FSize: integer;
    FAttributes: TDAList;

  protected
    procedure ClearAttributes;

  public
    constructor Create;
    destructor Destroy; override;

    function FindAttribute(Name: string): TAttribute;
    function AttributeByName(Name: string): TAttribute;

    property Name: string read FName;
    property DataType: word read FDataType;
    property Size: integer read FSize;
    property AttributeCount: integer read GetAttributeCount;

    property Attributes[Index: integer]: TAttribute read GetAttributes;
  end;

  TDBObject = class (TSharedObject)
  private
    FObjectType: TObjectType;

  protected
    procedure SetObjectType(Value: TObjectType);

    procedure GetAttributeValue(Name: string; Dest: IntPtr; var IsBlank: boolean); virtual;
    procedure SetAttributeValue(Name: string; Source: IntPtr); virtual;

  public
    constructor Create;

    property ObjectType: TObjectType read FObjectType;
  end;

  TCacheItem = class
    Item: PItemHeader;
    Next: TCacheItem;
  end;

{$IFDEF CLR}
  PRecBookmark = packed record
  private
    Ptr: IntPtr;

    function GetRefreshIteration: longint;
    procedure SetRefreshIteration(Value: longint);
    function GetItem: PItemHeader;
    procedure SetItem(Value: PItemHeader);
    function GetOrder: longint;
    procedure SetOrder(Value: longint);

  public
    property RefreshIteration: longint read GetRefreshIteration write SetRefreshIteration;
    property Item: PItemHeader read GetItem write SetItem;
    property Order: longint read GetOrder write SetOrder;

    class operator Implicit(AValue: IntPtr): PRecBookmark;
    class operator Implicit(AValue: PRecBookmark): IntPtr;
    class operator Implicit(AValue: integer): PRecBookmark;
  end;
{$ELSE}
  PRecBookmark = ^TRecBookmark;
{$ENDIF}
  TRecBookmark = record
    RefreshIteration: longint;
    Item: PItemHeader;
    Order: longint
  end;

  TFilterFunc = function(RecBuf: IntPtr): boolean of object;

  TBoolParser = class (TParser)
  protected
    procedure ToRightQuote(LeftQuote: Char); override;
  public
    constructor Create(const Text: string); override;
  end;

  TExpressionType = (ntEqual,ntMore,ntLess,ntMoreEqual,ntLessEqual,ntNoEqual,
    ntAnd,ntOr,ntNot,ntField,ntValue,ntTrue,ntFalse,ntLike);

  TExpressionNode = class
    NextAlloc: TExpressionNode;
    NodeType: TExpressionType;
    LeftOperand: TExpressionNode;
    RightOperand: TExpressionNode;
    NextOperand: TExpressionNode;
    FieldDesc: TFieldDesc; // used only when TExpressionType = ntField
    Value: variant;
  end;

  TBlob = class;

{ TData }

  TUpdateRecKind = (ukUpdate, ukInsert, ukDelete);
  TOnModifyRecord = procedure of object;
  TOnApplyRecord = procedure (UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean) of object;

  TOnGetCachedFields = procedure of object;
  TOnGetCachedBuffer = procedure(Buffer: IntPtr; Source: IntPtr = nil) of object;

  TData = class
  private
    FRecordSize: longint;  // FDataSize + TIndicatorSize
    FCalcRecordSize: longint;
    FCachedUpdates: boolean;
    FOnAppend: TOnModifyRecord;
    FOnDelete: TOnModifyRecord;
    FOnUpdate: TOnModifyRecord;
    FOnApplyRecord: TOnApplyRecord;
    FAutoInitFields: boolean; // initialization fields by InternalInitField
    FTrimFixedChar: boolean;
    FTrimVarChar: boolean;

  { Filter }
    FFilterFunc: TFilterFunc;
    FFilterMDFunc: TFilterFunc;
    FFilterText: string;
    FFilterCaseInsensitive: boolean;
    FFilterNoPartialCompare: boolean;
    FFilterItemTypes: TItemTypes;

    Parser: TBoolParser;
    Code: integer;
    StrLexem: string;
    FilterExpression: TExpressionNode;
    FirstAlloc: TExpressionNode;
    FilterRecBuf: IntPtr;

  /// if False then PutField set Null for string fields with empty value ('')
    FEnableEmptyStrings: boolean;
    FHasComplexFields: boolean;
    FSparseArrays: boolean;

    FOnGetCachedFields: TOnGetCachedFields;
    FOnGetCachedBuffer: TOnGetCachedBuffer;

    procedure FilterError;
    function AllocNode: TExpressionNode;
    function OrExpr: TExpressionNode;
    function AndExpr: TExpressionNode;
    function Condition: TExpressionNode;
    function Argument: TExpressionNode;

    procedure CreateFilterExpression(Text: string);
    procedure FreeFilterExpression;

    function Eval(Node: TExpressionNode): boolean;

    function GetFieldCount: word;
    procedure SetCachedUpdates(Value: boolean);
  protected
    FRecordNoOffset: integer;

    FRecordCount: longint;
    FBOF: boolean;
    FEOF: boolean;
    DataSize: longint; // size of data
    CalcDataSize: longint;

    FFields: TFieldDescs;

    StringHeap: TStringHeap;

  { Open/Close }
    procedure InternalPrepare; virtual;
    procedure InternalUnPrepare; virtual;
    procedure InternalOpen; virtual;
    procedure InternalClose; virtual;

  { Data }
    procedure InitData; virtual;
    procedure FreeData; virtual;

  { Fields }
    procedure InternalInitFields; virtual;
    procedure InitObjectFields(ObjectType: TObjectType; Parent: TFieldDesc);
    function InternalGetObject(FieldNo: word; RecBuf: IntPtr): TSharedObject;
    function GetArrayFieldName(ObjectType: TObjectType; ItemIndex: integer): string; virtual;

    function GetIndicatorSize: word; virtual;

    procedure GetChildFieldInfo(Field: TFieldDesc; var RootField: TFieldDesc; var AttrName: string);
    procedure GetChildField(Field: TFieldDesc; RecBuf: IntPtr; Dest: IntPtr; var IsBlank: boolean);
    procedure PutChildField(Field: TFieldDesc; RecBuf: IntPtr; Source: IntPtr);

    function NeedConvertEOL: boolean; virtual;

  { Records }

  { Navigation }
    function GetEOF: boolean; virtual;
    function GetBOF: boolean; virtual;

    function GetRecordCount: longint; virtual;
    function GetRecordNo: longint; virtual;
    procedure SetRecordNo(Value: longint); virtual;

  { Edit }
    procedure InternalAppend(RecBuf: IntPtr); virtual;
    procedure InternalDelete; virtual;
    procedure InternalUpdate(RecBuf: IntPtr); virtual;

    property IndicatorSize: word read GetIndicatorSize;

  { Filter }
    function Filtered: boolean;
    procedure SetFilterText(Value: string); virtual;

  { CachedUpdates }
    function GetUpdatesPending: boolean; virtual;
    procedure SetFilterItemTypes(Value: TItemTypes); virtual;

  public
    Active: boolean;
    Prepared: boolean;
    NewCacheRecBuf: IntPtr;
    OldCacheRecBuf: IntPtr;

    property FieldCount: word read GetFieldCount;
    property Fields: TFieldDescs read FFields;
    property Bof: boolean read GetBOF; // EOF: for CB case sensivity
    property Eof: boolean read GetEOF;

    constructor Create;
    destructor Destroy; override;

  { Open/Close }
    procedure Open; virtual;
    procedure Close; virtual;

    procedure Prepare; virtual;
    procedure UnPrepare; virtual;

    function IsFullReopen: boolean; virtual;    
    procedure Reopen; virtual;

  { Fields }
    function GetFieldDescType: TFieldDescClass; virtual;  
    procedure InitFields; virtual;
    procedure ClearFields; virtual;
    procedure GetField(FieldNo: word; RecBuf: IntPtr; Dest: IntPtr; var IsBlank: boolean);
    procedure GetFieldData(Field: TFieldDesc; RecBuf: IntPtr; Dest: IntPtr); virtual;
    function GetFieldBuf(RecBuf: IntPtr; FieldDesc: TFieldDesc; var DataType: integer; var IsBlank, NativeBuffer: boolean): IntPtr;
    procedure PutField(FieldNo: word; RecBuf: IntPtr; Source: IntPtr);
    procedure PutFieldData(Field: TFieldDesc; RecBuf: IntPtr; Source: IntPtr); virtual;
    function GetNull(FieldNo: word; RecBuf: IntPtr): boolean; virtual;
    procedure SetNull(FieldNo: word; RecBuf: IntPtr; Value: boolean); virtual;
    function GetNullByBlob(FieldNo: word; RecBuf: IntPtr): boolean;

    procedure GetFieldAsVariant(FieldNo: word; RecBuf: IntPtr; var Value: variant); virtual;
    procedure PutFieldAsVariant(FieldNo: word; RecBuf: IntPtr; const Value: variant); virtual;

    procedure GetDateFromBuf(Buf: IntPtr; Offset: integer; Date: IntPtr; Format: TDateFormat); virtual;
    procedure PutDateToBuf(Buf: IntPtr; Offset: integer; Date: IntPtr; Format: TDateFormat); virtual;

    function FindField(Name: string): TFieldDesc;
    function FieldByName(Name: string): TFieldDesc;

    function IsBlobFieldType(DataType: word): boolean; virtual; // TBlob descendants - dtBlob, dtMemo etc
    function IsComplexFieldType(DataType: word): boolean; virtual; // All supported complex field types (BlobFieldTypes, ExtFieldTypes and TSharedObject descendants (not BLOB))

    function HasFields(FieldTypes: TFieldTypeSet): boolean;
    function HasBlobFields: boolean;
    function CheckHasComplexFields: boolean;

  { Records }
    function AllocRecBuf(var RecBuf: IntPtr): IntPtr;
    procedure FreeRecBuf(RecBuf: IntPtr);

    procedure InitRecord(RecBuf: IntPtr);
    //procedure FreeRecord(RecBuf: pointer);
    procedure GetRecord(RecBuf: IntPtr); virtual; abstract;
    procedure GetNextRecord(RecBuf: IntPtr); virtual; abstract;
    procedure GetPriorRecord(RecBuf: IntPtr); virtual; abstract;
    procedure PutRecord(RecBuf: IntPtr); virtual; abstract;
    procedure AppendRecord(RecBuf: IntPtr); virtual; abstract;
    procedure AppendBlankRecord;
    procedure InsertRecord(RecBuf: IntPtr); virtual; abstract;
    procedure UpdateRecord(RecBuf: IntPtr); virtual; abstract; // Modify
    procedure DeleteRecord; virtual; abstract;

    procedure EditRecord(RecBuf: IntPtr);
    procedure PostRecord(RecBuf: IntPtr);
    procedure CancelRecord(RecBuf: IntPtr); virtual;

    procedure CreateComplexFields(RecBuf: IntPtr; WithBlob: boolean); virtual;
    procedure CreateComplexField(RecBuf: IntPtr; FieldIndex: integer; WithBlob: boolean); virtual;
    procedure FreeComplexFields(RecBuf: IntPtr; WithBlob: boolean); virtual;
    procedure CopyComplexFields(Source: IntPtr; Dest: IntPtr; WithBlob: boolean); virtual;  // copy content ComplexFields
    procedure AddRefComplexFields(RecBuf: IntPtr); virtual;

  { Navigation }
    procedure SetToBegin; virtual;
    procedure SetToEnd; virtual;

  { BookMarks }
    procedure GetBookmark(Bookmark: PRecBookmark); virtual;
    procedure SetToBookmark(Bookmark: PRecBookmark); virtual;
    function BookmarkValid(Bookmark: PRecBookmark): boolean; virtual;
    function CompareBookmarks(Bookmark1, Bookmark2: PRecBookmark): integer; virtual;

  { CachedUpdates }
    function GetUpdateStatus: TItemStatus; virtual;
    function GetUpdateResult: TUpdateRecAction; virtual;

    procedure SetCacheRecBuf(NewBuf: IntPtr; OldBuf: IntPtr); virtual;
    procedure ApplyUpdates; virtual;
    procedure CommitUpdates; virtual;
    procedure CancelUpdates; virtual;
    procedure RestoreUpdates; virtual;
    procedure RevertRecord; virtual;

    procedure ApplyRecord(UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean); virtual;

    procedure GetOldRecord(RecBuf: IntPtr); virtual; // get rollback data

  { Filter }
    procedure FilterUpdated; virtual;

  { Blobs }
    function GetObject(FieldNo: word; RecBuf: IntPtr): TSharedObject;
    procedure SetObject(FieldNo: word; RecBuf: IntPtr; Obj: TSharedObject);
    function ReadBlob(FieldNo: word; RecBuf: IntPtr; Position: longint;
      Count: longint; Dest: IntPtr; FromRollback: boolean = false; TrueUnicode: boolean = False): longint;
    procedure WriteBlob(FieldNo: word; RecBuf: IntPtr; Position: longint;
      Count: longint; Source: IntPtr; TrueUnicode: boolean = False);
    procedure TruncateBlob(FieldNo: word; RecBuf: IntPtr; Size: longint;
      TrueUnicode: boolean = False);
    function GetBlobSize(FieldNo: word; RecBuf: IntPtr; FromRollback: boolean = false;
      TrueUnicode: boolean = False): longint;
    procedure SetBlobSize(FieldNo: word; RecBuf: IntPtr; NewSize: longint; FromRollback: boolean = false;
      TrueUnicode: boolean = False);

    property RecordSize: longint read FRecordSize;
    property CalcRecordSize: longint read FCalcRecordSize;
    property RecordCount: longint read GetRecordCount;//FRecordCount;
    property RecordNo: longint read GetRecordNo write SetRecordNo;
    property CachedUpdates: boolean read FCachedUpdates write SetCachedUpdates default False;
    property UpdatesPending: boolean read GetUpdatesPending;
    property FilterFunc: TFilterFunc read FFilterFunc write FFilterFunc;
    property FilterMDFunc: TFilterFunc read FFilterMDFunc write FFilterMDFunc;
    property FilterText: string read FFilterText write SetFilterText;
    property FilterCaseInsensitive: boolean read FFilterCaseInsensitive write FFilterCaseInsensitive;
    property FilterNoPartialCompare: boolean read FFilterNoPartialCompare write FFilterNoPartialCompare;
    property FilterItemTypes: TItemTypes read FFilterItemTypes write SetFilterItemTypes;
    property AutoInitFields: boolean read FAutoInitFields write FAutoInitFields;
    property TrimFixedChar: boolean read FTrimFixedChar write FTrimFixedChar;
    property TrimVarChar: boolean read FTrimVarChar write FTrimVarChar;

  /// if False then PutField set Null for string fields with empty value ('')
    property EnableEmptyStrings: boolean read FEnableEmptyStrings write FEnableEmptyStrings;
    property SparseArrays: boolean read FSparseArrays write FSparseArrays;

    property OnAppend: TOnModifyRecord read FOnAppend write FOnAppend;
    property OnDelete: TOnModifyRecord write FOnDelete;
    property OnUpdate: TOnModifyRecord write FOnUpdate;
    property OnApplyRecord: TOnApplyRecord write FOnApplyRecord;
    property OnGetCachedFields: TOnGetCachedFields write FOnGetCachedFields;
    property OnGetCachedBuffer: TOnGetCachedBuffer write FOnGetCachedBuffer;
    property HasComplexFields: boolean read FHasComplexFields write FHasComplexFields;
  end;

  TReorderOption = (roInsert,roDelete,roFull);

  TSortColumn = class
  public
    FieldDesc: TFieldDesc;
    DescendingOrder: boolean;
    CaseSensitive: boolean;
  end;

  TSortColumns = class (TDAList)
  private
    function GetItems(Index: integer): TSortColumn;

  public
    destructor Destroy; override;

    procedure Clear; override;

    property Items[Index: integer]: TSortColumn read GetItems; default;
  end;

  TRecordNoCache = array of PItemHeader;

  TLocateExOption = (lxCaseInsensitive, lxPartialKey, lxNearest, lxNext, lxUp, lxPartialCompare{,lxCharCompare});
  TLocateExOptions = set of TLocateExOption;

  TMemData = class (TData)
  private
    Cache: TCacheItem;
    LastCacheItem: TCacheItem;
    FRefreshIteration: longint;
    FIndexFieldNames: string;
    FIndexFields: TSortColumns;

    FRecordNoCache: TRecordNoCache;

  { Sorting }
    procedure UpdateIndexFields;
    function CompareRecords(RecBuf1, RecBuf2: IntPtr): integer;
    procedure Exchange(I, J: PItemHeader);
    procedure MoveSortedRecord(Dir: integer);
    procedure QuickSort(L, R, P: PItemHeader);
    procedure ClearItemsOmittedStatus;
  protected
    FirstItem: PItemHeader;
    LastItem: PItemHeader;
    CurrentItem: PItemHeader;

    BlockMan: TBlockManager;

  { Items/Data }
    function InsertItem: PItemHeader;
    function AppendItem: PItemHeader;
    procedure DeleteItem(Item: PItemHeader);
    procedure RevertItem(Item: PItemHeader);

    procedure InitData; override;
    procedure FreeData; override;

    procedure ReorderItems(Item: PItemHeader; ReorderOption: TReorderOption);

  { Navigation }
    function GetEOF: boolean; override;
    function GetBOF: boolean; override;

    function GetRecordCount: longint; override;
    function GetRecordNo: longint; override;
    procedure SetRecordNo(Value: longint); override;

  { Fetch }
    function Fetch(FetchBack: boolean = False): boolean; virtual;
    procedure InitFetchedItems(FetchedItem: IntPtr; NoData, FetchBack: boolean);

  { Filter/Sorting }
  {$IFNDEF CLR}
    function InternalAnsiStrComp(const Value1, Value2: IntPtr;
      const Options: TLocateExOptions): integer; virtual;
  {$ENDIF}
    function InternalAnsiCompareText(const Value1, Value2: string;
      const Options: TLocateExOptions): integer; virtual;
    function InternalWStrLComp(const Value1, Value2: WideString;
      const Options: TLocateExOptions): integer; virtual;
    function InternalWStrComp(const Value1, Value2: WideString;
      const Options: TLocateExOptions): integer; virtual;

    function CompareStrValues(const Value: string; const FieldValue: string; const Options: TLocateExOptions): integer; virtual;
    function CompareWideStrValues(const Value: WideString; const FieldValue: WideString; const Options: TLocateExOptions): integer; virtual;
    function CompareBinValues(const Value: IntPtr; const ValueLen: integer; const FieldValue: IntPtr; const FieldValueLen: integer; const Options: TLocateExOptions): integer;

  { Edit }
    procedure AddCacheItem(CacheItem: TCacheItem);

  { CachedUpdates }
    function GetUpdatesPending: boolean; override;
    procedure SetFilterItemTypes(Value: TItemTypes); override;

  public
    constructor Create;
    destructor Destroy; override;

  { Open/Close }
    procedure Open; override;
    procedure Reopen; override;

  { Fields }
    procedure InitFields; override;
    procedure ClearFields; override;

  { Records }
    procedure GetRecord(RecBuf: IntPtr); override;
    procedure GetNextRecord(RecBuf: IntPtr); override;
    procedure GetPriorRecord(RecBuf: IntPtr); override;
    procedure PutRecord(RecBuf: IntPtr); override;
    procedure AppendRecord(RecBuf: IntPtr); override;
    procedure InsertRecord(RecBuf: IntPtr); override;
    procedure UpdateRecord(RecBuf: IntPtr); override;
    procedure DeleteRecord; override;
    procedure AddRecord(RecBuf: IntPtr);
    procedure RemoveRecord;  // remove record from memory


    function OmitRecord(Item: PItemHeader): boolean;
    procedure UpdateCachedBuffer(FItem, LItem: PItemHeader); // FItem and LItem can be nil. In this case FirstItem and LastItem used

  { Navigation }
    procedure SetToBegin; override;
    procedure SetToEnd; override;
    procedure PrepareRecNoCache;

  { BookMarks }
    procedure GetBookmark(Bookmark: PRecBookmark); override;
    procedure SetToBookmark(Bookmark: PRecBookmark); override;
    function BookmarkValid(Bookmark: PRecBookmark): boolean; override;
    function CompareBookmarks(Bookmark1, Bookmark2: PRecBookmark): integer; override;

  { CachedUpdates }
    function GetUpdateStatus: TItemStatus; override;
    function GetUpdateResult: TUpdateRecAction; override;

    procedure SetCacheRecBuf(NewBuf: IntPtr; OldBuf: IntPtr); override;
    procedure ApplyUpdates; override;
    procedure CommitUpdates; override;
    procedure CancelUpdates; override;
    procedure RestoreUpdates; override;
    procedure RevertRecord; override;

    procedure GetOldRecord(RecBuf: IntPtr); override;

  { Filter }
    function CompareFieldValue(ValuePtr: IntPtr; const ValueType: integer; FieldDesc: TFieldDesc; RecBuf: IntPtr; const Options: TLocateExOptions): integer; virtual;
    function CompareFields(RecBuf1: IntPtr; RecBuf2: IntPtr; SortColumn: TSortColumn): integer; overload; virtual;
    function CompareFields(RecBuf1: IntPtr; RecBuf2: IntPtr; FieldDesc: TFieldDesc; Options: TLocateExOptions = []): integer; overload; virtual;
    procedure FilterUpdated; override;

  { Sorting }
    procedure SetIndexFieldNames(Value: string); virtual;
    procedure SortItems; virtual;

    property IndexFields: TSortColumns read FIndexFields;
  end;

{ TBlob }

{$IFDEF CLR}
  PPieceHeader = packed record
  private
    Ptr: IntPtr;

    function GetBlob: integer;
    procedure SetBlob(Value: integer);
    function GetSize: cardinal;
    procedure SetSize(Value: cardinal);
    function GetUsed: cardinal;
    procedure SetUsed(Value: cardinal);
    function GetPrev: PPieceHeader;
    procedure SetPrev(Value: PPieceHeader);
    function GetNext: PPieceHeader;
    procedure SetNext(Value: PPieceHeader);

  public
    property Blob: integer read GetBlob write SetBlob;
    property Size: cardinal read GetSize write SetSize;
    property Used: cardinal read GetUsed write SetUsed;
    property Prev: PPieceHeader read GetPrev write SetPrev;
    property Next: PPieceHeader read GetNext write SetNext;

    class operator Implicit(AValue: IntPtr): PPieceHeader;
    class operator Implicit(AValue: PPieceHeader): IntPtr;
    class operator Implicit(AValue: PPieceHeader): integer;
    class operator Equal(ALeft, ARight: PPieceHeader): boolean;
  end;
{$ELSE}
  PPieceHeader = ^TPieceHeader;
{$ENDIF}
  TPieceHeader = packed record
    Blob: integer;
    Size: cardinal;
    Used: cardinal;  // offest 8 uses GetUsedPtr
    Prev: PPieceHeader;
    Next: PPieceHeader;
    Test: word;       // DEBUG
  end;

  TBlob = class (TSharedObject)
  protected
    FIsUnicode: boolean;

    FFirstPiece: PPieceHeader;
    FNeedRollback: boolean;
    Rollback: TBlob;

    // Used to detect a need to write LOB parameters before executing statement
    FModified: boolean;

    function GetAsString: string; virtual;
    procedure SetAsString(Value: string); virtual;

    function GetAsWideString: WideString; virtual;
    procedure SetAsWideString(Value: WideString); virtual;

    procedure AddCRUnicode;
    procedure RemoveCRUnicode;
    procedure AddCRString;
    procedure RemoveCRString;

    procedure CheckValid;   // DEBUG
    procedure CheckCached;

    procedure CheckValue; virtual;

    procedure SaveToRollback; virtual;

    function GetDataSize: cardinal; // sum of pieces.used
    function GetSize: cardinal; virtual; // if uncompressed then equal to GetDataSize else uncompressed size
    procedure SetSize(Value: cardinal); virtual;
    procedure SetIsUnicode(Value: boolean); virtual;
    procedure InternalClear;

  { Unicode to Ansi conversion methods }
    function TranslatePosition(Position: integer): integer; // Ansi to Unicode
    function GetSizeAnsi: integer;

  public
    PieceSize: cardinal;
    Test: byte;   // DEBUG

    constructor Create(IsUnicode: boolean = False);
    destructor Destroy; override;

  { Pieces }
    procedure AllocPiece(var Piece: PPieceHeader; Size: cardinal);
    procedure ReallocPiece(var Piece: PPieceHeader; Size: cardinal);
    procedure FreePiece(Piece: PPieceHeader);
    procedure AppendPiece(Piece: PPieceHeader);
    procedure DeletePiece(Piece: PPieceHeader);
    procedure CompressPiece(var Piece: PPieceHeader);

    function FirstPiece: PPieceHeader;

    function Read(Position: cardinal; Count: cardinal; Dest: IntPtr): cardinal; virtual;
    procedure Write(Position: cardinal; Count: cardinal; Source: IntPtr); virtual;
    procedure Clear; virtual;
    procedure Truncate(NewSize: cardinal); virtual;
    procedure Compress;
    procedure Defrag; virtual; // Move all data to first piece
    procedure AddCR;
    procedure RemoveCR;

  { Stream/File }

    procedure LoadFromStream(Stream: TStream); virtual;
    procedure SaveToStream(Stream: TStream); virtual;

    procedure LoadFromFile(const FileName: string);
    procedure SaveToFile(const FileName: string);

    procedure Assign(Source: TBlob);

  { Rollback }
    procedure EnableRollback;
    procedure Commit; virtual;
    procedure Cancel; virtual;
    function CanRollback: boolean;

    property Size: cardinal read GetSize write SetSize;
    property AsString: string read GetAsString write SetAsString;
    property AsWideString: WideString read GetAsWideString write SetAsWideString;
    property IsUnicode: boolean read FIsUnicode write SetIsUnicode;
    property Modified: boolean read FModified;
  end;

const
{$IFDEF CLR}
  DefaultPieceSize: longint = 64*1024 - 22;
{$ELSE}
  DefaultPieceSize: longint = 64*1024 - sizeof(TPieceHeader);  
{$ENDIF}

type
  TBlobUtils = class
  public
    class procedure SetModified(Blob: TBlob; Value: boolean);
  end;

{$IFDEF HAVE_COMPRESS}

{ TCompressedBlob }

const
  CCompressBlobHeaderGuidSize = 16;
  CCompressBlobHeaderSize = CCompressBlobHeaderGuidSize{guid} + SizeOf(Integer){uncompressed size};
  CCompressBlobHeaderGuid: array [0..CCompressBlobHeaderGuidSize - 1] of byte = ($39, $8C, $9D, $F1, $58, $55, $49, $38, $A6, $52, $87, $CE, $E0, $C6, $DA, $7E);

type
  TCompressBlobMode = (
    cbNone, // uncompressed (default)
    cbClient, // store compressed data on client. Save client memory. Other apps can read and write BLOBs on server
    cbServer, // store compressed data on server. Save server memory. Other apps can NOT read and write BLOBs on server
    cbClientServer // store compressed data on client and server.
  );

  TCompressedBlob = class(TBlob)
  protected
    function GetCompressed: boolean;
    procedure SetCompressed(Value: boolean);
    function UnCompressedSize: cardinal;

    function GetSize: cardinal; override;
    procedure SetSize(Value: cardinal); override;
    function GetCompressedSize: cardinal;
    procedure SaveToRollback; override;

    function CompressFrom(source: IntPtr; const sourceLen: longint): boolean;
    procedure UncompressTo(dest: IntPtr; var destlen: integer);

  public
    function Read(Position: cardinal; Count: cardinal; Dest: IntPtr): cardinal; override;
    procedure Write(Position: cardinal; Count: cardinal; Source: IntPtr); override;
    procedure Truncate(NewSize: cardinal); override;
    property Compressed: boolean read GetCompressed write SetCompressed;
    property CompressedSize: cardinal read GetCompressedSize;
  end;
{$ELSE}
type
  TCompressedBlob = class(TBlob);
{$ENDIF}

{ TVariantObject }

  TVariantObject = class (TSharedObject)
  private
    FValue: Variant;

  public
    property Value: Variant read FValue write FValue;
  end;

  function NextPiece(Piece: PPieceHeader): PPieceHeader;
  function PieceData(Piece: PPieceHeader): IntPtr;
  function PieceUsedPtr(Piece: PPieceHeader): IntPtr;

  procedure DataError(Msg: string);

const
  MaxArrayItem: integer = 100; // Max count of fields from array type

{$IFDEF CRDEBUG}
  ShareObjectCnt: integer = 0;
{$ENDIF}

  varDecimal  = $000E;
  varLongWord = $0013;
{$IFNDEF VER6P}
  varInt64    = $0014;

type
  TVarDataD6 = packed record // TVarData from Delphi 6
    VType: word;
    case Integer of
      0: (Reserved1: Word;
        case Integer of
          0: (Reserved2, Reserved3: Word;
            case Integer of
              varLongWord: (VLongWord: LongWord);
              varDecimal: (VInt64: Int64);
          );
      );
  end;

{$ENDIF}

var
  StartWaitProc: procedure;
  StopWaitProc: procedure;
  ApplicationTitleProc: function: string;
{$IFNDEF VER6P}
  ApplicationHandleException: procedure (Sender: TObject) of object;
{$ENDIF}

procedure StartWait;
procedure StopWait;
function ApplicationTitle: string;

function AddCRString(Source, Dest: IntPtr; Count: integer): integer; overload;
function RemoveCRString(Source, Dest: IntPtr; DestLen, Count: integer): integer; overload;

function AddCRUnicode(Source, Dest: IntPtr; Count: integer): integer; overload;
function RemoveCRUnicode(Source, Dest: IntPtr; DestLen, Count: integer): integer; overload;

implementation
uses
  DAConsts, SysUtils, Math;

const
  lxEqual           = 1;
  lxMore            = lxEqual + 1;
  lxLess            = lxMore + 1;
  lxMoreEqual       = lxLess + 1;
  lxLessEqual       = lxMoreEqual + 1;
  lxNoEqual         = lxLessEqual + 1;
  lxLeftBracket     = lxNoEqual + 1;
  lxRightBracket    = lxLeftBracket + 1;
  lxMinus           = lxRightBracket + 1;
  lxPlus            = lxMinus + 1;
  lxLeftSqBracket   = lxPlus + 1;
  lxRightSqBracket  = lxLeftSqBracket + 1;

  lxAND             = lxRightSqBracket + 1;
  lxFALSE           = lxAND + 1;
  lxIS              = lxFALSE + 1;
  lxLIKE            = lxIS + 1;
  lxNOT             = lxLIKE + 1;
  lxNULL            = lxNOT + 1;
  lxOR              = lxNULL + 1;
  lxTRUE            = lxOR + 1;

var
  BoolSymbolLexems, BoolKeywordLexems: TStringList;
  RefreshIteration: longint;

procedure DataError(Msg: string);
begin
  raise Exception.Create(Msg);
end;

procedure StartWait;
begin
  if Assigned(StartWaitProc) then
    StartWaitProc;
end;

procedure StopWait;
begin
  if Assigned(StopWaitProc) then
    StopWaitProc;
end;

function ApplicationTitle: string;
begin
  if Assigned(ApplicationTitleProc) then
    Result := ApplicationTitleProc
  else
    Result := '';
end;

{$IFDEF CLR}

{ PBlockHeader }

function PBlockHeader.GetItemCount: word;
begin
  Result := Marshal.ReadInt16(Ptr);
end;

procedure PBlockHeader.SetItemCount(Value: word);
begin
  Marshal.WriteInt16(Ptr, Value);
end;

function PBlockHeader.GetUsedItems: word;
begin
  Result := Marshal.ReadInt16(Ptr, sizeof(word));
end;

procedure PBlockHeader.SetUsedItems(Value: word);
begin
  Marshal.WriteInt16(Ptr, sizeof(word), Value);
end;

function PBlockHeader.GetPrev: PBlockHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(word) * 2);
end;

procedure PBlockHeader.SetPrev(Value: PBlockHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(word) * 2, Value.Ptr);
end;

function PBlockHeader.GetNext: PBlockHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(word) * 2 + sizeof(PBlockHeader));
end;

procedure PBlockHeader.SetNext(Value: PBlockHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(word) * 2 + sizeof(PBlockHeader), Value.Ptr);
end;

class operator PBlockHeader.Implicit(AValue: IntPtr): PBlockHeader;
begin
  Result.Ptr := AValue;
end;

class operator PBlockHeader.Implicit(AValue: PBlockHeader): IntPtr;
begin
  Result := AValue.Ptr;
end;

class operator PBlockHeader.Implicit(AValue: PBlockHeader): integer;
begin
  Result := AValue.Ptr.ToInt32;
end;

class operator PBlockHeader.Equal(ALeft, ARight: PBlockHeader): boolean;
begin
  Result := ALeft.Ptr = ARight.Ptr;
end;

{ PItemHeader }

function PItemHeader.GetBlock: PBlockHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr);
end;

procedure PItemHeader.SetBlock(Value: PBlockHeader);
begin
  Marshal.WriteIntPtr(Ptr, Value.Ptr);
end;

function PItemHeader.GetPrev: PItemHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(PBlockHeader));
end;

procedure PItemHeader.SetPrev(Value: PItemHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(PBlockHeader), Value.Ptr);
end;

function PItemHeader.GetNext: PItemHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader));
end;

procedure PItemHeader.SetNext(Value: PItemHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader), Value.Ptr);
end;

function PItemHeader.GetRollback: PItemHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 2);
end;

procedure PItemHeader.SetRollback(Value: PItemHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 2, Value.Ptr);
end;

function PItemHeader.GetStatus: TItemStatus;
begin
  Result := TItemStatus(Marshal.ReadByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3));
end;

procedure PItemHeader.SetStatus(Value: TItemStatus);
begin
  Marshal.WriteByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3, byte(Value));
end;

function PItemHeader.GetUpdateResult: TUpdateRecAction;
begin
  Result := TUpdateRecAction(Marshal.ReadByte(Ptr, sizeof(PBlockHeader) +
    sizeof(PItemHeader) * 3 + sizeof(TItemStatus)));
end;

procedure PItemHeader.SetUpdateResult(Value: TUpdateRecAction);
begin
  Marshal.WriteByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus), byte(Value));
end;

function PItemHeader.GetOrder: longint;
begin
  Result := Marshal.ReadInt32(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus) + sizeof(TUpdateRecAction));
end;

procedure PItemHeader.SetOrder(Value: longint);
begin
  Marshal.WriteInt32(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus) + sizeof(TUpdateRecAction), Value);
end;

function PItemHeader.GetFlag: byte;
begin
  Result := Marshal.ReadByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus) + sizeof(TUpdateRecAction) + sizeof(longint));
end;

procedure PItemHeader.SetFlag(Value: byte);
begin
  Marshal.WriteByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus) + sizeof(TUpdateRecAction) + sizeof(longint), Value);
end;

function PItemHeader.GetFilterResult: TItemFilterState;
begin
  Result := TItemFilterState(Marshal.ReadByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus) + sizeof(TUpdateRecAction) + sizeof(longint) +
    SizeOf(byte)));
end;

procedure PItemHeader.SetFilterResult(Value: TItemFilterState);
begin
  Marshal.WriteByte(Ptr, sizeof(PBlockHeader) + sizeof(PItemHeader) * 3 +
    sizeof(TItemStatus) + sizeof(TUpdateRecAction) + sizeof(longint) +
    SizeOf(byte), Byte(Value));
end;
  
class operator PItemHeader.Implicit(AValue: IntPtr): PItemHeader;
begin
  Result.Ptr := AValue;
end;

class operator PItemHeader.Implicit(AValue: PItemHeader): IntPtr;
begin
  Result := AValue.Ptr;
end;

class operator PItemHeader.Implicit(AValue: PItemHeader): integer;
begin
  Result := AValue.Ptr.ToInt32;
end;

class operator PItemHeader.Equal(ALeft, ARight: PItemHeader): boolean;
begin
  Result := ALeft.Ptr = ARight.Ptr;
end;

{ PRecBookmark }

function PRecBookmark.GetRefreshIteration: longint;
begin
  Result := Marshal.ReadInt32(Ptr);
end;

procedure PRecBookmark.SetRefreshIteration(Value: longint);
begin
  Marshal.WriteInt32(Ptr, Value);
end;

function PRecBookmark.GetItem: PItemHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(longint));
end;

procedure PRecBookmark.SetItem(Value: PItemHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(longint), Value);
end;

function PRecBookmark.GetOrder: longint;
begin
  Result := Marshal.ReadInt32(Ptr, sizeof(longint) + sizeof(PItemHeader));
end;

procedure PRecBookmark.SetOrder(Value: longint);
begin
  Marshal.WriteInt32(Ptr, sizeof(longint) + sizeof(PItemHeader), Value);
end;

class operator PRecBookmark.Implicit(AValue: IntPtr): PRecBookmark;
begin
  Result.Ptr := AValue;
end;

class operator PRecBookmark.Implicit(AValue: PRecBookmark): IntPtr;
begin
  Result := AValue.Ptr;
end;

class operator PRecBookmark.Implicit(AValue: integer): PRecBookmark;
begin
  Result.Ptr := IntPtr(AValue);
end;

{ PPieceHeader }

function PPieceHeader.GetBlob: integer;
begin
  Result := Marshal.ReadInt32(Ptr);
end;

procedure PPieceHeader.SetBlob(Value: integer);
begin
  Marshal.WriteInt32(Ptr, Value);
end;

function PPieceHeader.GetSize: cardinal;
begin
  Result := Marshal.ReadInt32(Ptr, sizeof(integer));
end;

procedure PPieceHeader.SetSize(Value: cardinal);
begin
  Marshal.WriteInt32(Ptr, sizeof(integer), Value);
end;

function PPieceHeader.GetUsed: cardinal;
begin
  Result := Marshal.ReadInt32(Ptr, sizeof(integer) * 2);
end;

procedure PPieceHeader.SetUsed(Value: cardinal);
begin
  Marshal.WriteInt32(Ptr, sizeof(integer) * 2, Value);
end;

function PPieceHeader.GetPrev: PPieceHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(integer) * 3);
end;

procedure PPieceHeader.SetPrev(Value: PPieceHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(integer) * 3, Value.Ptr);
end;

function PPieceHeader.GetNext: PPieceHeader;
begin
  Result := Marshal.ReadIntPtr(Ptr, sizeof(integer) * 3 + sizeof(PPieceHeader));
end;

procedure PPieceHeader.SetNext(Value: PPieceHeader);
begin
  Marshal.WriteIntPtr(Ptr, sizeof(integer) * 3 + sizeof(PPieceHeader), Value.Ptr);
end;

class operator PPieceHeader.Implicit(AValue: IntPtr): PPieceHeader;
begin
  Result.Ptr := AValue;
end;

class operator PPieceHeader.Implicit(AValue: PPieceHeader): IntPtr;
begin
  Result := AValue.Ptr;
end;

class operator PPieceHeader.Implicit(AValue: PPieceHeader): integer;
begin
  Result := AValue.Ptr.ToInt32;
end;

class operator PPieceHeader.Equal(ALeft, ARight: PPieceHeader): boolean;
begin
  Result := ALeft.Ptr = ARight.Ptr;
end;
{$ENDIF}

{ TFieldDesc }

constructor TFieldDesc.Create;
begin
  inherited;
end;

destructor TFieldDesc.Destroy;
begin
  if FObjectType <> nil then
    FObjectType.Release;

  inherited;
end;

function TFieldDesc.HasParent: boolean;
begin
  Result := FParentField <> nil;
end;

procedure TFieldDesc.Assign(FieldDesc:TFieldDesc);
begin
  Name := FieldDesc.Name;
  ActualName := FieldDesc.ActualName;
  DataType := FieldDesc.DataType;
  Length := FieldDesc.Length;
  Scale := FieldDesc.Scale;
  Size := FieldDesc.Size;
  Offset := FieldDesc.Offset;
  Required := FieldDesc.Required;
  FieldNo := FieldDesc.FieldNo;
end;

procedure TFieldDesc.SetObjectType(Value:TObjectType);
begin
  if Value <> FObjectType then begin
    if FObjectType <> nil then
      FObjectType.Release;

    FObjectType := Value;

    if FObjectType <> nil then
      FObjectType.AddRef;
  end;
end;

{ TFieldDescs }

destructor TFieldDescs.Destroy;
begin
  Clear;

  inherited;
end;

procedure TFieldDescs.Clear;
var
  i: integer;
begin
  for i := 0 to Count - 1 do
    if Items[i] <> nil then
      TFieldDesc(Items[i]).Free;

  inherited Clear;
end;

function TFieldDescs.FindField(Name: string):TFieldDesc;
var
  i: integer;
  ComplexField: boolean;
  Found: boolean;
begin
  Result := nil;
  ComplexField := (Pos('.', Name) > 0) or (Pos('[', Name) > 0);
  if not ComplexField then
    for i := 0 to Count - 1 do
      if (Items[i] <> nil) and (not TFieldDesc(Items[i]).HasParent) then begin
        Found := AnsiCompareText(TFieldDesc(Items[i]).Name, Name) = 0;

        if Found then begin
          Result := Items[i];
          Exit;
        end;
      end;
  for i := 0 to Count - 1 do
    if (Items[i] <> nil) then begin
      Found := False;
      if ComplexField then
        Found := AnsiCompareText(TFieldDesc(Items[i]).ActualName, Name) = 0
      else
      if (TFieldDesc(Items[i]).HasParent) then
        Found := AnsiCompareText(TFieldDesc(Items[i]).Name, Name) = 0;

      if Found then begin
        Result := Items[i];
        Exit;
      end;
    end;
end;

function TFieldDescs.FieldByName(Name: string): TFieldDesc;
begin
  Result := FindField(Name);

  if Result = nil then
    raise Exception.Create(Format(SFieldNotFound, [Name]));
end;

function TFieldDescs.GetItems(Index: integer): TFieldDesc;
begin
  Result := TFieldDesc(inherited Items[Index]);
end;

{ TAttribute }

constructor TAttribute.Create;
begin
  inherited;
end;

destructor TAttribute.Destroy;
begin
  if (FObjectType <> nil) and (FOwner.Name <> FObjectType.Name) then
    FObjectType.Release;

  inherited;
end;

procedure TAttribute.SetObjectType(Value:TObjectType);
begin
  if Value <> FObjectType then begin
    if FObjectType <> nil then
      FObjectType.Release;

    FObjectType := Value;

    if (FObjectType <> nil) and (FOwner.Name <> FObjectType.Name) then
      FObjectType.AddRef;
  end;
end;

{ TObjectType }

constructor TObjectType.Create;
begin
  inherited;

  FAttributes := TDAList.Create;
end;

destructor TObjectType.Destroy;
begin
  ClearAttributes;
  FAttributes.Free;

  inherited;
end;

{function TObjectType.AddAttribute:TAttribute;
begin
  Result := TAttribute.Create;
  FAttributes.Add(Result);
end;}

procedure TObjectType.ClearAttributes;
var
  i: integer;
begin
  for i := 0 to FAttributes.Count - 1 do
    TAttribute(FAttributes[i]).Free;
  FAttributes.Clear;
end;

function TObjectType.FindAttribute(Name: string):TAttribute;
var
  St: string;
  iPos,IndexPos: integer;
  i: integer;
  OType:TObjectType;
begin
  Name := AnsiUpperCase(Name);
  OType := Self;

  repeat
    Name := TrimLeft(Name);

    iPos := Pos('.', Name);
    IndexPos := Pos('[', Name);
    if IndexPos = 1 then begin
      i := Pos(']', Name);
      if i = 0 then begin
        Result := nil;
        Exit;
      end;
      if (i + 1 <= Length(Name)) and (Name[i + 1] = '.') then
        Inc(i);

      St := 'ELEMENT';
      Name := Copy(Name, i + 1, Length(Name));
    end
    else
      if (iPos > 0) and ((iPos < IndexPos) or (IndexPos = 0)) then begin
        St := Copy(Name, 1, iPos - 1);
        Name := Copy(Name, iPos + 1, Length(Name));
      end
      else
        if IndexPos > 0 then begin
          St := Copy(Name, 1, IndexPos - 1);
          Name := Copy(Name, IndexPos, Length(Name));
        end
        else
          St := Name;

    Result := nil;
    for i := 0 to OType.AttributeCount - 1 do
      if AnsiUpperCase(TAttribute(OType.Attributes[i]).Name) = St then begin
        Result := OType.Attributes[i];
        break;
      end;

    if (Result = nil) or not(Result.DataType in [dtObject,dtArray,dtTable,dtReference]) and
      (iPos <> 0)
    then begin
      Result := nil;
      Exit;
    end;

    OType := Result.ObjectType;
  until (iPos = 0) and ((IndexPos = 0) or (Name = ''));
end;

function TObjectType.AttributeByName(Name: string):TAttribute;
begin
  Result := FindAttribute(Name);
  if Result = nil then
    raise Exception.Create(Format(SAttributeNotFount, [Name]));
end;

function TObjectType.GetAttributes(Index: integer):TAttribute;
begin
  Result := TAttribute(FAttributes[Index]);
end;

function TObjectType.GetAttributeCount: integer;
begin
  Result := FAttributes.Count;
end;

{ TDBObject }

constructor TDBObject.Create;
begin
  inherited;
end;

procedure TDBObject.SetObjectType(Value:TObjectType);
begin
  if FObjectType <> nil then
    FObjectType.Release;

  FObjectType := Value;

  if FObjectType <> nil then
    FObjectType.AddRef;
end;

procedure TDBObject.GetAttributeValue(Name: string; Dest: IntPtr; var IsBlank: boolean);
begin
  IsBlank := True;
end;

procedure TDBObject.SetAttributeValue(Name: string; Source: IntPtr);
begin
end;

{ TBoolParser }

constructor TBoolParser.Create(const Text: string);
begin
  inherited Create(Text);

  FSymbolLexems := BoolSymbolLexems;
  FKeywordLexems := BoolKeywordLexems;
end;

procedure TBoolParser.ToRightQuote(LeftQuote: Char);
begin
  while (Pos <= TextLength) and (Text[Pos] <> LeftQuote) do begin
    Inc(Pos);
    if (Pos + 1 <= TextLength) and (Text[Pos] = '''') and (Text[Pos + 1] = '''') then
      Inc(Pos, 2);
  end;
end;

{ TData }

{$IFDEF CRDEBUG}
const
  DataCnt: integer = 0;
{$ENDIF}

constructor TData.Create;
begin
  inherited;

  FEOF := True;
  FBOF := True;
  FFields := TFieldDescs.Create;
  FAutoInitFields := True;
  FEnableEmptyStrings := False;

{$IFDEF CRDEBUG} Inc(DataCnt); {$ENDIF}
  StringHeap := TStringHeap.Create;
end;

destructor TData.Destroy;
begin
  Close;

  ClearFields;
  FFields.Free;
  StringHeap.Free;

  inherited;

{$IFDEF CRDEBUG} Dec(DataCnt); {$ENDIF}
end;

{ Data }

procedure TData.InitData;
begin
  FBOF := True;
  FEOF := True;
  FRecordCount := 0;
  FRecordNoOffset := 0;
end;

procedure TData.FreeData;
begin
  InitData;
end;

{ Open / Close }

procedure TData.InternalPrepare;
begin
end;

procedure TData.Prepare;
begin
  InternalPrepare;
  Prepared := True; // lost connection
end;

procedure TData.InternalUnPrepare;
begin
end;

procedure TData.UnPrepare;
begin
  if Prepared then begin
    Prepared := False;
    if FAutoInitFields then
      ClearFields;
    InternalUnPrepare;
  end;
end;

procedure TData.InternalOpen;
begin
end;

procedure TData.Open;
begin
  if not Active then begin
    InitData;
    try
      InternalOpen;
      CreateFilterExpression(FFilterText); // ???
    except
      FreeData;
      FreeFilterExpression;
      raise;
    end;

    Active := True;
  end;
end;

procedure TData.InternalClose;
begin
end;

procedure TData.Close;
begin
  try
    if Active then
      InternalClose;
  finally
    Active := False;
    FreeData;         // FreeData after for multithreads

    if FAutoInitFields and not Prepared then // After FreeData!
      ClearFields;

    FreeFilterExpression;
  end;
end;

function TData.IsFullReopen: boolean;
begin
  Result := True;
end;

procedure TData.Reopen;
begin
  Close;
  Open;
end;

{ Field }

function TData.GetFieldCount: word;
begin
  Result := FFields.Count;
end;

function TData.GetIndicatorSize: word;
begin
  Result := FieldCount;
end;

function TData.GetFieldDescType: TFieldDescClass;
begin
  Result := TFieldDesc;
end;

procedure TData.InternalInitFields;
begin
end;

function TData.GetArrayFieldName(ObjectType: TObjectType; ItemIndex: integer): string;
begin
  Result := '[' + IntToStr(ItemIndex) + ']';
end;

procedure TData.InitObjectFields(ObjectType:TObjectType; Parent: TFieldDesc);
var
  i: integer;
  Field:TFieldDesc;
  Item,CountItem: integer;
begin
  if (ObjectType.DataType in [dtObject,dtTable]) or FSparseArrays then
    CountItem := 1
  else begin
    CountItem := ObjectType.Size;
    if CountItem > MaxArrayItem then  // Restriction of array length
      CountItem := MaxArrayItem;
  end;

  for i := 0 to ObjectType.AttributeCount - 1 do begin
    for Item := 0 to CountItem - 1 do begin
      Field := GetFieldDescType.Create;
      Field.ParentField := Parent;
      if ObjectType.DataType in [dtObject,dtTable] then begin
        Field.Name := ObjectType.Attributes[i].Name;
        if Parent = nil then
          Field.ActualName := Field.Name
        else
          Field.ActualName := Parent.ActualName + '.' + Field.Name;
      end
      else begin
        Field.Name := GetArrayFieldName(ObjectType, Item);
        if Parent = nil then
          Field.ActualName := Field.Name
        else
          Field.ActualName := Parent.ActualName + Field.Name;
      end;

      Field.DataType := ObjectType.Attributes[i].DataType;
      Field.Size := 0;// ObjectType.Attributes[i].Size;
      Field.Fixed := ObjectType.Attributes[i].Fixed;
      Field.Length := ObjectType.Attributes[i].Length;
      Field.FieldNo := FFields.Count + 1;
      Field.ObjectType := ObjectType.Attributes[i].ObjectType;
      if Parent <> nil then
        Field.ReadOnly := Parent.ReadOnly;
      FFields.Add(Field);

      if Field.DataType in [dtObject,dtArray] then
        InitObjectFields(Field.ObjectType, Field);
    end;
  end;
end;

function CompareAlias(Field1, Field2: {$IFDEF CLR}TObject{$ELSE}pointer{$ENDIF}): integer;
begin
  if Field1 = Field2 then
    Result := 0
  else begin
    Result := AnsiCompareText(TFieldDesc(Field1).Name, TFieldDesc(Field2).Name);
    if Result = 0 then begin
      Result := TFieldDesc(Field1).FieldNo - TFieldDesc(Field2).FieldNo;
      TFieldDesc(Field1).FReserved := True;
      TFieldDesc(Field2).FReserved := True;
    end;
  end
end;

procedure TData.InitFields;
var
  i: integer;

  // perfomance optimization for many fields set aliases
  procedure InitAliases;
  var
    AliasNum, AliasLen: integer;
    AFields: TDAList;
    i: integer;
    s: string;

    procedure ReplaceNextOriginalNames(StartName: string; StartInd: integer);
    var
      i, Res: integer;
      AliasNum: integer;
      S: string;
    begin
      AliasNum := 1;
      for i := StartInd to AFields.Count - 1 do begin
        S := TFieldDesc(AFields[i]).Name;
        Res := AnsiCompareTextS(StartName, S);
        if (Res < 0) then
          break;
        if (Res = 0) then begin
          TFieldDesc(AFields[i]).Name := S + '_' + IntToStr(AliasNum);
          Inc(AliasNum);
          ReplaceNextOriginalNames(TFieldDesc(AFields[i]).Name, i + 1);
        end;
      end;
    end;
  begin
    AFields := TDAList.Create;
    try
      AFields.Capacity := FFields.Capacity;
      for i := 0 to FFields.Count - 1 do
        if (FFields[i] <> nil) and (TFieldDesc(FFields[i]).ParentField = nil) then
          AFields.Add(FFields[i]);

      AFields.Sort(CompareAlias);
      AliasNum := 0;
      for i := 0 to AFields.Count - 1 do
        if (TFieldDesc(AFields[i]).FReserved) or (TFieldDesc(AFields[i]).Name = '') then begin
          if (AliasNum > 1) then begin
            s := TFieldDesc(AFields[i-1]).Name;
            AliasLen := 1 {'_'} + Length(IntToStr((AliasNum - 1)));
            SetLength(s, Length(s) - AliasLen);
            if (AnsiCompareText(s, TFieldDesc(AFields[i]).Name) <> 0) then
              AliasNum := 0;
          end;
          if (AliasNum <> 0) or (TFieldDesc(AFields[i]).Name = '') then begin
            TFieldDesc(AFields[i]).Name := TFieldDesc(AFields[i]).Name + '_' + IntToStr(AliasNum);
            ReplaceNextOriginalNames(TFieldDesc(AFields[i]).Name, i + 1);
          end;
          Inc(AliasNum);
        end else
          AliasNum := 0;
    finally
      AFields.Free;
    end;
  end;

var
  Off, AlignOff: integer;
  FieldDesc: TFieldDesc;
begin
  if FAutoInitFields then begin
    ClearFields;
    InternalInitFields;
    if Assigned(FOnGetCachedFields) then
      FOnGetCachedFields();
    InitAliases;
  end;

  DataSize := 0;
  CalcDataSize := 0;
  for i := 0 to FieldCount - 1 do
    if Fields[i].FieldDescKind <> fdkCalculated then begin
      FieldDesc := Fields[i];
      FieldDesc.Offset := DataSize;

      if FieldDesc.DataType = dtWideString then begin
        Off := FieldDesc.Offset;
        AlignOff := Off and 1; // Fields[i].Offset mod 2;
        FieldDesc.Offset := Off + AlignOff; // align WideString field offset
      end
      else
        AlignOff := 0;

      DataSize := DataSize + FieldDesc.Size + AlignOff;
    end;

  FRecordSize := DataSize + IndicatorSize;
  FRecordSize := FRecordSize + (FRecordSize + 1) mod 2; //align

  for i := 0 to FieldCount - 1 do
    if Fields[i].FieldDescKind = fdkCalculated then begin
      FieldDesc := Fields[i];
      FieldDesc.Offset := FRecordSize + CalcDataSize;

      if FieldDesc.DataType = dtWideString then begin
        Off := FieldDesc.Offset;
        AlignOff := Off and 1; // Fields[i].Offset mod 2;
        FieldDesc.Offset := Off + AlignOff; // align WideString field offset
      end
      else
        AlignOff := 0;

      CalcDataSize := CalcDataSize + FieldDesc.Size + AlignOff;
    end;

  FCalcRecordSize := CalcDataSize;
  if FCalcRecordSize > 0 then
    FCalcRecordSize := FCalcRecordSize + (FCalcRecordSize + 1) mod 2; //align
  CheckHasComplexFields;
end;

procedure TData.ClearFields;
begin
  FFields.Clear;
end;

procedure TData.GetDateFromBuf(Buf: IntPtr; Offset: integer; Date: IntPtr; Format: TDateFormat);
var
  DateTime: double;
begin
  DateTime := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Buf, Offset));
  case Format of
    dfMSecs: begin
      DateTime := TimeStampToMSecs(DateTimeToTimeStamp(DateTime));
      Marshal.WriteInt64(Date, BitConverter.DoubleToInt64Bits(DateTime));
    end;
    dfDateTime:
      Marshal.WriteInt64(Date, BitConverter.DoubleToInt64Bits(DateTime));
    dfDate:
      Marshal.WriteInt32(Date, DateTimeToTimeStamp(DateTime).Date);
    dfTime:
      Marshal.WriteInt32(Date, DateTimeToTimeStamp(DateTime).Time);
  end;
end;

procedure TData.PutDateToBuf(Buf: IntPtr; Offset: integer; Date: IntPtr; Format: TDateFormat);
var
  Ts: TTimeStamp;
  DateTime: TDateTime;
begin
  case Format of
    dfMSecs: begin
    {$IFDEF CLR}
      DateTime := MemUtils.TimeStampToDateTime(MSecsToTimeStamp(Trunc(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Date)))));
    {$ELSE}
      DateTime := MemUtils.TimeStampToDateTime(MSecsToTimeStamp(TDateTime(Date^)));
    {$ENDIF}
      Marshal.WriteInt64(Buf, Offset, BitConverter.DoubleToInt64Bits(DateTime));
    end;
    dfDateTime:
      Marshal.WriteInt64(Buf, Offset, Marshal.ReadInt64(Date));
    dfDate: begin
      Ts.Date := Marshal.ReadInt32(Date);
      Ts.Time := 0;
      Marshal.WriteInt64(Buf, Offset, BitConverter.DoubleToInt64Bits(MemUtils.TimeStampToDateTime(Ts)));
    end;
    dfTime: begin
      Ts.Date := DateDelta;
      Ts.Time := Marshal.ReadInt32(Date);
      Marshal.WriteInt64(Buf, Offset, BitConverter.DoubleToInt64Bits(MemUtils.TimeStampToDateTime(Ts)));
    end;
  end;
end;

procedure TData.GetChildFieldInfo(Field: TFieldDesc; var RootField: TFieldDesc; var AttrName: string);
begin
  AttrName := '';
  repeat
    if AttrName = '' then
      AttrName := Field.Name
    else
      if Field.DataType = dtArray then
        AttrName := Field.Name + AttrName
      else
        AttrName := Field.Name + '.' + AttrName;
    Field := Field.ParentField;
  until not Field.HasParent;
  RootField := Field;
end;

procedure TData.GetChildField(Field: TFieldDesc; RecBuf: IntPtr; Dest: IntPtr; var IsBlank: boolean);
var
  DBObject: IntPtr;
  AttrName: string;
begin
  GetChildFieldInfo(Field, Field, AttrName);
  DBObject := Marshal.ReadIntPtr(RecBuf, Field.Offset);
  if DBObject <> nil then
    TDBObject(GetGCHandleTarget(DBObject)).GetAttributeValue(AttrName, Dest, IsBlank)
  else
    IsBlank := True;
end;

procedure TData.PutChildField(Field: TFieldDesc; RecBuf: IntPtr; Source: IntPtr);
var
  DBObject: IntPtr;
  AttrName: string;
begin
  GetChildFieldInfo(Field, Field, AttrName);
  DBObject := Marshal.ReadIntPtr(RecBuf, Field.Offset);
  if DBObject <> nil then
    TDBObject(GetGCHandleTarget(DBObject)).SetAttributeValue(AttrName, Source);
end;

const
  CRLF = $0A0D;
  LF   = $0A;
  CRLF_UTF16 = $000A000D;
  LF_UTF16   = $000A;

function AddCRString(Source, Dest: IntPtr; Count: integer): integer;
var
  SourceEnd: IntPtr;
  w: word;
  b: byte;
begin
  Result := Count;
  SourceEnd := IntPtr(Integer(Source) + Count);
  while Integer(Source) < Integer(SourceEnd) do begin
    w := Marshal.ReadInt16(Source);
    if w = CRLF then begin
      Marshal.WriteInt16(Dest, w);
      Source := IntPtr(Integer(Source) + 2);
      Dest := IntPtr(Integer(Dest) + 2);
    end
    else begin
      b := Byte(w);
      if b = 0 then begin
        Dec(Result, Integer(SourceEnd) - Integer(Source));
        break;
      end
      else      
      if b = LF then begin
        Marshal.WriteInt16(Dest, CRLF);
        Source := IntPtr(Integer(Source) + 1);
        Dest := IntPtr(Integer(Dest) + 2);
        Inc(Result);
      end
      else begin
        Marshal.WriteByte(Dest, b);
        Source := IntPtr(Integer(Source) + 1);
        Dest := IntPtr(Integer(Dest) + 1);
      end;
    end;
  end;
  Marshal.WriteByte(Dest, 0);
end;

function RemoveCRString(Source, Dest: IntPtr; DestLen, Count: integer): integer;
var
  SourceEnd: IntPtr;
  DestStart: IntPtr;
  w: word;
begin
  Result := Count;
  SourceEnd := IntPtr(Integer(Source) + Count);
  DestStart := Dest;
  while (Integer(Source) < Integer(SourceEnd)) and (Integer(Dest) - Integer(DestStart) < DestLen) do begin
    w := Marshal.ReadInt16(Source);
    if w = CRLF then begin
      Marshal.WriteByte(Dest, LF);
      Source := IntPtr(Integer(Source) + 2);
      Dec(Result);
      Dest := IntPtr(Integer(Dest) + 1);
    end
    else
    begin
      Marshal.WriteByte(Dest, Byte(w));
      Source := IntPtr(Integer(Source) + 1);
      Dest := IntPtr(Integer(Dest) + 1);
    end;
  end;
  Marshal.WriteByte(Dest, 0);
end;

function AddCRUnicode(Source, Dest: IntPtr; Count: integer): integer;
var
  SourceEnd: IntPtr;
  w: LongWord;
  b: word;
begin
  Result := Count;
  SourceEnd := IntPtr(Integer(Source) + Count * 2);
  while Integer(Source) < Integer(SourceEnd) do begin
    w := Marshal.ReadInt32(Source);
    if w = CRLF_UTF16 then begin
      Marshal.WriteInt32(Dest, w);
      Source := IntPtr(Integer(Source) + 4);
      Dest := IntPtr(Integer(Dest) + 4);
    end
    else begin
      b := Word(w);
      if b = 0 then begin
        Dec(Result, (Integer(SourceEnd) - Integer(Source)) div 2);
        break;
      end
      else      
      if b = LF_UTF16 then begin
        Marshal.WriteInt32(Dest, CRLF_UTF16);
        Source := IntPtr(Integer(Source) + 2);
        Dest := IntPtr(Integer(Dest) + 4);
        Inc(Result);
      end
      else begin
        Marshal.WriteInt16(Dest, b);
        Source := IntPtr(Integer(Source) + 2);
        Dest := IntPtr(Integer(Dest) + 2);
      end;
    end;
  end;
  Marshal.WriteInt16(Dest, 0);
end;

function RemoveCRUnicode(Source, Dest: IntPtr; DestLen, Count: integer): integer;
var
  SourceEnd: IntPtr;
  DestStart: IntPtr;
  w: LongWord;
begin
  Result := Count;
  SourceEnd := IntPtr(Integer(Source) + Count * 2);
  DestStart := Dest;
  while (Integer(Source) < Integer(SourceEnd)) and (Integer(Dest) - Integer(DestStart) < DestLen * 2) do begin
    w := Marshal.ReadInt32(Source);
    if w = CRLF_UTF16 then begin
      Marshal.WriteInt16(Dest, LF_UTF16);
      Source := IntPtr(Integer(Source) + 4);
      Dec(Result);
      Dest := IntPtr(Integer(Dest) + 2);
    end
    else
    begin
      Marshal.WriteInt16(Dest, Word(w));
      Source := IntPtr(Integer(Source) + 2);
      Dest := IntPtr(Integer(Dest) + 2);
    end;
  end;
  Marshal.WriteInt16(Dest, 0);
end;

function TData.NeedConvertEOL: boolean;
begin
  Result := False;
end;

procedure TData.GetFieldData(Field: TFieldDesc; RecBuf: IntPtr; Dest: IntPtr);
var
  Data: IntPtr;
begin
  case Field.DataType of
    dtUInt32:
      Marshal.WriteInt64(Dest, Longword(Marshal.ReadInt32(RecBuf, Field.Offset)));
    dtDateTime:
      GetDateFromBuf(RecBuf, Field.Offset, Dest, dfMSecs);
    dtDate:
      GetDateFromBuf(RecBuf, Field.Offset, Dest, dfDate);
    dtTime:
      GetDateFromBuf(RecBuf, Field.Offset, Dest, dfTime);
  {$IFDEF VER5P}
    dtVariant:
    {$IFDEF CLR}
      Assert(False);
    {$ELSE}
      Variant(Dest^) := TVariantObject(Marshal.ReadIntPtr(RecBuf, Field.Offset)).Value;
    {$ENDIF}
  {$ENDIF}
  {$IFDEF VER6P}
    dtFmtBCD:
      CopyBuffer(IntPtr(Integer(RecBuf) + Field.Offset), Dest, SizeOfTBcd); // To avoid errors if Field.Size > SizeOfTBcd 
  {$ENDIF}
    dtExtString: begin
      Assert(Marshal.ReadIntPtr(RecBuf, Field.Offset) <> nil);
      if NeedConvertEOL then
        AddCRString(Marshal.ReadIntPtr(RecBuf, Field.Offset), Dest, MaxInt div 2)
      else
        StrCopy(Dest, Marshal.ReadIntPtr(RecBuf, Field.Offset));
    end;
    dtExtWideString: begin
      Assert(Marshal.ReadIntPtr(RecBuf, Field.Offset) <> nil);
      if NeedConvertEOL then
        AddCRUnicode(Marshal.ReadIntPtr(RecBuf, Field.Offset), Dest, MaxInt div 4)
      else
        StrCopyW(Dest, Marshal.ReadIntPtr(RecBuf, Field.Offset));
    end;
    dtExtVarBytes: begin
      Data := Marshal.ReadIntPtr(RecBuf, Field.Offset);
      CopyBuffer(Data, Dest, Marshal.ReadInt16(Data) + SizeOf(Word));
    end;
    dtString:
      if NeedConvertEOL then
        AddCRString(IntPtr(Integer(RecBuf) + Field.Offset), Dest, Field.Size)
      else
        StrLCopy(Dest, IntPtr(Integer(RecBuf) + Field.Offset), Field.Size);
    dtWideString: begin
      if NeedConvertEOL then
        AddCRUnicode(IntPtr(Integer(RecBuf) + Field.Offset), Dest, Field.Size)
      else
        StrLCopyW(Dest, IntPtr(Integer(RecBuf) + Field.Offset), Field.Size);
    end;
  {$IFDEF CLR}
    dtBytes:
      CopyBuffer(IntPtr(Integer(RecBuf) + Field.Offset), Dest, Field.Length);
  {$ENDIF}
  else
    CopyBuffer(IntPtr(Integer(RecBuf) + Field.Offset), Dest, Field.Size);
  end;
end;

procedure TData.GetField(FieldNo: word; RecBuf: IntPtr; Dest: IntPtr; var IsBlank: boolean);
var
  Field: TFieldDesc;
  DataType: word;
  t: boolean;
  l: integer;

begin
  Assert((FieldNo <= FieldCount) and (FieldNo > 0));

  IsBlank := GetNull(FieldNo, RecBuf);

  Field := Fields[FieldNo - 1];

  DataType := Field.DataType;
  if (Dest = nil) or IsBlank and (not IsComplexFieldType(DataType)
    or (DataType = dtExtString) or (DataType = dtExtWideString)
    or (DataType = dtExtVarBytes))
  then
    Exit;

  if not Field.HasParent then
    GetFieldData(Field, RecBuf, Dest)
  else
    GetChildField(Field, RecBuf, Dest, IsBlank);

  if not IsBlank and (Field.DataType in [dtString, dtWideString]) then begin// trim fixed char values
    if Field.Fixed then begin
      t := FTrimFixedChar;
      l := Field.Length;
    end
    else begin
      t := FTrimVarChar;
      l := -1;
    end;
    if t then
      if Field.DataType = dtString then
        StrTrim(Dest, l)
      else
        StrTrimW(Dest, l);
  end;
end;

function TData.GetFieldBuf(RecBuf: IntPtr; FieldDesc: TFieldDesc; var DataType: integer; var IsBlank, NativeBuffer: boolean): IntPtr;
var
  FieldBufStatic: IntPtr;
  ValueBuf: IntPtr;
  Len: integer;
begin
  NativeBuffer := True;
  FieldBufStatic := nil;
  ValueBuf := nil;
  try
    if FieldDesc.ParentField = nil then begin
      Result := IntPtr(integer(RecBuf) + FieldDesc.Offset);
      IsBlank := GetNull(FieldDesc.FieldNo, RecBuf);
    end
    else begin
    // support objects
      FieldBufStatic := Marshal.AllocHGlobal(4001);
      Result := FieldBufStatic;
      GetField(FieldDesc.FieldNo, RecBuf, Result, IsBlank);  // GetChildField
    end;

    if not IsBlank then begin
      DataType := FieldDesc.DataType;
      case DataType of
        dtExtString: begin
          Result := Marshal.ReadIntPtr(Result);
          DataType := dtString;
        end;
        dtExtWideString: begin
          Result := Marshal.ReadIntPtr(Result);
          DataType := dtWideString;
        end;
        dtExtVarBytes:
          Result := Marshal.ReadIntPtr(Result);
        dtBCD: begin
          ValueBuf := Marshal.AllocHGlobal(SizeOf(double));
          Marshal.WriteInt64(ValueBuf, BitConverter.DoubleToInt64Bits(Marshal.ReadInt64(Result) / 10000));
          Result := ValueBuf;
        end;
        dtDateTime, dtDate, dtTime: begin
          ValueBuf := Marshal.AllocHGlobal(SizeOf(double));
          GetDateFromBuf(RecBuf, FieldDesc.Offset, ValueBuf, dfDateTime);
          Result := ValueBuf;
        end;
        dtBytes: begin
          ValueBuf := Marshal.AllocHGlobal(FieldDesc.Length + SizeOf(Word));
          Marshal.WriteInt16(ValueBuf, FieldDesc.Length);
          CopyBuffer(Result, IntPtr(Integer(ValueBuf) + SizeOf(Word)), FieldDesc.Length);
          Result := ValueBuf;
        end;
        else
          if IsBlobFieldType(DataType) then begin
            Len := GetBlobSize(FieldDesc.FieldNo, RecBuf);
            ValueBuf := Marshal.AllocHGlobal(Len + 1);
            ReadBlob(FieldDesc.FieldNo, RecBuf, 0, Len, ValueBuf);
            Marshal.WriteByte(ValueBuf, Len, 0);
            DataType := dtString;
            Result := ValueBuf;
          end
      end;
    end;
  finally
    if (FieldBufStatic <> nil) and (ValueBuf <> nil) then
      Marshal.FreeHGlobal(FieldBufStatic);
    NativeBuffer := (FieldBufStatic = nil) and (ValueBuf = nil);
  end;
end;

function SetScale(F: double; Scale: integer): double;
begin
  if Scale > 0 then begin
    Result := StrToFloat(FloatToStrF(F, ffFixed, 18, Scale)); // 0.009
  end
  else
    Result := F;
end;

procedure TData.PutFieldData(Field: TFieldDesc; RecBuf: IntPtr; Source: IntPtr);
var
  Dest, Src: IntPtr;
  D: double;
  Len: integer;

begin
  case Field.DataType of
    dtFloat: begin
      D := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Source));
      D := SetScale(D, Field.Scale);
      Marshal.WriteInt64(RecBuf, Field.Offset, BitConverter.DoubleToInt64Bits(D));
    end;
    dtDateTime:
      PutDateToBuf(RecBuf, Field.Offset, Source, dfMSecs);
    dtDate:
      PutDateToBuf(RecBuf, Field.Offset, Source, dfDate);
    dtTime:
      PutDateToBuf(RecBuf, Field.Offset, Source, dfTime);
  {$IFDEF VER5P}
    dtVariant:
    {$IFDEF CLR}
      Assert(False);
    {$ELSE}
      TVariantObject(Marshal.ReadIntPtr(RecBuf, Field.Offset)).Value := Variant(Source^);
    {$ENDIF}
  {$ENDIF}
    dtString:
      StrLCopy(IntPtr(Integer(RecBuf) + Field.Offset), Source, Field.Size);
    dtWideString: begin
      Src :=
      {$IFDEF CLR}
        Source;
      {$ELSE}
        {$IFDEF VER10P}
          Source;
        {$ELSE}
          PWideChar(WideString(Source^));
        {$ENDIF}
      {$ENDIF}
      StrLCopyW(IntPtr(Integer(RecBuf) + Field.Offset), Src, Field.Length);
    end;
    dtExtString: begin
      StringHeap.DisposeBuf(Marshal.ReadIntPtr(RecBuf, Field.Offset));
      Marshal.WriteIntPtr(RecBuf, Field.Offset, StringHeap.AllocStr(Source));
    end;
    dtExtWideString: begin
      StringHeap.DisposeBuf(Marshal.ReadIntPtr(RecBuf, Field.Offset));
      if Source = nil then
        Src := nil
      else
        Src :=
      {$IFDEF CLR}
          Source;
      {$ELSE}
        {$IFDEF VER10P}
            Source;
        {$ELSE}
            PWideChar(WideString(Source^));
        {$ENDIF}
      {$ENDIF}
      Marshal.WriteIntPtr(RecBuf, Field.Offset, StringHeap.AllocWideStr(Src));
    end;
    dtExtVarBytes: begin
      StringHeap.DisposeBuf(Marshal.ReadIntPtr(RecBuf, Field.Offset));
      if Source <> nil then begin
        Len := Marshal.ReadInt16(Source) + SizeOf(Word);
        Dest := StringHeap.NewBuf(Len);
        CopyBuffer(Source, Dest, Len);
        Marshal.WriteIntPtr(RecBuf, Field.Offset, Dest);
      end
      else
        Marshal.WriteIntPtr(RecBuf, Field.Offset, nil);
    end;
  {$IFDEF CLR}
    dtBytes:
      CopyBuffer(Source, IntPtr(Integer(RecBuf) + Field.Offset), Field.Length);
  {$ENDIF}
  else
    CopyBuffer(Source, IntPtr(Integer(RecBuf) + Field.Offset), Field.Size);
  end;
end;

procedure TData.PutField(FieldNo: word; RecBuf: IntPtr; Source: IntPtr);
var
  Field: TFieldDesc;
begin
  if Source = nil then begin
    SetNull(FieldNo, RecBuf, True);
    Exit;
  end;

  Field := Fields[FieldNo - 1];

  if not Field.HasParent then begin
    if (not FEnableEmptyStrings) and
      ((Field.DataType in [dtString, dtExtString]) and (Marshal.ReadByte(Source) = 0) or
       (Field.DataType in [dtWideString, dtExtWideString]) and (Marshal.ReadInt16(Source) = 0))
      then
      SetNull(FieldNo, RecBuf, True)
    else
    begin
      PutFieldData(Field, RecBuf, Source);
      SetNull(FieldNo, RecBuf, False);
    end;
  end
  else
    PutChildField(Field, RecBuf, Source);
end;

function TData.GetNull(FieldNo: word; RecBuf: IntPtr): boolean;
var
  Field:TFieldDesc;
begin
  Field := Fields[FieldNo - 1];
  if not Field.HasParent then
    Result := Marshal.ReadByte(RecBuf, DataSize + FieldNo - 1) = 1
  else
    GetChildField(Field, RecBuf, nil, Result);
end;

procedure TData.SetNull(FieldNo: word; RecBuf: IntPtr; Value: boolean);
var
  Flag: byte;
  Field: TFieldDesc;
  Blob: TBlob;
begin
  Field := Fields[FieldNo - 1];
  if not Field.HasParent then begin
    if Value then
      Flag := 1
    else
      Flag := 0;

    Marshal.WriteByte(RecBuf, DataSize + FieldNo - 1, Flag);

    if Value and IsBlobFieldType(Field.DataType) then begin // clear Blob value
      Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(RecBuf, Field.Offset)));
      if Blob <> nil then
        Blob.Clear;
    end;
  end
  else
    PutChildField(Field, RecBuf, nil);
end;

function TData.GetNullByBlob(FieldNo: word; RecBuf: IntPtr): boolean;
var
  Blob: TBlob;
  Ptr: IntPtr;
begin
  Result := True;
  if IsBlobFieldType(Fields[FieldNo - 1].DataType) then begin
    Ptr := Marshal.AllocHGlobal(sizeof(IntPtr));
    try
      if Fields[FieldNo - 1].HasParent then
        GetChildField(Fields[FieldNo - 1], RecBuf, Ptr, Result)
      else
        GetFieldData(Fields[FieldNo - 1], RecBuf, Ptr);
      Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(Ptr)));
    finally
      Marshal.FreeHGlobal(Ptr);
    end;

    if (Blob <> nil) and (Blob.Size <> 0) then begin
      Result := False;
      SetNull(FieldNo, RecBuf, False);
    end;
  end;
end;

procedure TData.GetFieldAsVariant(FieldNo: word; RecBuf: IntPtr; var Value: variant);
var
  Field: TFieldDesc;
  FieldData: IntPtr;
  Date: TDateTime;
  Date64: int64;
  PDate: IntPtr;
  Buf: IntPtr;
  Data: TBytes;
  IsBlank, t: boolean;
{  Blob: TBlob;
  l: integer;}
{$IFDEF VER6P}
  bcd: TBcd;
{$ENDIF}
begin
  if GetNull(FieldNo, RecBuf) then begin
    Value := Null;
    Exit;
  end;

  Value := Unassigned; // Delphi bug
  Field := Fields[FieldNo - 1];

  if not Field.HasParent then
    Buf := nil
  else
    Buf := Marshal.AllocHGlobal(4000);

  try
    if not Field.HasParent then
      FieldData := IntPtr(Integer(RecBuf) + Field.Offset)
    else begin
      FieldData := Buf;
      GetChildField(Field, RecBuf, FieldData, IsBlank);
    end;

    case Field.DataType of
      dtString: begin
        if Field.Fixed then
          t := FTrimFixedChar
        else
          t := FTrimVarChar;
        if t then
        // trim fixed char values
          Value := TrimRight(Marshal.PtrToStringAnsi(FieldData))
        else
          Value := Marshal.PtrToStringAnsi(FieldData);
      end;
      dtWideString: begin
        if Field.Fixed then
          t := FTrimFixedChar
        else
          t := FTrimVarChar;
        if t then
        // trim fixed char values
          Value := TrimRight(Marshal.PtrToStringUni(FieldData))
        else
          Value := Marshal.PtrToStringUni(FieldData);
      end;
      dtInt8:
        Value := shortint(Marshal.ReadByte(FieldData));
      dtSmallint:
        Value := Marshal.ReadInt16(FieldData);
      dtInt64: begin
      {$IFDEF VER6P}
        Value := Marshal.ReadInt64(FieldData);
      {$ELSE}
        TVarData(Value).VType := varDecimal;
        TVarDataD6(Value).VInt64 := Int64(FieldData^);
      {$ENDIF}
      end;
      dtUInt32: begin
      {$IFDEF VER6P}
        Value := LongWord(Marshal.ReadInt32(FieldData));
      {$ELSE}
        TVarData(Value).VType := varLongWord;
        TVarDataD6(Value).VLongword := LongWord(FieldData^);
      {$ENDIF}
      end;
      dtInteger:
        Value := Marshal.ReadInt32(FieldData);
      dtWord:
        Value := Word(Marshal.ReadInt16(FieldData));
      dtBoolean:
        Value := WordBool(Marshal.ReadInt16(FieldData));
      dtFloat,dtCurrency:
        Value := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldData));
      dtDateTime, dtDate, dtTime: begin
        if Field.HasParent then
          Date := MemUtils.TimeStampToDateTime(MSecsToTimeStamp(Trunc(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldData)))))
        else begin
          PDate := OrdinalToPtr(Date64);
          try
            GetDateFromBuf(FieldData, 0, PDate, dfDateTime);
          finally
            PtrToOrdinal(PDate, Date64);
          end;
          Date := BitConverter.Int64BitsToDouble(Date64);
        end;
        Value := Date;
      end;
      dtMemo:
        Value := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldData))).AsString;
      dtWideMemo:
        Value := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldData))).AsWideString;
    {$IFDEF VER5P}
      dtVariant:
        Value := TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(FieldData))).Value;
    {$ENDIF}
      dtExtString:
        Value := Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(FieldData));
      dtExtWideString:
        Value := Marshal.PtrToStringUni(Marshal.ReadIntPtr(FieldData));
      dtBytes: begin
        SetLength(Data, Field.Length);
        Marshal.Copy(FieldData, Data, 0, Field.Length);
        Value := Data;
      end;
      dtVarBytes: begin
        SetLength(Data, Marshal.ReadInt16(FieldData));
        Marshal.Copy(IntPtr(Integer(FieldData) + SizeOf(word)), Data, 0, Length(Data));
        Value := Data;
      end;
      dtExtVarBytes: begin
        SetLength(Data, Marshal.ReadInt16(Marshal.ReadIntPtr(FieldData)));
        Marshal.Copy(IntPtr(Integer(Marshal.ReadIntPtr(FieldData)) + SizeOf(word)), Data, 0, Length(Data));
        Value := Data;
      end;
(*      dtBlob: begin
        Blob := GetObject(FieldNo, RecBuf) as TBlob;
        try
          l := Blob.Size;
        {$IFDEF CLR}
          SetLength(Value, l);
          Blob.Defrag;
          asdd
        {$ELSE}
          Value := VarArrayCreate([0, l - 1], varByte);
          Blob.Read(0, l, TVarData(Value).VArray.Data);
        {$ENDIF}
        finally
          Blob.Free;
        end;
      end;  *)
      dtBCD:
      begin
      {$IFDEF CLR}
        Date64 := Marshal.ReadInt64(FieldData);
        Value := Date64 / 10000;
      {$ELSE}
        Value := PCurrency(FieldData)^;
      {$ENDIF}
      end;
    {$IFDEF VER6P}
      dtFmtBCD:
      begin
      {$IFDEF CLR}
        SetLength(Data, SizeOfTBcd);
        Marshal.Copy(FieldData, Data, 0, SizeOfTBcd);
        bcd := TBcd.FromBytes(Data);
      {$ELSE}
        bcd := PBcd(FieldData)^;
      {$ENDIF}
        Value := VarFMTBcdCreate(bcd);
      end;
    {$ENDIF}
      dtGuid:
        Value := Marshal.PtrToStringAnsi(FieldData);
    else
      raise EConvertError.Create(SCannotConvertType + ' ' + IntToStr(Integer(Field.DataType)));
    end;
  finally
    if Buf <> nil then
      Marshal.FreeHGlobal(Buf);
  end;
end;

procedure TData.PutFieldAsVariant(FieldNo: word; RecBuf: IntPtr; const Value: variant);
var
  FieldData: IntPtr;
  i: integer;
{$IFDEF VER6P}
  lw: Longword;
  i32: Int32;
  i64: Int64;
{$ENDIF}
{$IFDEF CLR}
  Data: TBytes;
  bcd: TBcd;
  d: Double;
{$ENDIF}
  Date: int64;
  PDate: IntPtr;
  Temp: IntPtr;
  Ws: WideString;
  l: word;

  s: string;
  p: IntPtr;
begin
  if VarIsNull(Value) or VarIsEmpty(Value) then begin
    SetNull(FieldNo, RecBuf, True);
    Exit;
  end;
  FieldData := IntPtr(Integer(RecBuf) + Fields[FieldNo - 1].Offset);
  case Fields[FieldNo - 1].DataType of
    dtString: begin
    {$IFDEF CLR}
      Data := Encoding.Default.GetBytes(String(Copy(String(Value), 1, Fields[FieldNo - 1].Size)));
      Marshal.Copy(Data, 0, FieldData, Length(Data));
      Marshal.WriteByte(FieldData, Length(Data), 0);
    {$ELSE}
      StrLCopy(FieldData, PChar(VarToStr(Value)), Fields[FieldNo - 1].Size);
    {$ENDIF}
    end;
    dtWideString: begin
      Ws := WideString(Value);
    {$IFDEF CLR}
      i := Fields[FieldNo - 1].Size div 2 - 1;
      if Length(Ws) > i then
        SetLength(Ws, i);
      Data := Encoding.Unicode.GetBytes(Ws);
      Marshal.Copy(Data, 0, FieldData, Length(Data));
      Marshal.WriteInt16(FieldData, Length(Data), 0);
    {$ELSE}
      StrLCopyW(FieldData, PWideChar(ws), Fields[FieldNo - 1].Size div 2 - 1);
    {$ENDIF}
    end;
    dtInt8: begin
      i := Value;
      case Fields[FieldNo - 1].Size of
        2:
          Marshal.WriteInt16(FieldData, i);
        1:
          Marshal.WriteByte(FieldData, byte(i));
        else
          Assert(False);
      end;
    end;
    dtSmallint:
      case VarType(Value) of
        varSmallint,varInteger,varByte{$IFDEF VER6P}, varWord{$ENDIF}:
          Marshal.WriteInt16(FieldData, smallint(Value));
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtInteger:
      case VarType(Value) of
        varString{$IFDEF WIN32},varOleStr{$ENDIF}{$IFDEF CLR}, varChar{$ENDIF}:
          Marshal.WriteInt32(FieldData, StrToInt(Value));
        varSmallint,varInteger,varByte,{$IFDEF VER6P}varWord,{$ENDIF}
        varSingle,varDouble{$IFDEF WIN32},varCurrency{$ENDIF}:
          Marshal.WriteInt32(FieldData, Integer(Value));
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtInt64:
      case VarType(Value) of
      {$IFDEF VER6P}
        varInt64: begin
          i64 := Value;
          Marshal.WriteInt64(FieldData, i64);
        end;
      {$ELSE}
        varDecimal:
          Int64(FieldData^) := TVarDataD6(Value).VInt64;
      {$ENDIF}
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtUInt32:
      case VarType(Value) of
        varLongWord:
          {$IFDEF VER6P}
          begin
            // To prevent range-checking error on large values (for example, 4294967295)
            i64 := Value;
            lw := longword(i64);
            i32 := Int32(lw);
            Marshal.WriteInt32(FieldData, i32);
          end;
          {$ELSE}
            LongWord(FieldData^) := TVarDataD6(Value).VLongWord;
          {$ENDIF}
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtWord:
      case VarType(Value) of
        varSmallint,varInteger,varByte{$IFDEF VER6P},varWord{$ENDIF}: begin
          i := Value;
          Marshal.WriteInt16(FieldData, smallint(i));
        end
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtBoolean:
      case VarType(Value) of
        varBoolean:
          Marshal.WriteInt16(FieldData, smallint(boolean(Value)));
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtFloat, dtCurrency:
      case VarType(Value) of
        varString{$IFDEF WIN32},varOleStr{$ENDIF}{$IFDEF CLR}, varChar{$ENDIF}:
          Marshal.WriteInt64(FieldData,
            BitConverter.DoubleToInt64Bits(SetScale(StrToFloat(Value), Fields[FieldNo - 1].Scale)));
        varSmallint,varInteger,varByte:
          Marshal.WriteInt64(FieldData, BitConverter.DoubleToInt64Bits(Value));
        varSingle,varDouble{$IFDEF WIN32},varCurrency{$ENDIF}:
          Marshal.WriteInt64(FieldData,
            BitConverter.DoubleToInt64Bits(SetScale(Value, Fields[FieldNo - 1].Scale)));
      else
        raise EConvertError.Create(SCannotConvertType);
      end;
    dtDateTime, dtDate, dtTime: begin
      Date := BitConverter.DoubleToInt64Bits(Value);
      PDate := OrdinalToPtr(Date);
      try
        PutDateToBuf(FieldData, 0, PDate, dfDateTime);
      finally
        FreeOrdinal(PDate);
      end;
    end;
    dtMemo,dtBlob: // used by ODAC to refresh String as Memo
      TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldData))).AsString := VarToStr(Value);
    dtWideMemo:
      TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(FieldData))).AsWideString := VarToWideStr(Value);
  {$IFDEF VER5P}
    dtVariant:
      TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(FieldData))).Value := Value;
  {$ENDIF}
    dtExtString: begin
      StringHeap.DisposeBuf(Marshal.ReadIntPtr(FieldData));
      Temp := Marshal.StringToHGlobalAnsi(Value);
      try
        Marshal.WriteIntPtr(FieldData, StringHeap.AllocStr(Temp));
      finally
        Marshal.FreeCoTaskMem(Temp);
      end;
    end;
    dtExtWideString: begin
      StringHeap.DisposeBuf(Marshal.ReadIntPtr(FieldData));
      Temp := Marshal.StringToHGlobalUni(Value);
      try
        Marshal.WriteIntPtr(FieldData, StringHeap.AllocWideStr(Temp));
      finally
        Marshal.FreeCoTaskMem(Temp);
      end;
    end;
    dtBytes: begin
      Assert(VarType(Value) = varArray + varByte, 'Invalid VType');
    {$IFDEF CLR}
      SetLength(Data, VarArrayHighBound(Value, 1) + 1);
      for i := 0 to High(Data) do
        Data[i] := VarArrayGet(Value, i);
      Marshal.Copy(Data, 0, FieldData, Length(Data));
    {$ELSE}
      Assert(TVarData(Value).VArray.Bounds[0].ElementCount = Fields[FieldNo - 1].Length, 'Invalid data size');
      Move(TVarData(Value).VArray.Data^, FieldData^, Fields[FieldNo - 1].Length);
    {$ENDIF}
    end;
    dtVarBytes: begin
      Assert(VarType(Value) = varArray + varByte, 'Invalid VType');
    {$IFDEF CLR}
      SetLength(Data, VarArrayHighBound(Value, 1) + 1);
      for i := 0 to High(Data) do
        Data[i] := VarArrayGet(Value, i);
      Marshal.WriteInt16(FieldData, Length(Data));
      Marshal.Copy(Data, 0, IntPtr(Integer(FieldData) + sizeof(word)), Length(Data));
    {$ELSE}
      Assert(TVarData(Value).VArray.Bounds[0].ElementCount <= Fields[FieldNo - 1].Length, 'Invalid data size');

      Word(FieldData^) := TVarData(Value).VArray.Bounds[0].ElementCount;
      Move(TVarData(Value).VArray.Data^, (PChar(FieldData) + sizeof(word))^, Word(FieldData^));
    {$ENDIF}
    end;
    dtExtVarBytes: begin
      Assert(VarType(Value) = varArray + varByte, 'Invalid VType');
//      Assert(VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1) + 1 <= Length, 'Invalid data size');

      StringHeap.DisposeBuf(Marshal.ReadIntPtr(FieldData));
      l := VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1) + 1;
      Marshal.WriteIntPtr(FieldData, StringHeap.NewBuf(l + sizeof(Word)));

      Marshal.WriteInt16( Marshal.ReadIntPtr(FieldData), l);
      for i:= VarArrayLowBound(Value, 1) to VarArrayHighBound(Value, 1) do
        Marshal.WriteByte( IntPtr(Integer(Marshal.ReadIntPtr(FieldData)) + sizeof(word) + i - VarArrayLowBound(Value, 1)),  Value[i]);
    end;
    dtBCD:
    {$IFDEF CLR}
    begin
      d := Value;
      d := d * 10000;
      Marshal.WriteInt64(FieldData, Round(d));
    end;
    {$ELSE}
      PCurrency(FieldData)^ := Value;
    {$ENDIF}
  {$IFDEF VER6P}
    dtFmtBCD:
    {$IFDEF CLR}
    begin
      bcd := Value;
      Data := TBcd.ToBytes(bcd);
      Marshal.Copy(Data, 0, FieldData, SizeOfTBcd);
    end;
    {$ELSE}
      PBcd(FieldData)^ := StrToBcd(Value);
    {$ENDIF}
  {$ENDIF}
    dtGuid:
    begin
      s := VarToStr(Value);
      p := Marshal.StringToHGlobalAnsi(s);
      try
        StrLCopy(FieldData, p, Fields[FieldNo - 1].Size);
      finally
        Marshal.FreeCoTaskMem(p);
      end;
    end;
  else
    raise EConvertError.Create(SCannotConvertType);
  end;

  SetNull(FieldNo, RecBuf, False);
end;

function TData.FindField(Name: string):TFieldDesc;
begin
  Result := FFields.FindField(Name);
end;

function TData.FieldByName(Name: string):TFieldDesc;
begin
  Result := FFields.FieldByName(Name);
end;

function TData.IsBlobFieldType(DataType: word): boolean; // TBlob descendants - dtBlob, dtMemo etc
begin
  Result := (DataType = dtBlob) or (DataType = dtMemo) or (DataType = dtWideMemo);
end;

function TData.IsComplexFieldType(DataType: word): boolean; // All supported complex field types (BlobFieldTypes, ExtFieldTypes and TSharedObject descendants (not BLOB))
begin
  case DataType of
    dtExtString, dtExtWideString, dtExtVarBytes{$IFDEF VER5P}, dtVariant{$ENDIF}:
      Result := True;
  else
    Result := IsBlobFieldType(DataType);
  end;
end;

function TData.HasFields(FieldTypes: TFieldTypeSet): boolean;
var
  i: integer;
begin
  i := 0;
  while (i < FieldCount) and not (Fields[i].DataType in FieldTypes) do
    Inc(i);
  Result := i < FieldCount;
end;

function TData.HasBlobFields: boolean;
var
  i: integer;
begin
  i := 0;
  while (i < FieldCount) and not IsBlobFieldType(Fields[i].DataType) do
    Inc(i);
  Result := i < FieldCount;
end;

function TData.CheckHasComplexFields: boolean;
var
  i: integer;
begin
  i := 0;
  while (i < FieldCount) and not IsComplexFieldType(Fields[i].DataType) do
    Inc(i);
  Result := i < FieldCount;
  FHasComplexFields := Result;
end;

{ Records }

function TData.AllocRecBuf(var RecBuf: IntPtr): IntPtr;
begin
  RecBuf := Marshal.AllocHGlobal(RecordSize);
  Result := RecBuf;
end;

procedure TData.FreeRecBuf(RecBuf: IntPtr);
begin
  Marshal.FreeHGlobal(RecBuf);
end;

procedure TData.CreateComplexFields(RecBuf: IntPtr; WithBlob: boolean);
var
  i: integer;
begin
  for i := 0 to FieldCount - 1 do
    CreateComplexField(RecBuf, i, WithBlob);
end;

procedure TData.CreateComplexField(RecBuf: IntPtr; FieldIndex: integer; WithBlob: boolean);
var
  Blob: TSharedObject;
  FieldDesc: TFieldDesc;
begin
  FieldDesc := Fields[FieldIndex];
  if FieldDesc.FieldDescKind <> fdkCalculated then
    case FieldDesc.DataType of
      dtBlob, dtMemo, dtWideMemo:
        if WithBlob then begin
          Blob := TBlob.Create;
          if FieldDesc.DataType = dtWideMemo then
            TBlob(Blob).IsUnicode := True;
          // RollBack is always on for LOB fields. Otherwise modification
          // that cannot be canceled is possible.
          TBlob(Blob).EnableRollback;
          SetObject(FieldIndex + 1, RecBuf, Blob);
        end;
    {$IFDEF VER5P}
      dtVariant:
        begin
          Blob := TVariantObject.Create;
          Marshal.WriteIntPtr(RecBuf, FieldDesc.Offset, Blob.GCHandle);
        end;
    {$ENDIF}
      dtExtString, dtExtWideString, dtExtVarBytes:
        Marshal.WriteIntPtr(RecBuf, FieldDesc.Offset, nil);
    end;
end;


procedure TData.AddRefComplexFields(RecBuf: IntPtr);
var
  i: integer;
  so: TSharedObject;
begin
  for i := 0 to FieldCount - 1 do
    if Fields[i].DataType in [dtExtString, dtExtWideString, dtExtVarBytes] then
      StringHeap.AddRef(Marshal.ReadIntPtr(RecBuf, Fields[i].Offset))
    else
    if IsComplexFieldType(Fields[i].DataType) and not Fields[i].HasParent then begin
      so := TSharedObject(GetGCHandleTarget(Marshal.ReadIntPtr(RecBuf, Fields[i].Offset)));
      Assert(so <> nil, 'Shared object for ' + Fields[i].Name + '=nil');
      so.AddRef;
    end;
end;

procedure TData.FreeComplexFields(RecBuf: IntPtr; WithBlob: boolean);
var
  i: integer;
  Handle: IntPtr;
  so: TSharedObject;
  b: boolean;
  Field: TFieldDesc;
begin
  for i := 0 to FieldCount - 1 do begin
    Field := Fields[i];
    if Field.FieldDescKind <> fdkCalculated then
      case Field.DataType of
        dtBlob, dtMemo, dtWideMemo:
          if WithBlob then begin
            Handle := Marshal.ReadIntPtr(RecBuf, Field.Offset);
            so := TSharedObject(GetGCHandleTarget(Handle));
            // see TSharedObject.Free for details
            b := (so <> nil) and (so.RefCount = 1);
            so.Free;
            if b then
              Marshal.WriteIntPtr(RecBuf, Field.Offset, nil);
          end;
      {$IFDEF VER5P}
        dtVariant: begin
          Handle := Marshal.ReadIntPtr(RecBuf, Field.Offset);
          TVariantObject(GetGCHandleTarget(Handle)).Free;
        end;
      {$ENDIF}
        dtExtString, dtExtWideString, dtExtVarBytes:
          if not StringHeap.Empty then begin
            Handle := Marshal.ReadIntPtr(RecBuf, Field.Offset);
            if (Handle <> nil) and (Marshal.ReadInt16(IntPtr(Integer(Handle) - SizeOf(Word))) = RefNull) then
              Handle := nil;
            StringHeap.DisposeBuf(Marshal.ReadIntPtr(RecBuf, Field.Offset));
            Marshal.WriteIntPtr(RecBuf, Field.Offset, Handle)
          end;
      end;
  end;
end;

procedure TData.CopyComplexFields(Source: IntPtr; Dest: IntPtr; WithBlob: boolean);
var
  i, l: integer;
  SrcPtr: IntPtr;
  DestPtr: IntPtr;
begin
  if WithBlob then
    Assert(False);

  for i := 0 to FieldCount - 1 do
    case Fields[i].DataType of
      dtExtString: begin
        SrcPtr := Marshal.ReadIntPtr(Source, Fields[i].Offset);
        Marshal.WriteIntPtr(Dest, Fields[i].Offset, StringHeap.AllocStr(SrcPtr));
      end;
      dtExtWideString: begin
        SrcPtr := Marshal.ReadIntPtr(Source, Fields[i].Offset);
        Marshal.WriteIntPtr(Dest, Fields[i].Offset, StringHeap.AllocWideStr(SrcPtr));
      end;
      dtExtVarBytes: begin
        SrcPtr := Marshal.ReadIntPtr(Source, Fields[i].Offset);
        DestPtr := IntPtr(Integer(Dest) + Fields[i].Offset);
        if SrcPtr = nil then
          Marshal.WriteIntPtr(DestPtr, nil)
        else begin
          l := Marshal.ReadInt16(IntPtr(SrcPtr)) + SizeOf(Word);
          Marshal.WriteIntPtr(DestPtr, StringHeap.NewBuf(l));
          CopyBuffer(SrcPtr, Marshal.ReadIntPtr(DestPtr), l);
        end
      end;
    {$IFDEF VER5P}
      dtVariant:
        TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(Dest, Fields[i].Offset))).Value :=
          TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(Source, Fields[i].Offset))).Value;
    {$ENDIF}
    end;
end;

procedure TData.InitRecord(RecBuf: IntPtr);
var
  i: integer;
begin
// Complex fields need create later
  if HasComplexFields then  // clear pointer to complex field
    FillChar(RecBuf, RecordSize, 0);

  for i := 1 to FieldCount do
    SetNull(i, RecBuf, True);
end;

procedure TData.AppendBlankRecord;
var
  RecBuf: IntPtr;
begin
  AllocRecBuf(RecBuf);
  try
    InitRecord(RecBuf);
    AppendRecord(RecBuf);
  finally
    FreeRecBuf(RecBuf);
  end;
end;

procedure TData.EditRecord(RecBuf: IntPtr);
var
  TempBuf: IntPtr;
begin
  AllocRecBuf(TempBuf);
  try
    GetRecord(TempBuf);
    CreateComplexFields(TempBuf, False);  // Blobs uses internal cache
    CopyComplexFields(RecBuf, TempBuf, False);
    PutRecord(TempBuf);

    {if IsBlobFields then
      for i:= 0 to FieldCount - 1 do
        if Fields[i].DataType in BlobFieldTypes then
          TBlob(Pointer(PChar(RecBuf) + Fields[i].Offset)^).EnableRollback;}
  finally
    FreeRecBuf(TempBuf);
  end;
end;

procedure TData.PostRecord(RecBuf: IntPtr);
var
  i: integer;
  TempBuf: IntPtr;
  Blob: TBlob;
begin
  AllocRecBuf(TempBuf);
  try
    GetRecord(TempBuf);

    UpdateRecord(RecBuf);

    if HasBlobFields then
      for i := 0 to FieldCount - 1 do
        if IsBlobFieldType(Fields[i].DataType) then begin
          Blob := TBlob(InternalGetObject(Fields[i].FieldNo, RecBuf));
          if Blob <> nil then
            Blob.Commit;
        end;

    FreeComplexFields(TempBuf, False);
  finally
    FreeRecBuf(TempBuf);
  end;
end;

procedure TData.CancelRecord(RecBuf: IntPtr);
var
  i: integer;
  Blob: TBlob;
begin
  if HasBlobFields then
    for i := 0 to FieldCount - 1 do
      if IsBlobFieldType(Fields[i].DataType) then begin
        Blob := TBlob(InternalGetObject(Fields[i].FieldNo, RecBuf));
        if Blob <> nil then
          Blob.Cancel;
      end;

  FreeComplexFields(RecBuf, False);
end;

{ Edit }

procedure TData.InternalAppend(RecBuf: IntPtr);
begin
  if Assigned(FOnAppend) then
    FOnAppend;
end;

procedure TData.InternalDelete;
begin
  if Assigned(FOnDelete) then
    FOnDelete;
end;

procedure TData.InternalUpdate(RecBuf: IntPtr);
begin
  if Assigned(FOnUpdate) then
    FOnUpdate;
end;

procedure TData.ApplyRecord(UpdateKind:TUpdateRecKind; var Action:TUpdateRecAction; LastItem: boolean);
begin
  if Assigned(FOnApplyRecord) then
    FOnApplyRecord(UpdateKind, Action, LastItem);
end;

{ Navigation }

function TData.GetEOF: boolean;
begin
  Result := FEOF;
end;

function TData.GetBOF: boolean;
begin
  Result := FBOF;
end;

procedure TData.SetToBegin;
begin
  FBOF := True;
  FEOF := False;
end;

procedure TData.SetToEnd;
begin
  FEOF := True;
  FBOF := False;
end;

function TData.GetRecordCount: longint;
begin
  Result := -1;
end;

function TData.GetRecordNo: longint;
begin
  Result := -1;
end;

procedure TData.SetRecordNo(Value: longint);
begin
end;

{ BookMarks }

procedure TData.GetBookmark(Bookmark: PRecBookmark);
begin
  Bookmark.Order := RecordNo;
end;

procedure TData.SetToBookmark(Bookmark: PRecBookmark);
begin
  if Bookmark.Order <> -1 then
    SetRecordNo(Bookmark.Order);
end;

function TData.BookmarkValid(Bookmark: PRecBookmark): boolean;
begin
  if IntPtr(Bookmark) <> nil then
    Result := Bookmark.Order <> -1
  else
    Result := False;
end;

function TData.CompareBookmarks(Bookmark1, Bookmark2: PRecBookmark): integer;
const
  RetCodes: array[Boolean, Boolean] of ShortInt = ((2,-1),(1,0));
begin
  Result := RetCodes[IntPtr(Bookmark1) = nil, IntPtr(Bookmark2) = nil];
  if Result = 2 then begin
    if Bookmark1.Order >= Bookmark2.Order then
      if Bookmark1.Order = Bookmark2.Order then
        Result := 0
      else
        Result := 1
    else
      Result := -1
  end;
end;

{ CachedUpdates }

function TData.GetUpdateStatus: TItemStatus;
begin
  Result := isUnmodified;
end;

function TData.GetUpdateResult: TUpdateRecAction;
begin
  Result := urNone;
end;

procedure TData.SetCacheRecBuf(NewBuf: IntPtr; OldBuf: IntPtr);
begin
end;

procedure TData.ApplyUpdates;
begin
end;

procedure TData.CommitUpdates;
begin
end;

procedure TData.CancelUpdates;
begin
end;

procedure TData.RestoreUpdates;
begin
end;

procedure TData.RevertRecord;
begin
end;

function TData.GetUpdatesPending: boolean;
begin
  Result := False;
end;

procedure TData.GetOldRecord(RecBuf: IntPtr);
begin
end;

{ Filter }

function TData.AllocNode: TExpressionNode;
begin
  Result := TExpressionNode.Create;
  Result.NextAlloc := FirstAlloc;
  FirstAlloc := Result;
  Result.LeftOperand := nil;
  Result.RightOperand := nil;
  Result.NextOperand := nil;
end;

procedure TData.FilterError;
begin
  raise Exception.Create(SIllegalFilter);
end;

function TData.OrExpr: TExpressionNode;
var
  Node: TExpressionNode;
begin
  Result := AndExpr;
  while Code = lxOR do begin
    Code := Parser.GetNext(StrLexem);
    Node := AllocNode;
    Node.NodeType := ntOr;
    Node.LeftOperand := Result;
    Node.RightOperand := AndExpr;
    Result := Node;
  end;
end;

function TData.AndExpr: TExpressionNode;
var
  Node: TExpressionNode;
begin
  Result := Condition;
  while Code = lxAND do begin
    Code := Parser.GetNext(StrLexem);
    Node := AllocNode;
    Node.NodeType := ntAnd;
    Node.LeftOperand := Result;
    Node.RightOperand := Condition;
    Result := Node;
  end;
end;

function TData.Condition: TExpressionNode;
var
  OpCode: integer;
begin
  Result := nil;
  if (Code = lcIdent) or (Code = lcNumber) or (Code = lcString) or
    (Code in [lxMinus, lxPlus, lxLeftSqBracket, lxRightSqBracket])
  then begin
    Result := AllocNode;
    Result.LeftOperand := Argument;
    OpCode := Code;
    case Code of
      lxEqual, lxIS:
        Result.NodeType := ntEqual;
      lxMore:
        Result.NodeType := ntMore;
      lxLess:
        Result.NodeType := ntLess;
      lxMoreEqual:
        Result.NodeType := ntMoreEqual;
      lxLessEqual:
        Result.NodeType := ntLessEqual;
      lxNoEqual:
        Result.NodeType := ntNoEqual;
      lxLike:
        Result.NodeType := ntLike;
    else
      FilterError;
    end;
    Code := Parser.GetNext(StrLexem);
    if OpCode = lxIS then begin
      if Code = lxNOT then begin
        Code := Parser.GetNext(StrLexem);
        if Code <> lxNULL then
          FilterError;
        Result.NodeType := ntNoEqual;
      end
      else if Code <> lxNULL then
        FilterError;
    end;
    Result.RightOperand := Argument;
  end
  else
    if Code = lxNOT then begin
      Code := Parser.GetNext(StrLexem);
      Result := AllocNode;
      Result.NodeType := ntNot;
      Result.LeftOperand := Condition;
    end
    else
      if Code = lxTRUE then begin
        Code := Parser.GetNext(StrLexem);
        Result := AllocNode;
        Result.NodeType := ntTrue;
      end
      else
        if Code = lxFALSE then begin
          Code := Parser.GetNext(StrLexem);
          Result := AllocNode;
          Result.NodeType := ntFalse;
        end
        else
          if Code = lxLeftBracket then begin
            Code := Parser.GetNext(StrLexem);
            Result := OrExpr;
            if Code = lxRightBracket then
              Code := Parser.GetNext(StrLexem)
            else
              FilterError;
          end
          else
            FilterError;
end;

{$IFNDEF VER6P}
function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string;
var
  P, Dest: PChar;
  DropCount: Integer;
begin
  Result := '';
  if (Src = nil) or (Src^ <> Quote) then Exit;
  Inc(Src);
  DropCount := 1;
  P := Src;
  Src := AnsiStrScan(Src, Quote);
  while Src <> nil do   // count adjacent pairs of quote chars
  begin
    Inc(Src);
    if Src^ <> Quote then Break;
    Inc(Src);
    Inc(DropCount);
    Src := AnsiStrScan(Src, Quote);
  end;
  if Src = nil then Src := StrEnd(P);
  if ((Src - P) <= 1) then Exit;
  if DropCount = 1 then
    SetString(Result, P, Src - P - 1)
  else
  begin
    SetLength(Result, Src - P - DropCount);
    Dest := PChar(Result);
    Src := AnsiStrScan(P, Quote);
    while Src <> nil do
    begin
      Inc(Src);
      if Src^ <> Quote then Break;
      Move(P^, Dest^, Src - P);
      Inc(Dest, Src - P);
      Inc(Src);
      P := Src;
      Src := AnsiStrScan(Src, Quote);
    end;
    if Src = nil then Src := StrEnd(P);
    Move(P^, Dest^, Src - P - 1);
  end;
end;

function AnsiDequotedStr(const S: string; AQuote: Char): string;
var
  LText: PChar;
begin
  LText := PChar(S);
  Result := AnsiExtractQuotedStr(LText, AQuote);
  if Result = '' then
    Result := S;
end;
{$ENDIF}

function TData.Argument: TExpressionNode;
var
  Field: TFieldDesc;
  FieldName: string;
  ASign: string;

  function ParseFieldName(FirstPart: string): string;
  begin
    Result := FirstPart;
    Code := Parser.GetNext(StrLexem);
    if StrLexem = '.' then
      repeat
        Code := Parser.GetNext(StrLexem);
        if Code = lcIdent then
          Result := Result + '.' + StrLexem
        else
          break;
        Code := Parser.GetNext(StrLexem);
      until StrLexem <> '.';
  end;
begin
  Result := AllocNode;
  case Code of
    lcIdent: begin
      FieldName := ParseFieldName(StrLexem);
      Field := FindField(FieldName);
      if Field = nil then
        raise Exception.Create(Format(SFieldNotFound, [FieldName]));
      Result.NodeType := ntField;
      Result.FieldDesc := Field;
      Result.Value := StrLexem;
      Exit;
    end;
    lxLeftSqBracket: begin
      FieldName := '';
      Parser.OmitBlank := False;
      Code := Parser.GetNext(StrLexem);
      while (Code <> lxRightSqBracket) and (Code <> lcEnd) do begin
        FieldName := FieldName + StrLexem;
        Code := Parser.GetNext(StrLexem);
      end;
      Parser.OmitBlank := True;
      Field := FindField(FieldName);
      if Field = nil then
        raise Exception.Create(Format(SFieldNotFound, [FieldName]));
      Result.NodeType := ntField;
      Result.FieldDesc := Field;
      Result.Value := FieldName;
    end;
    lcString: begin
      Result.NodeType := ntValue;
      Result.Value := AnsiDequotedStr('''' + StrLexem + '''', ''''); // TODO Optimize with StringBuilder
    end;
    lcNumber: begin
      Result.NodeType := ntValue;
      Result.Value := StrToFloat(StrLexem);
    end;
    lxMinus,lxPlus: begin
      Result.NodeType := ntValue;
      ASign := StrLexem;
      Code := Parser.GetNext(StrLexem);
      if Code = lcNumber then
        Result.Value := StrToFloat(ASign + StrLexem)
      else
        FilterError;
    end;
    lxNULL: begin
      Result.NodeType := ntValue;
      Result.Value := Null;
    end;
    lxTRUE: begin
      Result.NodeType := ntValue;
      Result.Value := True;
    end;
    lxFALSE: begin
      Result.NodeType := ntValue;
      Result.Value := False;
    end;
  else
    FilterError;
  end;

  Code := Parser.GetNext(StrLexem);
end;

procedure TData.CreateFilterExpression(Text: string);
begin
  FreeFilterExpression;
  if Trim(Text) <> '' then begin
    Parser := TBoolParser.Create(Text);
    try
      try
        Parser.ToBegin();
        Code := Parser.GetNext(StrLexem);
        FilterExpression := OrExpr();

        if (Code <> lcEnd) then
          FilterError;
      except
        FreeFilterExpression;
        raise;
      end;
    finally
      Parser.Free;
    end;
  end;
end;

procedure TData.FreeFilterExpression;
var
  Node: TExpressionNode;
begin
  while FirstAlloc <> nil do begin
    Node := FirstAlloc;
    FirstAlloc := FirstAlloc.NextAlloc;
    Node.Free;
  end;
  FilterExpression := nil;
end;

function TData.Eval(Node: TExpressionNode): boolean;

  function VarIsString(const V: Variant): boolean;
  begin
    Result := (VarType(V) = varString){$IFDEF WIN32} or (VarType(V) = varOleStr){$ENDIF}{$IFDEF CLR} or (VarType(V) = varChar){$ENDIF};
  end;

var
  V1,V2: variant;
  DateField1: boolean;
  DateField2: boolean;
  FieldDesc: TFieldDesc;

  function MatchesMask(St: string; Mask: string): boolean;
  const
    WildcardAst = '*';
    WildcardPct = '%';
    WildcardOne = '_';
  type
    TMatchesResult = (mrFalse,mrTrue,mrEnd);

    function SubMatchesMask(StIndex, MaskIndex: integer): TMatchesResult;
    begin
      while (MaskIndex <= Length(Mask)) and
        ((StIndex <= Length(St)) or
        ((Mask[MaskIndex] = WildcardAst) or (Mask[MaskIndex] = WildcardPct))) do begin
        if (Mask[MaskIndex] = WildcardAst) or (Mask[MaskIndex] = WildcardPct) then begin
          if MaskIndex = Length(Mask) then begin  //-
            Result := mrTrue;                     // Speed up
            Exit;                                 // with mask '*'
          end                                     //-
          else
            case SubMatchesMask(StIndex, MaskIndex + 1) of
              mrTrue: begin
                Result := mrTrue;
                Exit;
              end;
              mrFalse:
                if StIndex > Length(St) then begin
                  Result := mrEnd;
                  Exit;
                end
                else
                  Inc(StIndex);
              mrEnd: begin
                Result := mrEnd;
                Exit;
              end;
            end;
        end
        else
          if (St[StIndex] = Mask[MaskIndex]) or (Mask[MaskIndex] = WildcardOne)
          then begin
            Inc(StIndex);
            Inc(MaskIndex);
          end
          else begin
            Result := mrFalse;
            Exit;
          end;
      end;

      if StIndex > Length(St) then
        if MaskIndex > Length(Mask) then
          Result := mrTrue
        else
          Result := mrEnd
      else
        Result := mrFalse;
    end;
  begin
    Result := SubMatchesMask(1, 1) = mrTrue;
  end;

{$IFNDEF VER6P}
  function TryStrToDateTime(const S: string; out Value: TDateTime): Boolean;
  begin
    try
      Value := StrToDateTime(s);
      Result := True;
    except
      Result := False;
    end;
  end;
{$ENDIF}

  procedure NormalizeDateField(var V: Variant);
{$IFNDEF CLR}
  var
    d: TDateTime;
{$ENDIF}
  begin
    if VarIsString(V) then
    {$IFDEF CLR}
      V := TDateTime(V);
    {$ELSE}
      if TryStrToDateTime(V, d) then
        V := d
      else
        V := VarToDateTime(V);
    {$ENDIF}
  end;

begin
  Assert(Node <> nil);

  Result := False;

  if Node.NodeType in [ntEqual, ntMore, ntLess, ntMoreEqual, ntLessEqual,
    ntNoEqual, ntLike]
  then begin
    Assert(Node.LeftOperand <> nil);
    Assert(Node.RightOperand <> nil);

    DateField1 := False;
    DateField2 := False;

    case Node.LeftOperand.NodeType of
      ntField: begin
        FieldDesc := Node.LeftOperand.FieldDesc;
        GetFieldAsVariant(FieldDesc.FieldNo, FilterRecBuf, V1);
        DateField1 := FieldDesc.DataType in [dtDateTime, dtDate, dtTime];
      end;
      ntValue:
        V1 := Node.LeftOperand.Value;
    end;

    case Node.RightOperand.NodeType of
      ntField: begin
        FieldDesc := Node.RightOperand.FieldDesc;
        GetFieldAsVariant(FieldDesc.FieldNo, FilterRecBuf, V2);
        DateField2 := FieldDesc.DataType in [dtDateTime, dtDate, dtTime];
      end;
      ntValue:
        V2 := Node.RightOperand.Value;
    end;

    if DateField1 then
      NormalizeDateField(V2); /// CR-D12823
    if DateField2 then
      NormalizeDateField(V1);

    if FilterCaseInsensitive then begin
      if VarIsString(V1) then
        V1 := AnsiUpperCase(VarToStr(V1));
      if VarIsString(V2) then
        V2 := AnsiUpperCase(VarToStr(V2));
    end;

//    if FilterNoPartialCompare then;

  end;

  if (VarIsNull(V1) or VarIsNull(V2)) and (Node.NodeType in [ntMore, ntLess, ntMoreEqual, ntLessEqual]) then begin
    // To prevent exception on compare value with Null
    Result := False;
    Exit;
  end;

  case Node.NodeType of
    ntEqual, ntLike:
      if FilterNoPartialCompare or not VarIsString(V1) then
        Result := V1 = V2
      else
        Result := MatchesMask(VarToStr(V1), VarToStr(V2));
    ntNoEqual:
      if FilterNoPartialCompare or not VarIsString(V1) then
        Result := V1 <> V2
      else
        Result := not MatchesMask(VarToStr(V1), VarToStr(V2));
    ntMore:
      Result := V1 > V2;
    ntLess:
      Result := V1 < V2;
    ntMoreEqual:
      Result := V1 >= V2;
    ntLessEqual:
      Result := V1 <= V2;
    ntAnd:
      Result := Eval(Node.LeftOperand) and Eval(Node.RightOperand);
    ntOr:
      Result := Eval(Node.LeftOperand) or Eval(Node.RightOperand);
    ntNot:
      Result := not Eval(Node.LeftOperand);
    ntTrue:
      Result := True;
    ntFalse:
      Result := False;
  else
    Assert(False);
  end;
end;

procedure TData.FilterUpdated;
begin
end;

function TData.Filtered: boolean;
begin
  Result := Assigned(FFilterFunc) or Assigned(FilterExpression) or
    Assigned(FFilterMDFunc);
end;

{ Blobs }

function TData.InternalGetObject(FieldNo: word; RecBuf: IntPtr): TSharedObject;
var
  IsBlank: boolean;
  Ptr: IntPtr;
begin
  Ptr := Marshal.AllocHGlobal(sizeof(IntPtr));
  try
    GetField(FieldNo, RecBuf, Ptr, IsBlank);
    Result := TSharedObject(GetGCHandleTarget(Marshal.ReadIntPtr(Ptr)));
  finally
    Marshal.FreeHGlobal(Ptr);
  end;
end;

function TData.GetObject(FieldNo: word; RecBuf: IntPtr): TSharedObject;
begin
  if not IsBlobFieldType(Fields[FieldNo - 1].DataType) then
    raise Exception.Create(SNeedBlobType);

  Result := InternalGetObject(FieldNo, RecBuf);

  Assert(Result <> nil, 'Object for field ' + Fields[FieldNo - 1].Name + '(' + IntToStr(FieldNo) + ') = nil');
end;

procedure TData.SetObject(FieldNo: word; RecBuf: IntPtr; Obj: TSharedObject);
begin
  if not IsBlobFieldType(Fields[FieldNo - 1].DataType) then
    raise Exception.Create(SNeedBlobType);

  Marshal.WriteIntPtr(RecBuf, Fields[FieldNo - 1].Offset, Obj.GCHandle);
end;

{$IFDEF VER6}
{$IFDEF MSWINDOWS}
var
  DefaultUserCodePage: Integer;
{$ENDIF}

type
  PStrRec = ^StrRec;
  StrRec = packed record
    refCnt: Longint;
    length: Longint;
  end;

{$IFDEF LINUX}
const
  libc = 'libc.so.6';

const
  LC_CTYPE    = 0;
  _NL_CTYPE_CODESET_NAME = LC_CTYPE shl 16 + 14;

function iconv_open(ToCode: PChar; FromCode: PChar): Integer; cdecl;
  external libc name 'iconv_open';

function nl_langinfo(item: integer): pchar; cdecl;
  external libc name 'nl_langinfo';

function iconv(cd: Integer; var InBuf; var InBytesLeft: Integer; var OutBuf; var OutBytesLeft: Integer): Integer; cdecl;
  external libc name 'iconv';

function iconv_close(cd: Integer): Integer; cdecl;
  external libc name 'iconv_close';

function CharacterSizeWideChar(P: Pointer; MaxLen: Integer): Integer;
begin
  Result := SizeOf(WideChar);
end;
  
procedure LocaleConversionError;
begin
  Error(TRuntimeError(234) {reCodesetConversion});
end;

type
  TCharacterSizeProc = function(P: Pointer; MaxLen: Integer): Integer;

function __errno_location: PInteger; cdecl;
  external libc name '__errno_location';
  
function GetLastError: Integer;
begin
  Result := __errno_location^;
end;
  
function BufConvert(var Dest;   DestBytes: Integer;
                    const Source; SrcBytes: Integer;
                    context: Integer;
                    DestCharSize: Integer;
                    SourceCharSize: TCharacterSizeProc): Integer;
const
  E2BIG = 7;
  EINVAL = 22;
  EILSEQ = 84;
const
  UnknownCharIndicator = '?';
var
  SrcBytesLeft, DestBytesLeft, Zero: Integer;
  s, d, pNil: Pointer;
  LastError: Integer;
  cs: Integer;
begin
  Result := -1;

  // Make copies of parameters. iconv modifies param pointers.
  DestBytesLeft := DestBytes;
  SrcBytesLeft := SrcBytes;
  s := Pointer(Source);
  d := Pointer(Dest);

  while True do
  begin
    Result := iconv(context, s, SrcBytesLeft, d, DestBytesLeft);
    if Result <> -1 then
      Break
    else
    begin
      LastError := GetLastError;
      if (LastError = E2BIG) and (SrcBytesLeft > 0) and (DestBytesLeft > 0) then
        Continue;

      if (LastError <> EINVAL) and (LastError <> EILSEQ) then
        LocaleConversionError;
      pNil := nil;
      Zero := 0;
      iconv(context, pNil, Zero, pNil, Zero); // Reset state of context

      // Invalid input character in conversion stream.
      // Skip input character and write '?' to output stream.
      // The glibc iconv() implementation also returns EILSEQ
      // for a valid input character that cannot be converted
      // into the requested codeset.
      cs := SourceCharSize(s, SrcBytesLeft);
      Inc(Cardinal(s), cs);
      Dec(SrcBytesLeft, cs);

      Assert(DestCharSize in [1, 2]);
      case DestCharSize of
        1:
          begin
            PChar(d)^ := UnknownCharIndicator;
            Inc(PChar(d));
            Dec(DestBytesLeft, SizeOf(Char));
          end;

        2:
          begin
            PWideChar(d)^ := UnknownCharIndicator;
            Inc(PWideChar(d));
            Dec(DestBytesLeft, SizeOf(WideChar));
          end;
      end;
    end;
  end;

  if Result <> -1 then
    Result := DestBytes - DestBytesLeft;
end;
{$ENDIF}
  
function CharFromWCharD7(CharDest: PChar; DestBytes: Integer; const WCharSource: PWideChar; SrcChars: Integer): Integer;
{$IFDEF LINUX}
var
  IconvContext: Integer;
{$ENDIF}
begin
{$IFDEF LINUX}
  if (DestBytes <> 0) and (SrcChars <> 0) then
  begin
    IconvContext := iconv_open(nl_langinfo(_NL_CTYPE_CODESET_NAME), 'UNICODELITTLE');
    if IconvContext = -1 then
      LocaleConversionError;
    try
      Result := BufConvert(CharDest, DestBytes, WCharSource, SrcChars * SizeOf(WideChar),
         IconvContext, 1, CharacterSizeWideChar);
    finally
      iconv_close(IconvContext);
    end;
  end
  else
    Result := 0;
{$ENDIF}
{$IFDEF MSWINDOWS}
  Result := WideCharToMultiByte(DefaultUserCodePage, 0, WCharSource, SrcChars,
      CharDest, DestBytes, nil, nil);
{$ENDIF}
end;

procedure _LStrClr(var S);
var
  P: PStrRec;
begin
  if Pointer(S) <> nil then
  begin
    P := Pointer(Integer(S) - Sizeof(StrRec));
    Pointer(S) := nil;
    if P.refCnt > 0 then
      if InterlockedDecrement(P.refCnt) = 0 then
        FreeMem(P);
  end;
end;

function _NewAnsiString(length: Longint): Pointer;
var
  P: PStrRec;
begin
  Result := nil;
  if length <= 0 then Exit;
  // Alloc an extra null for strings with even length.  This has no actual cost
  // since the allocator will round up the request to an even size anyway.
  // All widestring allocations have even length, and need a double null terminator.
  GetMem(P, length + sizeof(StrRec) + 1 + ((length + 1) and 1));
  Result := Pointer(Integer(P) + sizeof(StrRec));
  P.length := length;
  P.refcnt := 1;
  PWideChar(Result)[length div 2] := #0;  // length guaranteed >= 2
end;

procedure _LStrFromPCharLen(var Dest: AnsiString; Source: PAnsiChar; Length: Integer);
asm
  { ->    EAX     pointer to dest }
  {       EDX source              }
  {       ECX length              }

        PUSH    EBX
        PUSH    ESI
        PUSH    EDI

        MOV     EBX,EAX
        MOV     ESI,EDX
        MOV     EDI,ECX

        { allocate new string }

        MOV     EAX,EDI

        CALL    _NewAnsiString
        MOV     ECX,EDI
        MOV     EDI,EAX

        TEST    ESI,ESI
        JE      @@noMove

        MOV     EDX,EAX
        MOV     EAX,ESI
        CALL    Move

        { assign the result to dest }

@@noMove:
        MOV     EAX,EBX
        CALL    _LStrClr
        MOV     [EBX],EDI

        POP     EDI
        POP     ESI
        POP     EBX
end;

procedure _LStrFromPWCharLenD7(var Dest: AnsiString; Source: PWideChar; Length: Integer);
var
  DestLen: Integer;
  Buffer: array[0..4095] of Char;
begin
  if Length <= 0 then
  begin
    _LStrClr(Dest);
    Exit;
  end;
  if Length+1 < (High(Buffer) div sizeof(WideChar)) then
  begin
    DestLen := CharFromWCharD7(Buffer, High(Buffer), Source, Length);
    if DestLen >= 0 then
    begin
      _LStrFromPCharLen(Dest, Buffer, DestLen);
      Exit;
    end;
  end;

  DestLen := (Length + 1) * sizeof(WideChar);
  SetLength(Dest, DestLen);  // overallocate, trim later
  DestLen := CharFromWCharD7(Pointer(Dest), DestLen, Source, Length);
  if DestLen < 0 then DestLen := 0;
  SetLength(Dest, DestLen);
end;

procedure _LStrFromWStrD7(var Dest: AnsiString; const Source: WideString);
asm
        { ->    EAX pointer to dest              }
        {       EDX pointer to WideString data   }

        XOR     ECX,ECX
        TEST    EDX,EDX
        JE      @@1
        MOV     ECX,[EDX-4]
        SHR     ECX,1
@@1:    JMP     _LStrFromPWCharLenD7
end;
{$ENDIF}

function TData.ReadBlob(FieldNo: word; RecBuf: IntPtr; Position: longint;
  Count: longint; Dest: IntPtr; FromRollback: boolean = false;
  TrueUnicode: boolean = False): longint;
var
  Blob: TBlob;
  LenBytes, BlobPos: longint;
  Ws, Buf: IntPtr;
  s: string;
  
begin
  Blob := TBlob(GetObject(FieldNo, RecBuf));
  if FromRollback and (Blob.Rollback <> nil) then
    Blob := Blob.Rollback;

  if not Blob.FIsUnicode or TrueUnicode then
    Result := Blob.Read(Position, Count, Dest)
  else
  begin
    BlobPos := Blob.TranslatePosition(Position);
    if Count = 0 then
      LenBytes := LongInt(Blob.Size) - BlobPos
    else
      LenBytes := Blob.TranslatePosition(Count);

    Ws := Marshal.AllocHGlobal(LenBytes);
    Buf := nil;
    try
      Result := Blob.Read(BlobPos, LenBytes, Ws);
    {$IFNDEF VER6}
      s := Marshal.PtrToStringUni(Ws, Result div 2);
    {$ELSE}
      _LStrFromWStrD7(s, Marshal.PtrToStringUni(Ws, Result div 2));
    {$ENDIF}
      Result := Length(s);
      Buf := Marshal.StringToHGlobalAnsi(s);
      CopyBuffer(Buf, Dest, Result);
    finally
      Marshal.FreeHGlobal(Ws);
      if Buf <> nil then
        Marshal.FreeCoTaskMem(Buf);
    end;
  end;
end;

procedure TData.WriteBlob(FieldNo: word; RecBuf: IntPtr; Position: longint;
  Count: longint; Source: IntPtr; TrueUnicode: boolean = False);
var
  Blob: TBlob;
  Buf: IntPtr;
  S: string;
  Ws: WideString;

begin
  Blob := TBlob(GetObject(FieldNo, RecBuf));

  Blob.EnableRollback;
  if not Blob.FIsUnicode or TrueUnicode then
    Blob.Write(Position, Count, Source)
  else begin
    S := Marshal.PtrToStringAnsi(Source, Count);
    Ws := S;
    Count := Length(Ws) * 2; // for MBCS this differ from Count * 2
    Position := Blob.TranslatePosition(Position);
    Buf := Marshal.StringToHGlobalUni(Ws);
    try
      Blob.Write(Position, Count, Buf); //Count length in bytes
    finally
      Marshal.FreeCoTaskMem(Buf);
    end;
  end;

  SetNull(FieldNo, RecBuf, False);
end;

procedure TData.TruncateBlob(FieldNo: word; RecBuf: IntPtr; Size: longint;
  TrueUnicode: boolean = False);
var
  Blob:TBlob;
begin
  Blob := TBlob(GetObject(FieldNo, RecBuf));

  Blob.EnableRollback;
  if Blob.FIsUnicode and not TrueUnicode then
    Size := Blob.TranslatePosition(Size);

  Blob.Truncate(Size);

  if Size = 0 then
    SetNull(FieldNo, RecBuf, True);
end;

function TData.GetBlobSize(FieldNo: word; RecBuf: IntPtr; FromRollback: boolean = false;
  TrueUnicode: boolean = False): longint;
var
  Blob: TBlob;
begin
  if GetNull(FieldNo, RecBuf) then begin
    Result := 0;
    Exit;
  end;
  Blob := TBlob(GetObject(FieldNo, RecBuf));
  if FromRollback and (Blob.Rollback <> nil) then
    Blob := Blob.Rollback;
  if not Blob.FIsUnicode or TrueUnicode then
    Result := Blob.Size
  else
    Result := Blob.GetSizeAnsi;
end;

procedure TData.SetBlobSize(FieldNo: word; RecBuf: IntPtr; NewSize: longint; FromRollback: boolean = false;
  TrueUnicode: boolean = False);
var
  Blob: TBlob;
  OldSize: integer;
begin
  SetNull(FieldNo, RecBuf, False);

  Blob := TBlob(GetObject(FieldNo, RecBuf));
  if FromRollback and (Blob.Rollback <> nil) then
    Blob := Blob.Rollback;

  if Blob.FIsUnicode and not TrueUnicode then begin
    // Blob.Size is char count * 2
    OldSize := Blob.GetSizeAnsi;
    if NewSize > OldSize then
      Blob.Size := Integer(Blob.Size) + (NewSize - OldSize) * 2
    else
      Blob.Size := Blob.TranslatePosition(NewSize);
  end
  else
    Blob.Size := NewSize;
end;

procedure TData.SetCachedUpdates(Value: boolean);
begin
  if Value <> FCachedUpdates then begin
    if FCachedUpdates then
      CancelUpdates;

    FCachedUpdates := Value;

    if FCachedUpdates then
      FFilterItemTypes := [isUnmodified, isUpdated, isAppended];
  end;
end;

procedure TData.SetFilterText(Value: string);
begin
  if Value <> FFilterText then begin
    if Active then
      CreateFilterExpression(Value);
    FFilterText := Value;
  end;
end;

procedure TData.SetFilterItemTypes(Value:TItemTypes);
begin
  FFilterItemTypes := Value;
end;

{ TSortColumns }

destructor TSortColumns.Destroy;
begin
  Clear;

  inherited;
end;

procedure TSortColumns.Clear;
var
  i: integer;
begin
  for i := 0 to Count - 1 do
    if Items[i] <> nil then
      TSortColumn(Items[i]).Free;

  inherited Clear;
end;

function TSortColumns.GetItems(Index: integer): TSortColumn;
begin
  Result := TSortColumn(inherited Items[Index]);
end;

{ TMemData }

constructor TMemData.Create;
begin
  inherited;

  BlockMan := TBlockManager.Create;
  FIndexFields := TSortColumns.Create;

  InitData;
end;

destructor TMemData.Destroy;
begin
  inherited;

  FIndexFields.Free;
  BlockMan.Free;
  SetLength(FRecordNoCache, 0);
end;

{ Items / Data }

procedure TMemData.SetIndexFieldNames(Value: string);
begin
  FIndexFieldNames := Value;
  if Active then begin
    UpdateIndexFields;
    SortItems;
  end;
end;

procedure TMemData.UpdateIndexFields;
var
  S, S1: string;
  FldName: string;
  FieldDesc: TFieldDesc;
  SortColumn: TSortColumn;
  ProcessedCS, ProcessedDESC: boolean;

  procedure RaiseError;
  begin
    raise Exception.Create('Invalid IndexFieldNames format!');
  end;

begin
  FIndexFields.Clear;
  S := FIndexFieldNames;
  if Trim(S) <> '' then begin
    Parser := TBoolParser.Create(S);

    try
      Parser.ToBegin();
      Code := Parser.GetNext(S1);
      while Code <> lcEnd do begin
        case Code of
          lcIdent, lcString, lxLeftSqBracket: begin
            if Code = lxLeftSqBracket then begin
              Parser.OmitBlank := False;
              Code := Parser.GetNext(S1);
              FldName := '';
              while (Code <> lxRightSqBracket) and (Code <> lcEnd) do begin
                FldName := FldName + S1;
                Code := Parser.GetNext(S1);
              end;
              Parser.OmitBlank := True;
              S1 := FldName;
            end;

            FieldDesc := Fields.FindField(S1);
            if FieldDesc = nil then
              raise Exception.Create(Format(SFieldNotFound, [S1]));
            SortColumn := TSortColumn.Create;
            SortColumn.FieldDesc := FieldDesc;
            SortColumn.CaseSensitive := True;
            FIndexFields.Add(SortColumn);
            Code := Parser.GetNext(S1);
            ProcessedCS := False;
            ProcessedDESC := False;
            while not (((Code = lcSymbol) and ((S1 = ';') or (S1 = ','))) or (Code = lcEnd)) do begin
              if Code = lcIdent then begin
                if not ProcessedDESC and ('DESC' = UpperCase(S1)) then begin
                  SortColumn.DescendingOrder := True;
                  ProcessedDESC := True;
                end
                else
                if not ProcessedCS and ('CIS' = UpperCase(S1)) then begin
                  SortColumn.CaseSensitive := False;
                  ProcessedCS := True;
                end
                else
                if not ProcessedDESC and ('ASC' = UpperCase(S1)) then
                  ProcessedDESC := True
                else
                if not ProcessedCS and ('CS' = UpperCase(S1)) then
                  ProcessedCS := True
                else
                  RaiseError;
                Code := Parser.GetNext(S1);
              end
              else
                RaiseError;
            end;
          end;
          lcSymbol: begin
            if (S1 <> ';') and (S1 <> ',') then
              RaiseError;
            Code := Parser.GetNext(S1);
          end
        else
          RaiseError;
        end;
      end;
    finally
      Parser.Free;
    end;
  end;
end;

{$IFNDEF CLR}
function TMemData.InternalAnsiStrComp(const Value1, Value2: IntPtr;
  const Options: TLocateExOptions): integer;
begin
  if lxCaseInsensitive in Options then
    Result := AnsiStrICompS(Value1, Value2)
  else
    Result := AnsiStrCompS(Value1, Value2);
end;
{$ENDIF}

function TMemData.InternalAnsiCompareText(const Value1, Value2: string;
  const Options: TLocateExOptions): integer;
begin
  if lxCaseInsensitive in Options then
    Result := AnsiCompareTextS(Value1, Value2)
  else
    Result := AnsiCompareStrS(Value1, Value2);
end;

function TMemData.InternalWStrLComp(const Value1, Value2: WideString;
  const Options: TLocateExOptions): integer;
begin
  if lxCaseInsensitive in Options then
    Result := AnsiStrLICompWS(Value1, Value2, Length(Value1))
  else
    Result := AnsiStrLCompWS(Value1, Value2, Length(Value1))
end;

function TMemData.InternalWStrComp(const Value1, Value2: WideString;
  const Options: TLocateExOptions): integer;
begin
  if lxCaseInsensitive in Options then
    Result := AnsiStrICompWS(Value1, Value2)
  else
    Result := AnsiStrCompWS(Value1, Value2);
end;

// Used to compare field value and string KeyValue with matching options
function TMemData.CompareStrValues(const Value: string;
  const FieldValue: string; const Options: TLocateExOptions): integer;
var
  Res: integer;
  ValueLen: integer;
begin
  if lxPartialCompare in Options then begin
    if lxCaseInsensitive in Options then
      Res := Pos(AnsiUpperCase(Value), AnsiUpperCase(FieldValue))
    else
      Res := Pos(Value, FieldValue);
    if Res = 0 then
      Res := 1
    else
      Res := 0;
  end
  else
  if lxPartialKey in Options then begin
    ValueLen := Length(Value);
    if ValueLen = 0 then
      ValueLen := Length(FieldValue);
    if Length(FieldValue) >= ValueLen then
      Result := 0
    else
      Result := 1;
    if Result <> 0 then
      Exit // To avoid AV in case Len(Value) > Len(St)
    else
      Res := InternalAnsiCompareText(Value, Copy(FieldValue, 1, ValueLen), Options);
  end
  else
    Res := InternalAnsiCompareText(Value, FieldValue, Options);
  Result := Res;
end;

function TMemData.CompareWideStrValues(const Value: WideString;
  const FieldValue: WideString; const Options: TLocateExOptions): integer;
var
  Res: integer;
  ValueLen: integer;

{$IFDEF MSWINDOWS}
  ValueS, FieldValueS: string;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
  if IsWin9x then begin
    ValueS := Value;
    FieldValueS := FieldValue;
    Result := CompareStrValues(ValueS, FieldValueS, Options);
    Exit;
  end;
{$ENDIF}

  if lxPartialCompare in Options then begin
    if lxCaseInsensitive in Options then
      Res := Pos(WideUpperCase(Value), WideUpperCase(FieldValue))
    else
      Res := Pos(Value, FieldValue);
    if Res = 0 then
      Res := 1
    else
      Res := 0;
  end
  else
  if lxPartialKey in Options then begin
    ValueLen := Length(Value);
    if Length(FieldValue) >= ValueLen then
      Result := 0
    else
      Result := 1;
    if Result <> 0 then
      Exit // To avoid AV in case Len(Value) > Len(St)
    else
      Res := InternalWStrLComp(Value, FieldValue, Options);
  end
  else
    Res := InternalWStrComp(Value, FieldValue, Options);
  Result := Res;
end;

// Used to compare binary field value and binary KeyValue with matching options
function TMemData.CompareBinValues(const Value: IntPtr;
  const ValueLen: integer; const FieldValue: IntPtr;
  const FieldValueLen: integer; const Options: TLocateExOptions): integer;

  function CompareMem(FieldValue, Value: IntPtr; FieldValueLen: integer): integer;
  var
    i: integer;
  begin
    for i := 0 to FieldValueLen div 4 - 1 do begin
      if Longword(Marshal.ReadInt32(Value, i shl 2)) > Longword(Marshal.ReadInt64(FieldValue, i shl 2)) then begin
        Result := 1;
        Exit;
      end
      else
      if Longword(Marshal.ReadInt32(Value, i shl 2)) < Longword(Marshal.ReadInt64(FieldValue, i shl 2)) then begin
        Result := -1;
        Exit;
      end
    end;
    for i := ((FieldValueLen - 1) and $fffffffc) to FieldValueLen - 1 do begin
      if Marshal.ReadByte(Value, i) > Marshal.ReadByte(FieldValue, i) then begin
        Result := 1;
        Exit;
      end
      else
      if Marshal.ReadByte(Value, i) > Marshal.ReadByte(FieldValue, i) then begin
        Result := -1;
        Exit;
      end
    end;
    Result := 0;
  end;

var
  i: integer;

begin
  if lxPartialCompare in Options then begin
    if FieldValueLen >= ValueLen then
      Result := 0
    else
      Result := 1;
    if Result <> 0 then
      Exit // Field value is shorter when Value
    else
    begin
      for i := integer(FieldValue) to integer(FieldValue) + FieldValueLen - ValueLen - 1 do begin
        Result := CompareMem(IntPtr(i), Value, ValueLen);
        if Result = 0 then
          Break;
      end;
      Result := 1;
    end;
  end
  else
  if lxPartialKey in Options then begin
    if FieldValueLen >= ValueLen then
      Result := 0
    else
      Result := 1;
    if Result <> 0 then
      Exit // Field value is shorter when Value
    else
      Result := CompareMem(FieldValue, Value, ValueLen);
  end
  else
  begin
    if ValueLen = FieldValueLen then
      Result := 0
    else
      Result := 1;
    if Result <> 0  then
      Exit
    else
      Result := CompareMem(FieldValue, Value, FieldValueLen);
  end;
end;

// Used to compare field value and KeyValue from MemDataSet.LocateRecord
function TMemData.CompareFieldValue(
  ValuePtr: IntPtr; const ValueType: integer; FieldDesc: TFieldDesc;
  RecBuf: IntPtr; const Options: TLocateExOptions): integer;
var
  St: string;
  WSt: WideString;
  BlobValue: IntPtr;
  FieldBuf: IntPtr;
  FieldBufStatic: IntPtr;
  IsBlank: boolean;
  l: integer;
  c, cValue: Currency;
{$IFDEF VER6P}
  bcd, bcdValue: TBcd;
{$ENDIF}
{$IFDEF CLR}
  Data: TBytes;
{$ENDIF}
  Value: string;
  WValue: WideString;
  v1, v2: variant;
  v1VType, v2VType: TVarType;
{$IFNDEF CLR}
  v1VArray, v2VArray: PVarArray;
  v1VArrayData, v2VArrayData: IntPtr;
{$ENDIF}

begin
  FieldBufStatic := nil;

  if FieldDesc.ParentField = nil then
    FieldBuf := IntPtr(integer(RecBuf) + FieldDesc.Offset)
  else begin
  // support objects
    FieldBufStatic := Marshal.AllocHGlobal(4001);
    FieldBuf := FieldBufStatic;
    GetField(FieldDesc.FieldNo, RecBuf, FieldBuf, IsBlank);  // GetChildField
  end;

  Result := 0;
  try
    case ValueType of
      dtString, dtGuid: begin
        case FieldDesc.DataType of
          dtString: begin
          {$IFNDEF CLR}
            if not (lxPartialKey in Options)
              and not (lxPartialCompare in Options)
              and not (FieldDesc.Fixed and TrimFixedChar)
              and not (not FieldDesc.Fixed and TrimVarChar)
            then begin
              Result := InternalAnsiStrComp(ValuePtr, FieldBuf, Options);
              Exit;
            end;
          {$ENDIF}
            St := Marshal.PtrToStringAnsi(FieldBuf);
            if FieldDesc.Fixed and TrimFixedChar then
              St := Trim(St)
            else
            if not FieldDesc.Fixed and TrimVarChar then
              St := Trim(St)
          end;
          dtWideString: begin
            St := Marshal.PtrToStringUni(FieldBuf);
            if FieldDesc.Fixed and TrimFixedChar then
              St := Trim(St)
            else
            if not FieldDesc.Fixed and TrimVarChar then
              St := Trim(St)
          end;
          dtExtString: begin
          {$IFNDEF CLR}
            if not (lxPartialKey in Options) and not (lxPartialCompare in Options) then begin
              Result := InternalAnsiStrComp(ValuePtr, Marshal.ReadIntPtr(FieldBuf), Options);
              Exit;
            end;
          {$ENDIF}
            St := Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(FieldBuf));
          end;
          dtExtWideString:
            St := Marshal.PtrToStringUni(Marshal.ReadIntPtr(FieldBuf));
        {$IFDEF VER5P}
          dtVariant:
            St := TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf))).Value;
        {$ENDIF}
          dtInt8:
            St := IntToStr(ShortInt(Marshal.ReadByte(FieldBuf)));
          dtInt16:
            St := IntToStr(Marshal.ReadInt16(FieldBuf));
          dtUInt16:
            St := IntToStr(Word(Marshal.ReadInt16(FieldBuf)));
          dtInt32:
            St := IntToStr(Marshal.ReadInt32(FieldBuf));
          dtUInt32:
            St := IntToStr(Longword(Marshal.ReadInt32(FieldBuf)));
          dtInt64:
            St := IntToStr(Marshal.ReadInt64(FieldBuf));
          dtFloat, dtCurrency:
            St := FloatToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtDate:
            St := DateToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtTime:
            St := TimeToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtDateTime:
            St := DateTimeToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtBCD:
          begin
            c := Marshal.ReadInt64(RecBuf, FieldDesc.Offset) / 10000;
            St := CurrToStr(c);
            Result := CompareStrValues(Marshal.PtrToStringAnsi(ValuePtr), St, Options + [lxCaseInsensitive]);
            Exit;
          end;
        {$IFDEF VER6P}
          dtFmtBCD:
          begin
          {$IFDEF CLR}
            SetLength(Data, SizeOfTBcd);
            Marshal.Copy(IntPtr(Integer(RecBuf) + FieldDesc.Offset), Data, 0, SizeOfTBcd);
            bcd := TBcd.FromBytes(Data);
          {$ELSE}
            bcd := PBcd(PChar(RecBuf) + FieldDesc.Offset)^;
          {$ENDIF}
            St := BcdToStr(bcd);
            Result := CompareStrValues(Marshal.PtrToStringAnsi(ValuePtr), St, Options + [lxCaseInsensitive]);
            Exit;
          end;
        {$ENDIF}
          dtGuid:
          begin
            Result := CompareStrValues(Marshal.PtrToStringAnsi(ValuePtr), Marshal.PtrToStringAnsi(FieldBuf), Options + [lxCaseInsensitive]);
            Exit;
          end;
        else
          if IsBlobFieldType(FieldDesc.DataType) then begin
            l := GetBlobSize(FieldDesc.FieldNo, RecBuf);
            BlobValue := Marshal.AllocHGlobal(l + 1);
            try
              ReadBlob(FieldDesc.FieldNo, RecBuf, 0, l, BlobValue);
              St := Marshal.PtrToStringAnsi(BlobValue, l);
            finally
              Marshal.FreeHGlobal(BlobValue);
            end;
          end
          else
            raise EConvertError.Create(SCannotConvertType);
        end;

        if ((FieldDesc.DataType = dtString) or (FieldDesc.DataType = dtWideString))
          and ((FieldDesc.Fixed and TrimFixedChar) or (not FieldDesc.Fixed and TrimVarChar)) then
          Value := Trim(Marshal.PtrToStringAnsi(ValuePtr))
        else
          Value := Marshal.PtrToStringAnsi(ValuePtr);
        Result := CompareStrValues(Value, St, Options);
      end;
      dtWideString: begin
        case FieldDesc.DataType of
          dtWideString: begin
            WSt := Marshal.PtrToStringUni(FieldBuf);
            if FieldDesc.Fixed and TrimFixedChar then
              WSt := Trim(WSt)
            else
            if not FieldDesc.Fixed and TrimVarChar then
              WSt := Trim(WSt)
          end;
          dtString: begin
            WSt := Marshal.PtrToStringAnsi(FieldBuf);
            if FieldDesc.Fixed and TrimFixedChar then
              WSt := Trim(WSt)
            else
            if not FieldDesc.Fixed and TrimVarChar then
              WSt := Trim(WSt)
          end;
          dtExtString:
            WSt := Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(FieldBuf));
          dtExtWideString:
            WSt := Marshal.PtrToStringUni(Marshal.ReadIntPtr(FieldBuf));
        {$IFDEF VER5P}
          dtVariant:
            WSt := TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf))).Value;
        {$ENDIF}
          dtInt8:
            WSt := IntToStr(ShortInt(Marshal.ReadByte(FieldBuf)));
          dtInt16:
            WSt := IntToStr(Marshal.ReadInt16(FieldBuf));
          dtUInt16:
            WSt := IntToStr(Word(Marshal.ReadInt16(FieldBuf)));
          dtInt32:
            WSt := IntToStr(Marshal.ReadInt32(FieldBuf));
          dtUInt32:
            WSt := IntToStr(Longword(Marshal.ReadInt16(FieldBuf)));
          dtInt64:
            WSt := IntToStr(Marshal.ReadInt64(FieldBuf));
          dtFloat, dtCurrency:
            WSt := FloatToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtDate:
            WSt := DateToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtTime:
            WSt := TimeToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtDateTime:
            WSt := DateTimeToStr(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)));
          dtBCD:
          begin
            c := Marshal.ReadInt64(RecBuf, FieldDesc.Offset) / 10000;
            WSt := CurrToStr(c);
            Result := CompareWideStrValues(Marshal.PtrToStringUni(ValuePtr), WSt, Options + [lxCaseInsensitive]);
            Exit;
          end;
        else
          if IsBlobFieldType(FieldDesc.DataType) then begin
            BlobValue := Marshal.AllocHGlobal(GetBlobSize(FieldDesc.FieldNo, RecBuf));
            try
              ReadBlob(FieldDesc.FieldNo, RecBuf, 0, 0, BlobValue);
              St := Marshal.PtrToStringUni(BlobValue);
            finally
              Marshal.FreeHGlobal(BlobValue);
            end;
          end
          else
            raise EConvertError.Create(SCannotConvertType);
        end;

        if ((FieldDesc.DataType = dtString) or (FieldDesc.DataType = dtWideString))
          and ((FieldDesc.Fixed and TrimFixedChar) or (not FieldDesc.Fixed and TrimVarChar)) then
          WValue := Trim(Marshal.PtrToStringUni(ValuePtr))
        else
          WValue := Marshal.PtrToStringUni(ValuePtr);
        Result := CompareWideStrValues(WValue, WSt, Options);
      end;
      dtInt8:
        if ShortInt(Marshal.ReadByte(FieldBuf)) < ShortInt(Marshal.ReadByte(ValuePtr)) then
          Result := 1
        else
        if ShortInt(Marshal.ReadByte(FieldBuf)) > ShortInt(Marshal.ReadByte(ValuePtr)) then
          Result := -1
        else
          Result := 0;
      dtInt16:
        if Marshal.ReadInt16(FieldBuf) < Marshal.ReadInt16(ValuePtr) then
          Result := 1
        else
        if Marshal.ReadInt16(FieldBuf) > Marshal.ReadInt16(ValuePtr) then
          Result := -1
        else
          Result := 0;
      dtUInt16:
        if Word(Marshal.ReadInt16(FieldBuf)) < Word(Marshal.ReadInt16(ValuePtr)) then
          Result := 1
        else
        if Word(Marshal.ReadInt16(FieldBuf)) > Word(Marshal.ReadInt16(ValuePtr)) then
          Result := -1
        else
          Result := 0;
      dtInt32:
        if Marshal.ReadInt32(FieldBuf) < Marshal.ReadInt32(ValuePtr) then
          Result := 1
        else
        if Marshal.ReadInt32(FieldBuf) > Marshal.ReadInt32(ValuePtr) then
          Result := -1
        else
          Result := 0;
      dtUInt32:
        if Longword(Marshal.ReadInt32(FieldBuf)) < Longword(Marshal.ReadInt32(ValuePtr)) then
          Result := 1
        else
        if Longword(Marshal.ReadInt32(FieldBuf)) > Longword(Marshal.ReadInt32(ValuePtr)) then
          Result := -1
        else
          Result := 0;
      dtInt64:
        if Marshal.ReadInt64(FieldBuf) < Marshal.ReadInt64(ValuePtr) then
          Result := 1
        else
        if Marshal.ReadInt64(FieldBuf) > Marshal.ReadInt64(ValuePtr) then
          Result := -1
        else
          Result := 0;
      dtBoolean:
        if (Marshal.ReadByte(FieldBuf) = 0) = (Marshal.ReadByte(ValuePtr) = 0) then // Cannot use 'boolean(FieldBuf^) = boolean(ValuePtr^)' because 'True' may have any value without 0
          Result := 0
        else
        if (Marshal.ReadByte(FieldBuf) = 0) then
          Result := 1
        else
          Result := -1;
      dtFloat, dtCurrency,
      dtDateTime, dtDate, dtTime: begin
        if BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)) < BitConverter.Int64BitsToDouble(Marshal.ReadInt64(ValuePtr)) then
          Result := 1
        else
        if BitConverter.Int64BitsToDouble(Marshal.ReadInt64(FieldBuf)) > BitConverter.Int64BitsToDouble(Marshal.ReadInt64(ValuePtr)) then
          Result := -1
        else
          Result := 0;
      end;
      dtBytes:
        Result := CompareBinValues(IntPtr(integer(ValuePtr) + SizeOf(Word)),
         Marshal.ReadInt16(ValuePtr), FieldBuf, FieldDesc.Length, Options);
      dtVarBytes:
        Result := CompareBinValues(IntPtr(integer(ValuePtr) + SizeOf(Word)),
          Marshal.ReadInt16(ValuePtr), IntPtr(integer(FieldBuf) + SizeOf(Word)),
          Marshal.ReadInt16(FieldBuf), Options);
      dtExtVarBytes:
        Result := CompareBinValues(IntPtr(integer(ValuePtr) + SizeOf(Word)),
          Marshal.ReadInt16(ValuePtr),
          IntPtr(integer(Marshal.ReadIntPtr(FieldBuf)) + SizeOf(Word)),
          Marshal.ReadInt16(Marshal.ReadIntPtr(FieldBuf)), Options);
      dtBCD: begin
        c := Marshal.ReadInt64(RecBuf, FieldDesc.Offset) / 10000;
        cValue := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(ValuePtr));
        if c < cValue then
          Result := 1
        else
        if c > cValue then
          Result := -1
        else
          Result := 0;
      end;
      dtVariant: begin
        v1 := TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(FieldBuf))).Value;
        v2 := TVariantObject(GetGCHandleTarget(Marshal.ReadIntPtr(ValuePtr))).Value;
        v1VType := VarType(v1);
        v2VType := VarType(v2);

      {$IFNDEF CLR}
        if (v1VType = varArray + varByte) or (v2VType = varArray + varByte) then begin
          if (v1VType = varNull) and (v2VType = varNull) then
            Result := 0
          else
          if v1VType = varNull then // (v1VType = varNull) and (v2VType = varArray + varByte)
            Result := 1
          else
          if v2VType = varNull then // (v2VType = varNull) and (v1VType = varArray + varByte)
            Result := -1
          else // (v1VType <> varNull) and (v2VType <> varNull)
          if v1VType <> v2VType then begin
            if v1VType < v2VType then
              Result := 1
            else
              Result := -1;
          end
          else begin
            Assert(v1VType = varArray + varByte, 'Invalid v1.VType');
            Assert(v2VType = varArray + varByte, 'Invalid v2.VType');

            v1VArray := TVarData(v1).VArray;
            v2VArray := TVarData(v2).VArray;
            if (v1VArray = nil) and (v2VArray = nil) then
              Result := 0
            else
            if (v1VArray = nil) and (v2VArray = nil) then
              Result := 0
            else
            if v1VArray = nil then // (v1VArray = nil) and (v2VArray <> nil)
              Result := 1
            else
            if v2VArray = nil then // (v2VArray = nil) and (v1VArray <> nil)
              Result := -1
            else // (v1VArray <> nil) and (v2VArray <> nil)
            if v1VArray.Bounds[0].ElementCount < v2VArray.Bounds[0].ElementCount then
              Result := 1
            else
            if v1VArray.Bounds[0].ElementCount > v2VArray.Bounds[0].ElementCount then 
              Result := - 1
            else begin
              v1VArrayData := v1VArray.Data;
              v2VArrayData := v2VArray.Data;
              if (v1VArrayData = nil) and (v2VArrayData = nil) then
                Result := 0
              else
              if (v1VArrayData = nil) and (v2VArrayData = nil) then
                Result := 0
              else
              if v1VArrayData = nil then // (v1VArrayData = nil) and (v2VArrayData <> nil)
                Result := 1
              else
              if v2VArrayData = nil then // (v2VArrayData = nil) and (v1VArrayData <> nil)
                Result := -1
              else // (v1VArrayData <> nil) and (v2VArrayData <> nil)
                Result := CompareBinValues(v1VArrayData, v1VArray.Bounds[0].ElementCount, v2VArrayData, v2VArray.Bounds[0].ElementCount, Options);
            end;
          end
        end
        else
      {$ENDIF}
        {$IFNDEF VER6P}
          if (v1VType = v2VType) and (v1VType = varDecimal) then begin
            if PInt64(@TVarData(v1).VInteger)^ < PInt64(@TVarData(v2).VInteger)^ then
              Result := 1
            else
            if PInt64(@TVarData(v1).VInteger)^ > PInt64(@TVarData(v2).VInteger)^ then
              Result := -1
            else
              Result := 0;
          end
          else
        {$ENDIF}
          if (v1VType = v2VType) or
             ((((v1VType >= varSmallint) and (v1VType <= varCurrency)) or ((v1VType >= varDecimal) and (v1VType <= varInt64))) and
              (((v2VType >= varSmallint) and (v2VType <= varCurrency)) or ((v2VType >= varDecimal) and (v2VType <= varInt64)))) then begin // Equal VarType or Numbers
            if v1 < v2 then
              Result := 1
            else
            if v1 > v2 then
              Result := -1
            else
              Result := 0;
          end
          else
          if ((v1VType = varString) or (v1VType = varOleStr)) or
             ((v2VType = varString) or (v2VType = varOleStr)) then begin// String
            Result := CompareStrValues(v2, v1, Options)
          end
          else // VarType is different 
          if v1VType < v2VType then
            Result := 1
          else
            Result := -1;
      end;
    {$IFDEF VER6P}
      dtFmtBCD: begin
      {$IFDEF CLR}
        SetLength(Data, SizeOfTBcd);

        Marshal.Copy(IntPtr(Integer(RecBuf) + FieldDesc.Offset), Data, 0, SizeOfTBcd);
        bcd := TBcd.FromBytes(Data);

        Marshal.Copy(ValuePtr, Data, 0, SizeOfTBcd);
        bcdValue := TBcd.FromBytes(Data);
      {$ELSE}
        bcd := PBcd(PChar(RecBuf) + FieldDesc.Offset)^;
        bcdValue := PBcd(ValuePtr)^;
      {$ENDIF}
        Result := BcdCompare(bcdValue, bcd);
      end;
    {$ENDIF}
    else
      Assert(False, 'Unknown ValueType = ' + IntToStr(ValueType));
    end;
  finally
    if FieldBufStatic <> nil then
      Marshal.FreeHGlobal(FieldBufStatic);
  end;
end;

function TMemData.CompareFields(RecBuf1: IntPtr; RecBuf2: IntPtr; SortColumn: TSortColumn): integer;
var
  Options: TLocateExOptions;
begin
  if not SortColumn.CaseSensitive then
    Options := [lxCaseInsensitive]
  else
    Options := [];

  Result := CompareFields(RecBuf1, RecBuf2, SortColumn.FieldDesc, Options);
end;

function TMemData.CompareFields(RecBuf1: IntPtr; RecBuf2: IntPtr; FieldDesc: TFieldDesc; Options: TLocateExOptions = []): integer;
var
  FieldBuf: IntPtr;
  IsBlank1, IsBlank2: boolean;
  DataType: integer;
  NativeBuffer: boolean;
begin
  FieldBuf := nil;
  NativeBuffer := True;
  try
    FieldBuf := GetFieldBuf(RecBuf1, FieldDesc, DataType, IsBlank1, NativeBuffer);
    IsBlank2 := GetNull(FieldDesc.FieldNo, RecBuf2);
    if IsBlank1 and not IsBlank2 then
      Result := -1
    else
    if not IsBlank1 and IsBlank2 then
      Result := 1
    else
    if IsBlank1 and IsBlank2 then
      Result := 0
    else
      Result := CompareFieldValue(FieldBuf, DataType, FieldDesc,
        RecBuf2, Options);
  finally
    if not NativeBuffer then
      Marshal.FreeHGlobal(FieldBuf);
  end;
end;

function TMemData.CompareRecords(RecBuf1, RecBuf2: IntPtr): integer;
var
  SortColumn: TSortColumn;
  i: integer;
  Dir: integer;

  CalcRecBuf1, CalcRecBuf2: IntPtr;
begin
  CalcRecBuf1 := nil;
  CalcRecBuf2 := nil;
  try
    Result := 0;
    for i := 0 to FIndexFields.Count - 1 do begin
      SortColumn := FIndexFields.Items[i];
      if SortColumn.DescendingOrder then
        Dir := -1
      else
        Dir := 1;

      if SortColumn.FieldDesc.FieldDescKind = fdkCalculated then begin
        if CalcRecBuf1 = nil then begin
          CalcRecBuf1 := Marshal.AllocHGlobal(FRecordSize + CalcRecordSize);
          CalcRecBuf2 := Marshal.AllocHGlobal(FRecordSize + CalcRecordSize);
          if Assigned(FOnGetCachedBuffer) then
            FOnGetCachedBuffer(CalcRecBuf1, RecBuf1);
          if Assigned(FOnGetCachedBuffer) then
            FOnGetCachedBuffer(CalcRecBuf2, RecBuf2);
        end;
        Result := CompareFields(CalcRecBuf1, CalcRecBuf2, SortColumn) * Dir;
      end
      else
        Result := CompareFields(RecBuf1, RecBuf2, SortColumn) * Dir;

      if Result <> 0 then
        break;
    end;
  finally
    if CalcRecBuf1 <> nil then
      Marshal.FreeHGlobal(CalcRecBuf1);
    if CalcRecBuf2 <> nil then
      Marshal.FreeHGlobal(CalcRecBuf2);
  end;
end;

procedure TMemData.Exchange(I, J: PItemHeader);
var
  NextToI, PrevToJ: PItemHeader;
begin
  NextToI := I.Next;
  PrevToJ := J.Prev;
  if IntPtr(I.Prev) <> nil then
    I.Prev.Next := J;
  if IntPtr(J.Next) <> nil then
    J.Next.Prev := I;
  J.Prev := I.Prev;
  I.Next := J.Next;
  if NextToI = J then begin
    I.Prev := J;
    J.Next := I;
  end
  else begin
    I.Prev := PrevToJ;
    if IntPtr(PrevToJ) <> nil then
      PrevToJ.Next := I;
    J.Next := NextToI;
    if IntPtr(NextToI) <> nil then
      NextToI.Prev := J;
  end;

  if I = FirstItem then FirstItem := J;
  if J = LastItem then LastItem := I;
end;

procedure TMemData.MoveSortedRecord(Dir: integer);
begin
  if Dir = 0 then
    Exit;
  while True do begin
    if Dir > 0 then begin
      if (IntPtr(CurrentItem.Next) <> nil) and
        (CompareRecords(IntPtr(Integer(CurrentItem) + sizeof(TItemHeader)), IntPtr(Integer(CurrentItem.Next) + sizeof(TItemHeader))) > 0)
      then
        Exchange(CurrentItem, CurrentItem.Next)
      else
        break;
    end
    else begin
      if (IntPtr(CurrentItem.Prev) <> nil) and
        (CompareRecords(IntPtr(Integer(CurrentItem) + sizeof(TItemHeader)), IntPtr(Integer(CurrentItem.Prev) + sizeof(TItemHeader))) < 0)
      then
        Exchange(CurrentItem.Prev, CurrentItem)
      else
        break;
    end;
  end;
end;

procedure TMemData.QuickSort(L, R, P: PItemHeader);
var
  I, J, IP, JP, I1: PItemHeader;
  changeIP, changeJP: boolean;
begin
  repeat
    I := L;
    J := R;
    IP := I;
    JP := J;
    changeIP := False;
    changeJP := False;
    while True do begin
      while (IntPtr(I) <> IntPtr(P)) and (CompareRecords(IntPtr(Integer(I) + sizeof(TItemHeader)), IntPtr(Integer(P) + sizeof(TItemHeader))) < 0) do begin
        I := I.Next;
        if changeIP then
          IP := IP.Next;
        changeIP := not changeIP;
      end;
      while (IntPtr(J) <> IntPtr(P)) and (CompareRecords(IntPtr(Integer(J) + sizeof(TItemHeader)), IntPtr(Integer(P) + sizeof(TItemHeader))) > 0) do begin
        J := J.Prev;
        if changeJP then
          JP := JP.Prev;
        changeJP := not changeJP;
      end;
      if (IntPtr(J.Next) = IntPtr(I)) or
        (IntPtr(I) = IntPtr(J))
      then
        break;

      if CompareRecords(IntPtr(Integer(I) + sizeof(TItemHeader)), IntPtr(Integer(J) + sizeof(TItemHeader))) <> 0 then begin
        Exchange(I, J);
        I1 := I;
        I := J;
        J := I1;
        if L = I then
          L := J
        else
        if L = J then
          L := I;

        if JP = I then
          JP := J
        else
        if JP = J then
          JP := I;

        if IP = I then
          IP := J
        else
        if IP = J then
          IP := I;

        if R = I then
          R:=J
        else
        if R=J then
          R:=I;
      end;

      if IntPtr(I) <> IntPtr(R) then begin
        I := I.Next;
        if changeIP then
          IP := IP.Next;
        changeIP := not changeIP;
      end;
      if IntPtr(J) <> IntPtr(L) then begin
        J := J.Prev;
        if changeJP then
          JP := JP.Prev;
        changeJP := not changeJP;
      end;
    end;
    if IntPtr(L) <> IntPtr(J) then QuickSort(L, J, IP);
    if (IntPtr(I) = IntPtr(J)) and (IntPtr(I) <> IntPtr(R)) then
      I := I.Next;
    L := I;
    P := JP;
  until I = R;
end;

procedure TMemData.SortItems;
begin
  if FIndexFields.Count = 0 then
    Exit;
  if (IntPtr(FirstItem) <> nil) and (IntPtr(LastItem) <> nil) then begin
    QuickSort(FirstItem, LastItem, FirstItem);
    ReorderItems(nil, roFull);
  end;
end;

function TMemData.InsertItem: PItemHeader;
var
  Item: PItemHeader;
begin
  if EOF then begin
    Result := AppendItem;
    Exit;
  end;

  if BOF then
    CurrentItem := FirstItem;

  BlockMan.AllocItem(Item);

  Item.Next := CurrentItem;

  if IntPtr(CurrentItem) <> nil then begin
    Item.Prev := CurrentItem.Prev;
    if IntPtr(CurrentItem.Prev) <> nil then
      CurrentItem.Prev.Next := Item;
    CurrentItem.Prev := Item
  end
  else begin
    Item.Prev := nil;
  end;

  if FirstItem = CurrentItem then
    FirstItem := Item;

  if IntPtr(LastItem) = nil then
    LastItem := Item;

  Result := Item;
end;

function TMemData.AppendItem: PItemHeader;
var
  Item: PItemHeader;
begin
  BlockMan.AllocItem(Item);

  if IntPtr(FirstItem) = nil then begin
    FirstItem := Item;
    Item.Order := 1;
  end
  else begin
    LastItem.Next := Item;
    Item.Order := LastItem.Order + 1;
  end;

  Item.Prev := LastItem;
  Item.Next := nil;
  LastItem := Item;

  Result := Item;
end;

procedure TMemData.DeleteItem(Item: PItemHeader);
begin
  if IntPtr(Item) <> nil then begin
    if Item = FirstItem then
      if Item = LastItem then begin
        CurrentItem := nil;
        FirstItem := nil;
        LastItem := nil;
        FBOF := True;
        FEOF := True;
      end
      else begin
        FirstItem := Item.Next;
        FirstItem.Prev := nil;
        if Item = CurrentItem then
          CurrentItem := FirstItem;
      end
    else
      if Item = LastItem then begin
        LastItem := Item.Prev;
        LastItem.Next := nil;
        if Item = CurrentItem then
          CurrentItem := LastItem;
      end
      else begin
        if Item = CurrentItem then
          CurrentItem := Item.Next;

        if IntPtr(Item.Prev) <> nil then
          Item.Prev.Next := Item.Next;
        if IntPtr(Item.Next) <> nil then
          Item.Next.Prev := Item.Prev;
      end;

    {if IsComplexFields then
      FreeComplexFields(PChar(Item) + sizeof(TItemHeader), True);}

    BlockMan.FreeItem(Item);
  end;
end;

procedure TMemData.InitData;
begin
  FirstItem := nil;
  LastItem := nil;
  CurrentItem := nil;
  Cache := nil;
  LastCacheItem := nil;

  FBOF := True;
  FEOF := True;
  FRecordCount := 0;
  FRecordNoOffset := 0;

  BlockMan.FirstFree := nil;
  Inc(RefreshIteration);
  FRefreshIteration := RefreshIteration;
end;

procedure TMemData.FreeData;
var
  CacheItem: TCacheItem;
  Item: PItemHeader;
  NeedFreeComplex: boolean;

  function HasComplexFields (IncludeStrings : boolean): boolean;
  var
    i: integer;
  begin
    Result := False;
    for i := 0 to FieldCount - 1 do
      if IsComplexFieldType(Fields[i].DataType) then
        case Fields[i].DataType of
          dtExtString, dtExtWideString, dtExtVarBytes:
            Result := IncludeStrings;
        else
          begin
            Result := True;
            Exit;
          end;
        end;
  end;

begin
  if not StringHeap.SysGetMem then begin
    NeedFreeComplex := HasComplexFields(False);
    StringHeap.Clear;
  end
  else
    NeedFreeComplex := HasComplexFields(True);

  if NeedFreeComplex then begin
  // Free complex fields
    Item := FirstItem;
    while IntPtr(Item) <> nil do begin
      FreeComplexFields(IntPtr(Integer(Item) + sizeof(TItemHeader)), True);
      Item := Item.Next;
    end;
    CacheItem := Cache;
    while CacheItem <> nil do begin
      Item := CacheItem.Item.Rollback;
      if IntPtr(Item) <> nil then
        FreeComplexFields(IntPtr(Integer(Item) + sizeof(TItemHeader)), True);
      CacheItem := CacheItem.Next;
    end;
  end;

// Free cache
  while Cache <> nil do begin
    CacheItem := Cache;
    Cache := Cache.Next;
    CacheItem.Free;
  end;

  StringHeap.Clear;
  BlockMan.FreeAllBlock;

  InitData;
end;

procedure TMemData.ReorderItems(Item: PItemHeader; ReorderOption: TReorderOption);
var
  No: longint;
  Item1: PItemHeader;
begin
  if Length(FRecordNoCache) > 0 then
    SetLength(FRecordNoCache, 0);
  if (IntPtr(Item) <> nil) or (ReorderOption = roFull) and (IntPtr(FirstItem) <> nil)
  then begin
    if ReorderOption = roFull then begin
      Item := FirstItem;
      No := 1;
    end
    else
      if IntPtr(Item.Next) <> nil then
        No := Item.Next.Order
      else
        if IntPtr(Item.Prev) <> nil then
          No := Item.Prev.Order
        else begin
          Item.Order := 1;
          FRecordNoOffset := 0;
        { $IFDEF LINUX
          No := 0; // Kylix 1 anti warning
         $ENDIF}
          Exit;
        end;

    if (ReorderOption = roFull) or (No > (FRecordCount + FRecordNoOffset) div 2)
    then begin
      Item1 := Item.Prev;
      while (IntPtr(Item1) <> nil) and OmitRecord(Item1) do
        Item1 := Item1.Prev;
      if IntPtr(Item1) <> nil then
        No := Item1.Order + 1
      else begin
        No := 1;
        FRecordNoOffset := 0;
      end;

      while IntPtr(Item) <> nil do begin

        if not OmitRecord(Item) then begin
          Item.Order := No;
          Inc(No);
        end
        else
          Item.Order := 0;
        Item := Item.Next;
      end;
    end
    else begin
      Item1 := Item.Next;
      while (IntPtr(Item1) <> nil) and OmitRecord(Item1) do
        Item1 := Item1.Next;
      if IntPtr(Item1) <> nil then begin
        No := Item1.Order - 1;
        if ReorderOption = roInsert then
          Inc(FRecordNoOffset)
        else
          Dec(FRecordNoOffset)
      end
      else begin
        No := FRecordCount;
        FRecordNoOffset := 0;
      end;

      while IntPtr(Item) <> nil do begin
        if not OmitRecord(Item) then begin
          Item.Order := No;
          Dec(No);
        end
        else
          Item.Order := 0;
        Item := Item.Prev;
      end;
    end;

    if ReorderOption = roFull then
      FRecordCount := No - 1;
  end;
end;

{ Fields }

procedure TMemData.Open;
begin
  inherited Open;
end;

procedure TMemData.Reopen;
begin
  inherited;

  if Length(FRecordNoCache) > 0 then
    SetLength(FRecordNoCache, 0);

  // M11255
  if FilterText <> '' then
    FilterUpdated;
  // M11254
  if IndexFields.Count > 0 then
    SortItems;
end;

procedure TMemData.InitFields;
begin
  inherited;

  BlockMan.RecordSize := RecordSize;
  UpdateIndexFields;
end;

procedure TMemData.ClearFields;
begin
  FIndexFields.Clear;

  inherited;
end;

{ Records }

function TMemData.OmitRecord(Item: PItemHeader): boolean;
var
  LocalFilterBuf: IntPtr;
begin
  if IntPtr(Item) <> nil then begin
    if Item.FilterResult = fsNotChecked then begin
      FilterRecBuf := BlockMan.GetRecordPtr(Item);
      LocalFilterBuf := nil;
      try
        Result := FCachedUpdates and not (Item.Status in FFilterItemTypes);
        if not Result then begin
          if (CalcRecordSize > 0) and (Assigned(FFilterMDFunc) or Assigned(FilterExpression) or
            Assigned(FFilterFunc))
          then begin
            LocalFilterBuf := Marshal.AllocHGlobal(RecordSize + CalcRecordSize);
            CopyBuffer(FilterRecBuf, LocalFilterBuf, RecordSize);
            if Assigned(FOnGetCachedBuffer) then
              FOnGetCachedBuffer(LocalFilterBuf);
            FilterRecBuf := LocalFilterBuf;
          end;

          Result := Assigned(FFilterFunc) and not FFilterFunc(FilterRecBuf) or
            Assigned(FFilterMDFunc) and not FFilterMDFunc(FilterRecBuf) or
            Assigned(FilterExpression) and not Eval(FilterExpression);
        end;
      finally
        if LocalFilterBuf <> nil then
          Marshal.FreeHGlobal(LocalFilterBuf);
      end;
      if Result then
        Item.FilterResult := fsOmitted
      else
        Item.FilterResult := fsNotOmitted;
    end
    else
      Result := Item.FilterResult = fsOmitted;
  end
  else
    Result := True; //False;
end;

procedure TMemData.GetRecord(RecBuf: IntPtr);
begin
  if not(EOF or BOF or (IntPtr(CurrentItem) = nil)) then
    if OmitRecord(CurrentItem) then
      GetNextRecord(RecBuf)
    else
      BlockMan.GetRecord(CurrentItem, RecBuf);
end;

procedure TMemData.GetNextRecord(RecBuf: IntPtr);
  procedure OmitRecords;
  begin
    while (IntPtr(CurrentItem) <> nil) and OmitRecord(CurrentItem) do
      CurrentItem := CurrentItem.Next;
  end;
begin
  if not EOF then begin
    if BOF then begin
      FBOF := False;
      CurrentItem := FirstItem;
    end
    else
      if IntPtr(CurrentItem) <> nil then
        CurrentItem := CurrentItem.Next
      else
        CurrentItem := FirstItem;

    OmitRecords;
    if IntPtr(CurrentItem) = nil then
      FEOF := True
    else
      if RecBuf <> nil then
        GetRecord(RecBuf);
  end;
end;

procedure TMemData.GetPriorRecord(RecBuf: IntPtr);
  procedure OmitRecords;
  begin
    while (IntPtr(CurrentItem) <> nil) and OmitRecord(CurrentItem) do
      CurrentItem := CurrentItem.Prev;
  end;
begin
  if not BOF then begin
    if EOF then begin
      FEOF := False;
      CurrentItem := LastItem;
    end
    else
      if IntPtr(CurrentItem) <> nil then
        CurrentItem := CurrentItem.Prev
      else
        CurrentItem := LastItem;

    OmitRecords;
    if IntPtr(CurrentItem) = nil then
      FBOF := True
    else
      if RecBuf <> nil then
        GetRecord(RecBuf);
  end;
end;

procedure TMemData.UpdateCachedBuffer(FItem, LItem: PItemHeader);
var
  Item: PItemHeader;
begin
  if not Assigned(FOnGetCachedBuffer) or (CalcDataSize > 0) then
    Exit;

  if IntPtr(FItem) = nil then
    FItem := FirstItem;
  if IntPtr(LItem) = nil then
    LItem := LastItem;

  Item := FItem;

  while IntPtr(Item) <> nil do begin
    FOnGetCachedBuffer(IntPtr(Integer(Item) + SizeOf(TItemHeader)));
    if Item = LItem then
      Break;
    Item := Item.Next;
  end;
end;

procedure TMemData.PutRecord(RecBuf: IntPtr);
begin
  Assert(IntPtr(CurrentItem) <> nil);
  if Length(FRecordNoCache) > 0 then
    SetLength(FRecordNoCache, 0);
  CurrentItem.FilterResult := fsNotChecked;
  BlockMan.PutRecord(CurrentItem, RecBuf);
end;

procedure TMemData.AddRecord(RecBuf: IntPtr);
var
  OldCurrentItem: PItemHeader;
  MoveDir, i: integer;
  Blob: TBlob;
begin
  OldCurrentItem := CurrentItem;
  CurrentItem := InsertItem;

  PutRecord(RecBuf);

  if HasBlobFields then
    for i := 0 to FieldCount - 1 do
      if IsBlobFieldType(Fields[i].DataType) then begin
        Blob := TBlob(InternalGetObject(Fields[i].FieldNo, RecBuf));
        if Blob <> nil then
          Blob.Commit;
      end;

  if FIndexFields.Count > 0 then begin
    if IntPtr(OldCurrentItem) = nil then
      MoveDir := -1
    else
      MoveDir := CompareRecords(RecBuf, IntPtr(Integer(OldCurrentItem) + sizeof(TItemHeader)));
    MoveSortedRecord(MoveDir);
  end;
  Inc(FRecordCount);
  ReorderItems(CurrentItem, roInsert);
end;

procedure TMemData.InsertRecord(RecBuf: IntPtr);
var
  CacheItem: TCacheItem;
begin
  if not FCachedUpdates then
    InternalAppend(RecBuf);

  AddRecord(RecBuf);

  if FCachedUpdates then begin
    CacheItem := TCacheItem.Create;
    CacheItem.Item := CurrentItem;
    AddCacheItem(CacheItem);

    CurrentItem.Status := isAppended;
    CurrentItem.UpdateResult := urNone;
  end;
end;

procedure TMemData.AppendRecord(RecBuf: IntPtr);
begin
  SetToEnd;
  InsertRecord(RecBuf);
end;

procedure TMemData.UpdateRecord(RecBuf: IntPtr);
var
  CacheItem: TCacheItem;
  Rollback: PItemHeader;
  i: integer;
  Blob: TBlob;
  RollbackRecBuf: IntPtr;
  ItemRecBuf: IntPtr;
  MoveDir: integer;
begin
  Assert(IntPtr(CurrentItem) <> nil);

  if not FCachedUpdates then
    InternalUpdate(RecBuf)
  else begin
    if CurrentItem.Status = isUnmodified then begin
    // add to cache
      CacheItem := TCacheItem.Create;
      CacheItem.Item := CurrentItem;
      AddCacheItem(CacheItem);
    end;

    if (CurrentItem.Status <> isAppended) or (CurrentItem.UpdateResult = urApplied)
    then begin
      CurrentItem.Status := isUpdated;

      if IntPtr(CurrentItem.Rollback) = nil then begin
      // create rollback record
        BlockMan.AllocItem(Rollback);
        CurrentItem.Rollback := Rollback;
        BlockMan.CopyRecord(CurrentItem, Rollback);
        AddRefComplexFields(IntPtr(Integer(Rollback) + sizeof(TItemHeader)));
      end;
      if HasBlobFields then begin
        RollbackRecBuf := IntPtr(Integer(CurrentItem.Rollback) + sizeof(TItemHeader));
        ItemRecBuf := IntPtr(Integer(CurrentItem) + sizeof(TItemHeader));
        for i := 0 to FieldCount - 1 do
          if IsBlobFieldType(Fields[i].DataType) then begin
            Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(RollbackRecBuf, Fields[i].Offset)));
            if (Blob.Rollback <> nil)
              and (Blob = TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(ItemRecBuf, Fields[i].Offset))))
            then begin
              Marshal.WriteIntPtr(RollbackRecBuf, Fields[i].Offset, Blob.Rollback.GCHandle);
              Blob.Rollback := nil;
              Blob.Release;
            end;
          end;
      end;
    end;
    CurrentItem.UpdateResult := urNone;
  end;

  MoveDir := CompareRecords(RecBuf, IntPtr(Integer(CurrentItem) + sizeof(TItemHeader)));
  PutRecord(RecBuf);
  if FIndexFields.Count > 0 then begin
    MoveSortedRecord(MoveDir);
    ReorderItems(nil, roFull);
  end;
end;

procedure TMemData.RemoveRecord;
var
  PermitDelete: boolean;
begin
  if FCachedUpdates then begin
    PermitDelete := CurrentItem.Status <> isAppended;
    RevertRecord;
  end
  else
    PermitDelete := True;

  if PermitDelete then begin
    if HasComplexFields then
      FreeComplexFields(IntPtr(Integer(CurrentItem) + sizeof(TItemHeader)), True);
    DeleteItem(CurrentItem);
    Dec(FRecordCount); // if PermitDelete = False RecordCount is decreased on RevertRecord
  end;

  ReorderItems(CurrentItem, roDelete);
end;

procedure TMemData.DeleteRecord;
var
  CacheItem: TCacheItem;
  OldCacheItem: TCacheItem;
begin
  if IntPtr(CurrentItem.Next) = nil then
    Fetch;

  if not FCachedUpdates then begin
    InternalDelete;

    RemoveRecord;
  end
  else begin
    if CurrentItem.Status = isUnmodified then begin
    // add to cache
      CacheItem := TCacheItem.Create;
      CacheItem.Item := CurrentItem;
      AddCacheItem(CacheItem);

      CurrentItem.Status := isDeleted;
      CurrentItem.UpdateResult := urNone;
    end
    else
      case CurrentItem.Status of
        isAppended: begin
        // remove record from cache
          CacheItem := Cache;
          OldCacheItem := CacheItem;
          while CacheItem <> nil do begin
            if CacheItem.Item = CurrentItem then begin
              if CacheItem = LastCacheItem then
                if CacheItem = Cache then
                  LastCacheItem := nil
                else
                  LastCacheItem := OldCacheItem;

              if CacheItem = Cache then
                Cache := CacheItem.Next
              else
                OldCacheItem.Next := CacheItem.Next;

              CacheItem.Free;
              break;
            end;

            OldCacheItem := CacheItem;
            CacheItem := CacheItem.Next;
          end;

          if HasComplexFields then
            FreeComplexFields(IntPtr(Integer(CurrentItem) + sizeof(TItemHeader)), True);

          DeleteItem(CurrentItem);
        end;
        isUpdated: begin
        // rollback record
          FreeComplexFields(IntPtr(Integer(CurrentItem) + sizeof(TItemHeader)), True);
          BlockMan.CopyRecord(CurrentItem.Rollback, CurrentItem);
          BlockMan.FreeItem(CurrentItem.Rollback);
          CurrentItem.Rollback := nil;

          CurrentItem.Status := isDeleted;
          CurrentItem.UpdateResult := urNone;
        end;
      end;
      if IntPtr(CurrentItem) <> nil then
        CurrentItem.FilterResult := fsNotChecked;
      Dec(FRecordCount);
      ReorderItems(CurrentItem, roDelete);
  end;
end;

{ Edit }

{ Navigation }

function TMemData.GetBOF: boolean;
begin
  Result := (IntPtr(CurrentItem) = nil) and FBOF;  // WAR
end;

function TMemData.GetEOF: boolean;
begin
  Result := (IntPtr(CurrentItem) = nil) and FEOF;  // WAR
end;

procedure TMemData.SetToBegin;
begin
  CurrentItem := nil; //FirstItem;
  FBOF := True;
  if IntPtr(LastItem) <> nil then
    FEOF := False;
end;

procedure TMemData.SetToEnd;
begin
  CurrentItem := nil; //LastItem;
  FEOF := True;
  if IntPtr(FirstItem) <> nil then
    FBOF := False;
end;

procedure TMemData.PrepareRecNoCache;
var
  i: Integer;
  Item: PItemHeader;
begin
  if Length(FRecordNoCache) > 0 then
    Exit;
  i := 0;
  Item := FirstItem;
  SetLength(FRecordNoCache, RecordCount);
  while IntPtr(Item) <> nil do begin
    if Item.FilterResult = fsNotOmitted then begin
      FRecordNoCache[i] := Item;
      inc(i);
    end;
    Item := Item.Next;
  end;
end;

function TMemData.GetRecordCount: longint;
begin
  Result := FRecordCount;
end;

function TMemData.GetRecordNo: longint;
begin
  if IntPtr(CurrentItem) <> nil then
    Result := CurrentItem.Order + FRecordNoOffset
  else
    Result := 0;
end;

procedure TMemData.SetRecordNo(Value: longint);
var
  Item, CurrItem, LastOrderedItem: PItemHeader;
  ForwardDir: boolean;
begin
  if (IntPtr(FirstItem) <> nil) and (Value > 0) then begin
    if Length(FRecordNoCache) > 0 then begin
      CurrentItem := FRecordNoCache[Value - 1];
      Exit;
    end;

    if IntPtr(CurrentItem) <> nil then
      CurrItem := CurrentItem
    else
      CurrItem := FirstItem;

    LastOrderedItem := LastItem;
    while LastOrderedItem.Order = 0 do begin // if recordset is filtered
      LastOrderedItem := LastOrderedItem.Prev;
      if IntPtr(LastOrderedItem) = nil then
        Exit; // all records are rejected by filter
    end;

    if (Value < Abs(LastOrderedItem.Order + FRecordNoOffset - Value)) and
      (Value < Abs(CurrItem.Order + FRecordNoOffset - Value))
    then begin
    // from first
      Item := FirstItem;
      ForwardDir := True;
    end
    else
      if Abs(LastOrderedItem.Order + FRecordNoOffset - Value) <
        Abs(CurrItem.Order + FRecordNoOffset - Value)
      then begin
      // from
        Item := LastOrderedItem;
        ForwardDir := LastOrderedItem.Order + FRecordNoOffset < Value;
      end
      else begin
      // from current
        Item := CurrItem;
        ForwardDir := CurrItem.Order + FRecordNoOffset < Value;
      end;

    while (IntPtr(Item) <> nil) and (Item.Order + FRecordNoOffset <> Value) do
      if ForwardDir then begin
        if IntPtr(Item.Next) = nil then
          Fetch;
        Item := Item.Next
      end
      else
        Item := Item.Prev;

    if IntPtr(Item) <> nil then
      CurrentItem := Item;
  end;
end;

{ Fetch }

function TMemData.Fetch(FetchBack: boolean = False): boolean;
begin
  Result := False;
end;

procedure TMemData.InitFetchedItems(FetchedItem: IntPtr; NoData, FetchBack: boolean);
var
  Item: IntPtr;
  ItemHeader: PItemHeader {$IFNDEF CLR}absolute Item{$ENDIF};
  NewOrder: Integer;
begin
  Item := FetchedItem;
{$IFDEF CLR}
  ItemHeader := Item;
{$ENDIF}
  if not FetchBack then begin
    NewOrder := 1;
    while (Item = FetchedItem) or ((Item <> nil) and OmitRecord(ItemHeader)) do begin
      ItemHeader := ItemHeader.Prev;
    {$IFDEF CLR}
      Item := ItemHeader;
    {$ENDIF}
    end;
    if Item <> nil then
      NewOrder := ItemHeader.Order + 1;
  end
  else
    NewOrder := ItemHeader.Order;

  Item := FetchedItem;
{$IFDEF CLR}
  ItemHeader := Item;
{$ENDIF}
  while IntPtr(Item) <> nil do begin
    if not OmitRecord(ItemHeader) then begin
      if not (NoData or FetchBack)  then
        Inc(FRecordCount);
      ItemHeader.Order := NewOrder;
      if FetchBack then
        Dec(NewOrder)
      else
        Inc(NewOrder);
    end;
    if FetchBack then
      ItemHeader := ItemHeader.Prev
    else
      ItemHeader := ItemHeader.Next;
  {$IFDEF CLR}
    Item := ItemHeader;
  {$ENDIF}
  end;
end;

{ BookMarks }

procedure TMemData.GetBookmark(Bookmark: PRecBookmark);
begin
  Bookmark.RefreshIteration := FRefreshIteration;
  Bookmark.Item := CurrentItem;
  if IntPtr(CurrentItem) <> nil then
    Bookmark.Order := CurrentItem.Order + FRecordNoOffset
  else
    Bookmark.Order := -1;
end;

procedure TMemData.SetToBookmark(Bookmark: PRecBookmark);
var
  OldCurrentItem: PItemHeader;
begin
  if (Bookmark.RefreshIteration = FRefreshIteration) and
    (IntPtr(Bookmark.Item) <> nil)
  then begin
    OldCurrentItem := CurrentItem;
    try // for freed item
      CurrentItem := Bookmark.Item;
      if CurrentItem.Flag = flUsed then begin
        FBOF := False;
        FEOF := False;
        Exit;
      end
      else
        CurrentItem := OldCurrentItem;
    except
      CurrentItem := OldCurrentItem;
    end;
  end;

// Set by order
  inherited;
end;

function TMemData.BookmarkValid(Bookmark: PRecBookmark): boolean;
begin
  if IntPtr(Bookmark) <> nil then
    Result := (Bookmark.Order <> -1) or (IntPtr(Bookmark.Item) <> nil)
  else
    Result := False;

  if Result and Filtered then 
    Result := not OmitRecord(Bookmark.Item);
end;

function TMemData.CompareBookmarks(Bookmark1, Bookmark2: PRecBookmark): integer;
const
  RetCodes: array[Boolean, Boolean] of ShortInt = ((2,-1),(1,0));
begin
  Result := RetCodes[IntPtr(Bookmark1) = nil, IntPtr(Bookmark2) = nil];
  if Result = 2 then
    if Bookmark1.RefreshIteration = Bookmark2.RefreshIteration then
      if Bookmark1.Item = Bookmark2.Item then begin
        Result := 0;
        Exit;
      end;

// Compare by order
  Result := inherited CompareBookmarks(Bookmark1, Bookmark2);
end;

{ CachedUpdates }

function TMemData.GetUpdateStatus: TItemStatus;
begin
  if IntPtr(CurrentItem) <> nil then
    Result := CurrentItem.Status
  else
    Result := isUnmodified;
end;

function TMemData.GetUpdateResult: TUpdateRecAction;
begin
  if IntPtr(CurrentItem) <> nil then
    Result := CurrentItem.UpdateResult
  else
    Result := urNone;
end;

procedure TMemData.AddCacheItem(CacheItem: TCacheItem);
begin
// add to end cache
  CacheItem.Next := nil;
  if Cache = nil then
    Cache := CacheItem
  else
    LastCacheItem.Next := CacheItem;

  LastCacheItem := CacheItem;
end;

procedure TMemData.SetCacheRecBuf(NewBuf: IntPtr; OldBuf: IntPtr);
begin
  NewCacheRecBuf := NewBuf;
  OldCacheRecBuf := OldBuf;
end;

procedure TMemData.ApplyUpdates;
var
  CacheItem, NextCacheItem, PrevCacheItem: TCacheItem;
  Action: TUpdateRecAction;
  OldCurrentItem: PItemHeader;

  PacketCacheItem: TCacheItem;
  PrevPacketCacheItem: TCacheItem;

  function ValidateCacheItem: boolean;
    { On case of deleting current item from cache via RevertRecord in
      ApplyRecord call. Returns True if CacheItem was deleted. }
  begin
    Result := True;
    if PrevCacheItem <> nil then begin
      if PrevCacheItem.Next = NextCacheItem then begin
        CacheItem := NextCacheItem;
        Result := True;
      end;
    end
    else
      if NextCacheItem <> nil then begin
        if CacheItem.Next <> NextCacheItem then begin
          CacheItem := NextCacheItem;
          Result := False;
        end;
      end
      else
        if Cache = nil then begin
          CacheItem := nil;
          Result := False;
        end;
  end;

  procedure SetAction(Action: TUpdateRecAction);
  var
    Temp: TCacheItem;
  begin
    // Set action for batch of items
    if Action <> urSuspended then begin
      Temp := PacketCacheItem;
      while (Temp <> nil) and (Temp <> CacheItem) do begin
        Temp.Item.UpdateResult := Action;
        Temp := Temp.Next;
      end;
    end;  
    CacheItem.Item.UpdateResult := Action;
  end;

begin
  if FCachedUpdates then begin
    OldCurrentItem := CurrentItem;
    try
      PrevCacheItem := nil;
      PacketCacheItem := nil;
      PrevPacketCacheItem := nil;
      CacheItem := Cache;
      while CacheItem <> nil do
        if CacheItem.Item.UpdateResult <> urApplied then begin
          NextCacheItem := CacheItem.Next;
          try
            CurrentItem := CacheItem.Item; // for refresh on applied
            Action := urFail;
            try
              case CacheItem.Item.Status of
                isAppended: begin
                  BlockMan.GetRecord(CacheItem.Item, NewCacheRecBuf);
                  BlockMan.GetRecord(CacheItem.Item, OldCacheRecBuf);
                  ApplyRecord(ukInsert, Action, CacheItem.Next = nil);
                  BlockMan.PutRecord(CacheItem.Item, NewCacheRecBuf); // for ReturnParams
                end;
                isUpdated: begin
                  BlockMan.GetRecord(CacheItem.Item, NewCacheRecBuf);
                  BlockMan.GetRecord(CacheItem.Item.Rollback, OldCacheRecBuf);
                  ApplyRecord(ukUpdate, Action, CacheItem.Next = nil);
                  BlockMan.PutRecord(CacheItem.Item, NewCacheRecBuf); // for ReturnParams
                end;
                isDeleted: begin
                  BlockMan.GetRecord(CacheItem.Item, NewCacheRecBuf);
                  BlockMan.GetRecord(CacheItem.Item, OldCacheRecBuf);
                  ApplyRecord(ukDelete, Action, CacheItem.Next = nil);
                end;
              else
                Assert(False);
              end;
            finally
              if Active and ValidateCacheItem then begin
                SetAction(Action);
                case Action of
                  urSuspended:
                    if PacketCacheItem = nil then begin
                      PacketCacheItem := CacheItem;
                      PrevPacketCacheItem := PrevCacheItem;
                    end;
                  urRetry:
                    if PacketCacheItem <> nil then begin
                      CacheItem := PacketCacheItem;
                      PrevCacheItem := PrevPacketCacheItem;
                    end;
                else
                  PacketCacheItem := nil;
                  PrevPacketCacheItem := nil;
                end;
                if Action <> urRetry then begin
                  PrevCacheItem := CacheItem;
                  CacheItem := NextCacheItem;
                end;
              end;
            end;
          except
            if CacheItem <> nil then
              OldCurrentItem := CacheItem.Item; // failed item is current
            raise;
          end;
        end
        else
          CacheItem := CacheItem.Next;
    finally
      CurrentItem := OldCurrentItem;
    end;
  end;
end;

procedure TMemData.CommitUpdates;
var
  CacheItem,CacheItem1: TCacheItem;
begin
  if UpdatesPending then
    ApplyUpdates;

  CacheItem := Cache;
  LastCacheItem := nil;
  while CacheItem <> nil do
    if CacheItem.Item.UpdateResult = urApplied then begin
      if IntPtr(CacheItem.Item.Rollback) <> nil then begin
        FreeComplexFields(IntPtr(Integer(CacheItem.Item.Rollback) + sizeof(TItemHeader)), True);
        BlockMan.FreeItem(CacheItem.Item.Rollback);
        CacheItem.Item.Rollback := nil;
      end;

      if CacheItem.Item.Status = isDeleted then begin
        if HasComplexFields then
          FreeComplexFields(IntPtr(Integer(CacheItem.Item) + sizeof(TItemHeader)), True);

        DeleteItem(CacheItem.Item)
      end
      else begin
        CacheItem.Item.Status := isUnmodified;
        CacheItem.Item.UpdateResult := urNone;
      end;

      CacheItem1 := CacheItem;
      CacheItem := CacheItem.Next;
      if CacheItem1 = Cache then
        Cache := CacheItem;
      if (CacheItem = nil) and (Cache <> nil) and (Cache.Next = CacheItem1) then
        Cache.Next := nil;
      CacheItem1.Free;
    end
    else begin
      CacheItem1 := CacheItem;
      CacheItem := CacheItem.Next;
      if CacheItem <> nil then
        LastCacheItem := CacheItem
      else
        if (LastCacheItem = nil) and (CacheItem1 = Cache) then // only one unapllied reacord
          LastCacheItem := Cache;
    end;
end;

procedure TMemData.RevertItem(Item: PItemHeader);
begin
  case Item.Status of
    isAppended: begin
      if HasComplexFields then
        FreeComplexFields(IntPtr(Integer(Item) + sizeof(TItemHeader)), True);

      DeleteItem(Item);
      Dec(FRecordCount);
    end;
    isUpdated: begin
      FreeComplexFields(IntPtr(Integer(Item) + sizeof(TItemHeader)), True);
      BlockMan.CopyRecord(Item.Rollback, Item);
      BlockMan.FreeItem(Item.Rollback);
      Item.Rollback := nil;
      Item.Status := isUnmodified;
      Item.UpdateResult := urNone;
      Item.FilterResult := fsNotChecked;
    end;
    isDeleted: begin
      Item.Status := isUnmodified;
      Item.UpdateResult := urNone;
      Item.FilterResult := fsNotChecked;      
      Inc(FRecordCount);
    end;
    isUnmodified: begin
      Item.UpdateResult := urNone;
      Item.FilterResult := fsNotChecked;
    end;        
  end;
end;

procedure TMemData.RevertRecord;
var
  CacheItem: TCacheItem;
  OldCacheItem: TCacheItem;
begin
  if Cache <> nil then begin
    CacheItem := Cache;
    OldCacheItem := CacheItem;
    while (CacheItem <> nil) and not (CacheItem.Item = CurrentItem) do begin
      OldCacheItem := CacheItem;
      CacheItem := CacheItem.Next;
    end;
    if CacheItem <> nil then begin
      if OldCacheItem <> CacheItem then
        OldCacheItem.Next := CacheItem.Next
      else
        Cache := CacheItem.Next;

      RevertItem(CacheItem.Item);
      if CacheItem = LastCacheItem then
        LastCacheItem := OldCacheItem;
      CacheItem.Free;
    end;
  end;
end;

procedure TMemData.CancelUpdates;
var
  CacheItem: TCacheItem;
begin
  if Cache <> nil then begin
    while Cache <> nil do begin
      RevertItem(Cache.Item);
      CacheItem := Cache;
      Cache := Cache.Next;
      CacheItem.Free;
    end;

    LastCacheItem := nil;

    ReorderItems(nil, roFull);
  end;
end;

procedure TMemData.RestoreUpdates;
var
  CacheItem: TCacheItem;
begin
  if FCachedUpdates then begin
    CacheItem := Cache;
    while CacheItem <> nil do begin
      //CacheItem.Item.Status
      CacheItem.Item.UpdateResult := urNone;
      CacheItem := CacheItem.Next;
    end;
  end;
end;

function TMemData.GetUpdatesPending: boolean;
var
  CacheItem: TCacheItem;
begin
  Result := False;
  CacheItem := Cache;
  while (CacheItem <> nil) and not Result do begin
    Result := CacheItem.Item.UpdateResult <> urApplied;
    CacheItem := CacheItem.Next;
  end;
end;

procedure TMemData.GetOldRecord(RecBuf: IntPtr);
begin
  if not(EOF or BOF or (IntPtr(CurrentItem) = nil)) then begin
    if OmitRecord(CurrentItem) then
      GetNextRecord(RecBuf);
    if IntPtr(CurrentItem) <> nil then
      if IntPtr(CurrentItem.Rollback) <> nil then
        BlockMan.GetRecord(CurrentItem.Rollback, RecBuf)
      else
        BlockMan.GetRecord(CurrentItem, RecBuf);
  end;
end;

{ Filter }

procedure TMemData.FilterUpdated;
begin
  ClearItemsOmittedStatus;
  ReorderItems(nil, roFull);
  FEOF := RecordCount = 0; // for correct navigation
end;

procedure TMemData.ClearItemsOmittedStatus;
var
  Item: PItemHeader;
begin
  Item := FirstItem;
  while IntPtr(Item) <> nil do begin
    Item.FilterResult := fsNotChecked;
    Item := Item.Next;
  end;
end;

procedure TMemData.SetFilterItemTypes(Value: TItemTypes);
begin
  if Value <> FilterItemTypes then begin
    inherited;

    ClearItemsOmittedStatus;
    ReorderItems(nil, roFull);
    FEOF := RecordCount = 0; // for correct navigation
  end;
end;

{ TBlockManager }

{$IFDEF CLR}
[DllImport(kernel32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'HeapAlloc')]
function HeapAlloc(hHeap: THandle; dwFlags, dwBytes: DWORD): IntPtr; external;

[DllImport(kernel32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'HeapFree')]
function HeapFree(hHeap: THandle; dwFlags: DWORD; lpMem: IntPtr): BOOL; external;

[DllImport(kernel32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'HeapCreate')]
function HeapCreate(flOptions, dwInitialSize, dwMaximumSize: DWORD): THandle; external;

[DllImport(kernel32, CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'HeapDestroy')]
function HeapDestroy(hHeap: THandle): BOOL; external;

const
  HEAP_NO_SERIALIZE = 1;
  HEAP_GENERATE_EXCEPTIONS = 4;
{$ENDIF}


constructor TBlockManager.Create;
begin
  inherited;

  DefaultItemCount := 10;  // WAR
end;

destructor TBlockManager.Destroy;
begin
  FreeAllBlock;

  inherited;
end;

procedure TBlockManager.AllocBlock(var Block: PBlockHeader; ItemCount: word);
var
  BlockSize: integer;
begin
  BlockSize := sizeof(TBlockHeader) + ItemCount*(sizeof(TItemHeader) + RecordSize);

{$IFDEF CLR}
  if IntPtr(FHeap) = nil then
    FHeap := HeapCreate(HEAP_GENERATE_EXCEPTIONS + HEAP_NO_SERIALIZE, BlockSize + 100 {overhead}, 0);
  Block := HeapAlloc(FHeap, HEAP_GENERATE_EXCEPTIONS + HEAP_NO_SERIALIZE, BlockSize);
{$ELSE}
  GetMem(Block, BlockSize);
{$ENDIF}

  Block.ItemCount := ItemCount;
  Block.UsedItems := ItemCount;

  Block.Next := FirstBlock;
  Block.Prev := nil;

  //Block.Test := btSign;         // DEBUG

  if IntPtr(FirstBlock) <> nil then
    FirstBlock.Prev := Block;
  FirstBlock := Block;
end;

procedure TBlockManager.FreeBlock(Block: PBlockHeader);
begin
  if Block = FirstBlock then begin
    FirstBlock := Block.Next;
    if IntPtr(FirstBlock) <> nil then
      FirstBlock.Prev := nil;
  end
  else begin
    Block.Prev.Next := Block.Next;
    if IntPtr(Block.Next) <> nil then
      Block.Next.Prev := Block.Prev;
  end;

{$IFDEF CLR}
  HeapFree(FHeap, HEAP_NO_SERIALIZE, Block);
{$ELSE}
  FreeMem(Block, BlockSize);
{$ENDIF}
end;

procedure TBlockManager.FreeAllBlock;
begin
{$IFDEF CLR}
  HeapDestroy(FHeap);
  FHeap := 0;
  FirstBlock := nil;
{$ELSE}
  while IntPtr(FirstBlock) <> nil do
    FreeBlock(FirstBlock);
{$ENDIF}

  FirstFree := nil;
end;

procedure TBlockManager.AddFreeBlock;
var
  Block: PBlockHeader;
  Item: PItemHeader;
  i: word;
begin
  AllocBlock(Block, DefaultItemCount);

  Item := IntPtr(Integer(Block) + sizeof(TBlockHeader));
  for i := 1 to DefaultItemCount do begin
    Item.Prev := nil;
    Item.Next := FirstFree;
    Item.Block := Block;
    Item.Flag := flFree;

    if IntPtr(FirstFree) <> nil then
      FirstFree.Prev := Item;
    FirstFree := Item;

    Item := IntPtr(Integer(Item) + sizeof(TItemHeader) + RecordSize);
  end;
  Block.UsedItems := 0;
end;

procedure TBlockManager.AllocItem(var Item: PItemHeader);
begin
  if IntPtr(FirstFree) = nil then
    AddFreeBlock;

  Item := FirstFree;

  Assert(Item.Flag = flFree);
  Item.Flag := flUsed;

  FirstFree := FirstFree.Next;
  if IntPtr(FirstFree) <> nil then
    FirstFree.Prev := nil;

  Item.Rollback := nil;
  Item.Status := isUnmodified;
  Item.UpdateResult := urNone;
  Item.Order := 0;
  Item.FilterResult := fsNotChecked;

  Item.Block.UsedItems := Item.Block.UsedItems + 1;
end;

procedure TBlockManager.FreeItem(Item: PItemHeader);
var
  Free: PItemHeader;
  i: integer;
begin
  Assert(Item.Flag = flUsed);

  Item.Flag := flFree;

  if Item.Block.UsedItems =  1 then begin
  // Procesing Free List
    Free := IntPtr(Integer(Item.Block) + sizeof(TBlockHeader));
    for i := 1 to Item.Block.ItemCount do begin
      if not(Free = Item) then begin
        Assert(Free.Flag = flFree);

        if Free = FirstFree then begin
          FirstFree := Free.Next;
          if IntPtr(FirstFree) <> nil then
            FirstFree.Prev := nil;
        end
        else begin
          Free.Prev.Next := Free.Next;
          if IntPtr(Free.Next) <> nil then
            Free.Next.Prev := Free.Prev;
        end;
      end;
      Free := IntPtr(Integer(Free) + sizeof(TItemHeader) + RecordSize);
    end;
    FreeBlock(Item.Block);
  end
  else begin
    Item.Prev := nil;
    Item.Next := FirstFree;
    if IntPtr(FirstFree) <> nil then
      FirstFree.Prev := Item;
    FirstFree := Item;
    Item.Block.UsedItems := Item.Block.UsedItems - 1;
  end;
end;

procedure TBlockManager.InitItem(Item: PItemHeader);
begin
  Item.Rollback := nil;
  Item.Status := isUnmodified;
  Item.UpdateResult := urNone;
end;

procedure TBlockManager.PutRecord(Item: PItemHeader; Rec: IntPtr);
begin
  CopyBuffer(Rec, IntPtr(Integer(Item) + sizeof(TItemHeader)), RecordSize)
end;

procedure TBlockManager.GetRecord(Item: PItemHeader; Rec: IntPtr);
begin
  CopyBuffer(IntPtr(Integer(Item) + sizeof(TItemHeader)), Rec, RecordSize)
end;

function TBlockManager.GetRecordPtr(Item: PItemHeader): IntPtr;
begin
  Result := IntPtr(Integer(Item) + sizeof(TItemHeader));
end;

procedure TBlockManager.CopyRecord(ItemSrc: PItemHeader; ItemDest: PItemHeader);
begin
  CopyBuffer(IntPtr(Integer(ItemSrc) + sizeof(TItemHeader)),
    IntPtr(Integer(ItemDest) + sizeof(TItemHeader)), RecordSize);
end;

{ TStringHeap }

{$IFDEF CLR}
function PBlock.GetNext: PBlock;
begin
  Result := Marshal.ReadIntPtr(Ptr, 0);
end;

procedure PBlock.SetNext(Value: PBlock);
begin
  Marshal.WriteIntPtr(Ptr, 0, Value);
end;

class operator PBlock.Implicit(AValue: IntPtr): PBlock;
begin
  Result.Ptr := AValue;
end;

class operator PBlock.Implicit(AValue: PBlock): IntPtr;
begin
  Result := AValue.Ptr;
end;

{$ENDIF}

constructor TStringHeap.Create;
begin
  inherited;

  FRoot := nil;
  FEmpty := True;
  FSysGetMem := False;

{$IFDEF WIN32}
  FUseSysMemSize := not IsMemoryManagerSet;
{$ENDIF}
  FThreadSafety := False;
  FThreadSafetyCS := nil;
end;

destructor TStringHeap.Destroy;
begin
  Clear;
  FThreadSafetyCS.Free;

  inherited;
end;

procedure TStringHeap.SetThreadSafety(const Value: boolean);
begin
  if Value <> FThreadSafety then begin
    FThreadSafety := Value;
    if Value then begin
      Assert(FThreadSafetyCS = nil);
      FThreadSafetyCS := TCriticalSection.Create;
    end
    else begin
      FThreadSafetyCS.Free;
      FThreadSafetyCS := nil;
    end;
  end;
end;

procedure TStringHeap.Clear;
var
  P, Temp: PBlock;
  i: integer;
begin
  if Empty then
    Exit;
  if FThreadSafetyCS <> nil then
    FThreadSafetyCS.Acquire;
  try
    P := FRoot;
    while IntPtr(P) <> nil do begin
      Temp := P;
      P := P.Next;
      Marshal.FreeHGlobal(Temp);
    end;
    FRoot := nil;
    FFree := SizeOf_TStrData;
    for i := Low(FSmallTab) to High(FSmallTab) do
      FSmallTab[i] := nil;
    FEmpty := True;
    FSysGetMem := False;
  finally
    if FThreadSafetyCS <> nil then
      FThreadSafetyCS.Release;
  end;
end;

function TStringHeap.AllocStr(Str: IntPtr; Trim: boolean = false; Len: integer = -1): IntPtr;
var
  EndPtr: IntPtr;
begin
  if Str = nil then
    Result := nil
  else begin
    if Len = -1 then
      Len := StrLen(Str);
    if Trim then begin
      EndPtr := IntPtr(Integer(Str) + Len - 1);
      while (Len > 0) and
      {$IFDEF CLR}
        (Marshal.ReadByte(EndPtr) = Byte(' '))
      {$ELSE}
        (PByte(EndPtr)^ = Byte(' ')) 
      {$ENDIF}
      do begin
        EndPtr := IntPtr(Integer(EndPtr) - 1 {sizeof(AsciiChar)});
        Dec(Len);
      end;
    end;
    Result := NewBuf(Len + 1);
    CopyBuffer(Str, Result, Len);
    Marshal.WriteByte(Result, Len, byte(#0));
  end;
end;

function TStringHeap.AllocWideStr(Str: IntPtr; Trim: boolean = false; Len: integer = -1): IntPtr;
var
  EndPtr: IntPtr;
begin
  if Str = nil then
    Result := nil
  else begin
    if Len = -1 then
      Len := StrLenW(Str);
    if Trim then begin
      EndPtr := IntPtr(Integer(Str) + (Len - 1) * sizeof(WideChar));
      while (Len > 0) and
      {$IFDEF CLR}
        (Marshal.ReadInt16(EndPtr) = SmallInt(' '))
      {$ELSE}
        (PSmallInt(EndPtr)^ = SmallInt(' '))
      {$ENDIF}
      do begin
        EndPtr := IntPtr(Integer(EndPtr) - sizeof(WideChar));
        Dec(Len);
      end;
    end;
    Result := NewBuf((Len + 1) * sizeof(WideChar));
    CopyBuffer(Str, Result, Len * sizeof(WideChar));
    Marshal.WriteInt16(Result, Len * sizeof(WideChar), byte(#0));
  end;
end;

function TStringHeap.ReAllocStr(Str: IntPtr; Trim: boolean = false): IntPtr;
begin
  Result := AllocStr(Str, Trim);
  DisposeBuf(Str);
end;

function TStringHeap.ReAllocWideStr(Str: IntPtr; Trim: boolean = false): IntPtr;
begin
  Result := AllocStr(Str, Trim);
  DisposeBuf(Str);
end;

function TStringHeap.UseSmallTabs(divSize: integer): boolean;
begin
  Result := divSize <= SmallSize div Align;
  // This fix was added 04.04.2006 and rolled back 07.03.2007 because of bug
  // with allocation using memory manager and disposing using StringHeap block of memroy with size 2002 bytes
  // if (not Result) and ((Size - 1) div Align <= SmallSize div Align) then
  //   Result := True;
end;

function TStringHeap.NewBuf(Size: integer): IntPtr;
var
  P: IntPtr;
  Temp: PBlock;
  Idx: integer;
  divSize: integer;
begin
  if Size <= 0 then begin
    Result := nil;
  end
  else begin
    if FThreadSafetyCS <> nil then
      FThreadSafetyCS.Acquire;
    try
      FEmpty := False;
      divSize := (Size + Align - 1) div Align;
      if UseSmallTabs(divSize) then begin
        Result := FSmallTab[divSize];
        if Result <> nil then begin
          FSmallTab[divSize] := Marshal.ReadIntPtr(Result);
          p := IntPtr(Integer(Result) - SizeOf(Word));
          Marshal.WriteInt16(p, Marshal.ReadInt16(p) + 1);
          Exit;
        end;
        Size := divSize * Align;
        if IntPtr(FRoot) = nil then begin
          FRoot := Marshal.AllocHGlobal(SizeOf_TBlock);
          FRoot.Next := nil;
          FFree := SizeOf_TStrData;
        end
        else
        if FFree < Size + SizeOf(Integer) + SizeOf(Word) then begin
          P := IntPtr(Integer(IntPtr(FRoot)) + SizeOf(PBlock) + SizeOf_TStrData - FFree);
          divSize := (FFree - SizeOf(Integer) - SizeOf(Word)) div Align;
          Marshal.WriteInt32(P, divSize * Align);
          P := IntPtr(Integer(P) + SizeOf(Integer));
          Marshal.WriteInt16(P, RefNull);
          P := IntPtr(Integer(P) + SizeOf(Word));
          Idx := divSize;
          Marshal.WriteIntPtr(P, FSmallTab[Idx]);
          FSmallTab[Idx] := P;
          Temp := FRoot;
          FRoot := Marshal.AllocHGlobal(SizeOf_TBlock);
          FRoot.Next := Temp;
          FFree := SizeOf_TStrData;
        end;
        Result := IntPtr(Integer(IntPtr(FRoot)) + SizeOf(PBlock) + SizeOf_TStrData - FFree);
        Marshal.WriteInt32(Result, Size);
        Dec(FFree, Size + SizeOf(Integer) + SizeOf(Word));
        if FFree < SizeOf(Integer) + SizeOf(Word) + Align then begin
          Marshal.WriteInt32(Result, Marshal.ReadInt32(Result) + FFree and not (Align - 1));
          Temp := FRoot;
          FRoot := Marshal.AllocHGlobal(SizeOf_TBlock);
          FRoot.Next := Temp;
          FFree := SizeOf_TStrData;
        end;
        Result := IntPtr(Integer(Result) + SizeOf(Integer));
      end
      else begin
      {$IFDEF WIN32}
        if FUseSysMemSize then
          Result := Marshal.AllocHGlobal(Size + SizeOf(Word))
        else begin
      {$ENDIF}
          Result := Marshal.AllocHGlobal(Size + SizeOf(Word) + SizeOf(Integer));
          Marshal.WriteInt32(Result, Size);
          Result := IntPtr(Integer(Result) + SizeOf(Integer));
      {$IFDEF WIN32}
        end;
      {$ENDIF}
        FSysGetMem := True;
      end;
      Marshal.WriteInt16(Result, RefNull);
      Result := IntPtr(Integer(Result) + SizeOf(Word));
    finally
      if FThreadSafetyCS <> nil then
        FThreadSafetyCS.Release;
    end;
  end;
end;

procedure TStringHeap.DisposeBuf(Buf: IntPtr);
var
  Size: integer;
  PRefCount: IntPtr;
  RefCount: Word;
  Idx: integer;
  divSize: integer;
begin
  if (Buf <> nil) then begin
    if FThreadSafetyCS <> nil then
      FThreadSafetyCS.Acquire;
    try
      PRefCount := IntPtr(Integer(Buf) - SizeOf(Word));
      RefCount := Marshal.ReadInt16(PRefCount);
      Assert(RefCount >= RefNull, 'DisposeBuf failed');
      if RefCount = RefNull then begin
        Marshal.WriteInt16(PRefCount, RefCount - 1);
        Size := Marshal.ReadInt32(IntPtr(Integer(PRefCount) - SizeOf(Integer)));
        divSize := (Size + Align - 1) div Align;
        Assert(divSize <> 0, 'SmallTab in DisposeBuf failed');
        if UseSmallTabs(divSize) then begin
          Idx := divSize;
          Marshal.WriteIntPtr(Buf, FSmallTab[Idx]);
          FSmallTab[Idx] := Buf;
        end
        else
        {$IFDEF WIN32}
          if FUseSysMemSize then
            Marshal.FreeHGlobal(PRefCount)
          else
        {$ENDIF}
            Marshal.FreeHGlobal(IntPtr(Integer(PRefCount) - SizeOf(Integer)));
      end
      else
        Marshal.WriteInt16(PRefCount, RefCount - 1);
    finally
      if FThreadSafetyCS <> nil then
        FThreadSafetyCS.Release;
    end;
  end;
end;

procedure TStringHeap.AddRef(Buf: IntPtr);
var
  PRefCount: IntPtr;
  RefCount: Word;
begin
  if (Buf <> nil) then begin
    PRefCount := IntPtr(Integer(Buf) - SizeOf(Word));
    RefCount := Marshal.ReadInt16(PRefCount);
    Assert(RefCount >= RefNull, 'AddRefStr failed');
    Marshal.WriteInt16(PRefCount, RefCount + 1);
  end;
end;

{ TSharedObject }

constructor TSharedObject.Create;
begin
  inherited;

  AddRef;

{$IFDEF CRDEBUG} Inc(ShareObjectCnt); {$ENDIF}
end;

destructor TSharedObject.Destroy;
begin
  {$IFDEF CRDEBUG} Dec(ShareObjectCnt); {$ENDIF}
  FRefCount := 0;

  inherited;
end;

procedure TSharedObject.CheckValid;
begin
  if FRefCount = 0 then
    raise Exception.Create(SInvalidSharedObject);
end;

procedure TSharedObject.Free;
begin
  if Assigned(Self) then begin
    Assert(FRefCount > 0, ClassName + '.Free RefCount = ' + IntToStr(FRefCount));

    if FRefCount = 1 then begin
      if FGCHandle <> nil then
        FreeGCHandle(FGCHandle);
      inherited Free;
    end
    else
      Dec(FRefCount);
  end;
end;

procedure TSharedObject.AddRef;
begin
  Inc(FRefCount);
end;

procedure TSharedObject.Release;
begin
  Free;
end;

{$IFNDEF CLR}
function TSharedObject.GetHashCode: integer;
begin
  Result := Integer(Self);
end;
{$ENDIF}

function TSharedObject.GetGCHandle: IntPtr;
begin
  if FGCHandle = nil then
    FGCHandle := AllocGCHandle(Self);
  Result := FGCHandle;
end;

{ TPiece }

function NextPiece(Piece: PPieceHeader): PPieceHeader;
begin
  if IntPtr(Piece) <> nil then
    Result := Piece.Next
  else
    Result := nil;
end;

function PieceData(Piece: PPieceHeader): IntPtr;
begin
  if IntPtr(Piece) <> nil then
    Result := IntPtr(Integer(Piece) + sizeof(TPieceHeader))
  else
    Result := nil;
end;

function PieceUsedPtr(Piece: PPieceHeader): IntPtr;
begin
  if IntPtr(Piece) <> nil then
    Result := IntPtr(Integer(Piece) + sizeof(integer) * 2)
  else
    Result := nil;
end;

{ TBlob }

constructor TBlob.Create(IsUnicode: boolean = False);
begin
  inherited Create;

  FIsUnicode := IsUnicode;
  PieceSize := DefaultPieceSize;
  Test := btSign;                    // DEBUG
end;

destructor TBlob.Destroy;
begin
  CheckValid;   // DEBUG
  Test := 0;    // DEBUG
  
  FNeedRollback := False;
  Clear;

  if Rollback <> nil then
    Rollback.Free;

  inherited;
end;

procedure TBlob.CheckValid;
begin
  if Test <> btSign then                    // DEBUG
    raise Exception.Create(SInvalidBlob);
end;

procedure TBlob.Clear;
begin
  if FNeedRollback and (Rollback = nil) then
    SaveToRollback;

  InternalClear;

  FModified := True;
end;

{ Pieces }

procedure TBlob.AllocPiece(var Piece: PPieceHeader; Size: cardinal);
begin
  Assert(Size > 0);
  Piece := Marshal.AllocHGlobal(Integer(sizeof(TPieceHeader)) + Integer(Size));
  Piece.Blob := 0;
  Piece.Size := Size;
  Piece.Used := 0;
  Piece.Prev := nil;
  Piece.Next := nil;
end;

procedure TBlob.ReallocPiece(var Piece: PPieceHeader; Size: cardinal);
var
  MemSize: integer;
begin
  if Size = 0 then begin
    FreePiece(Piece);
    Piece := nil;
  end
  else
    if Size <> Piece.Size then begin
      MemSize := Integer(sizeof(TPieceHeader)) + Integer(Size);
      Piece := Marshal.ReAllocHGlobal(Piece, IntPtr(MemSize));
      Piece.Size := Size;
      if Piece.Used > Size then
        Piece.Used := Size;
      if Piece.Blob <> 0 then begin
        if IntPtr(Piece.Prev) <> nil then
          Piece.Prev.Next := Piece
        else
          FFirstPiece := Piece;

        if IntPtr(Piece.Next) <> nil then
          Piece.Next.Prev := Piece;
      end;
    end;
end;

procedure TBlob.FreePiece(Piece: PPieceHeader);
begin
  if Piece.Blob <> 0 then
    DeletePiece(Piece);

  Marshal.FreeHGlobal(Piece);
end;

procedure TBlob.AppendPiece(Piece: PPieceHeader);
var
  Last: PPieceHeader;
begin
  Piece.Blob := Self.GetHashCode;
  Piece.Next := nil;
  if IntPtr(FFirstPiece) = nil then begin
    Piece.Prev := nil;
    FFirstPiece := Piece;
  end
  else begin
    Last := FFirstPiece;
    while IntPtr(Last.Next) <> nil do
      Last := Last.Next;
    Last.Next := Piece;
    Piece.Prev := Last;
  end;
end;

procedure TBlob.DeletePiece(Piece: PPieceHeader);
begin
  Assert(Piece.Blob = Self.GetHashCode);

  if FFirstPiece = Piece then begin
    FFirstPiece := Piece.Next;
    if IntPtr(FFirstPiece) <> nil then
      FFirstPiece.Prev := nil;
  end
  else
  begin
    Piece.Prev.Next := Piece.Next;
    if IntPtr(Piece.Next) <> nil then
      Piece.Next.Prev := Piece.Prev;
  end;

  Piece.Blob := 0;
end;

procedure TBlob.CompressPiece(var Piece: PPieceHeader);
begin
  if Piece.Used < Piece.Size then
    ReallocPiece(Piece, Piece.Used);
end;

function TBlob.FirstPiece: PPieceHeader;
begin
  Result := FFirstPiece;
end;

procedure TBlob.CheckValue;
begin
end;

function TBlob.Read(Position: cardinal; Count: cardinal; Dest: IntPtr): cardinal;
var
  Piece: PPieceHeader;
  Pos, { shift from Blob begin }
  Shift, { for read, in Piece }
  ReadCount, { all }
  MoveSize: cardinal; { in Piece }
begin
  CheckValid;   // DEBUG

  CheckValue;

  Result := 0;

  if (IntPtr(FFirstPiece) = nil) or (Position > Size) then
    Exit;

  if Count = 0 then
    Count := Size;

  if Position + Count > Size then
    Count := Size - Position;

  Piece := FFirstPiece;
  ReadCount := 0;
  Pos := 0;
  while (IntPtr(Piece) <> nil) and (Pos < (Position + Count)) do begin
    if Pos + Piece.Used > Position then begin
      if Position > Pos then
        Shift := Position - Pos
      else
        Shift := 0;

      if (Pos + Piece.Used) > (Position + Count) then
        MoveSize := (Position + Count) - (Pos + Shift)
      else
        MoveSize := Piece.Used - Shift;

      CopyBuffer(IntPtr(Integer(Piece) + sizeof(TPieceHeader) + Integer(Shift)),
        IntPtr(Integer(Dest) + Integer(ReadCount)), MoveSize);
      Inc(ReadCount, MoveSize);
    end;
    Inc(Pos, Piece.Used);
    Piece := Piece.Next;
  end;
  Result := ReadCount;
end;

{ similar to Read }

procedure TBlob.Write(Position: cardinal; Count: cardinal; Source: IntPtr);
var
  Piece: PPieceHeader;
  Pos, { shift from Blob begin }
  Shift, { for write, in Piece }
  WriteCount, { all }
  MoveSize: cardinal; { in Piece }
begin
  CheckValid;   // DEBUG

  if FNeedRollback and (Rollback = nil) then
    SaveToRollback;

  if (Position > Size) then
    Position := Size;

  Piece := FFirstPiece;
  WriteCount := 0;
  Pos := 0;
  while (Pos < (Position + Count)) do begin
    if IntPtr(Piece) = nil then begin
      if Count > PieceSize then
        AllocPiece(Piece, PieceSize)
      else
        AllocPiece(Piece, Count);
      AppendPiece(Piece);
    end;

    if Pos + Piece.Size > Position then begin
      if Position > Pos then
        Shift := Position - Pos
      else
        Shift := 0;

      if (Pos + Piece.Size) > (Position + Count) then
        MoveSize := (Position + Count) - (Pos + Shift)
      else
        MoveSize := Piece.Size - Shift;

      CopyBuffer(IntPtr(Integer(Source) + Integer(WriteCount)),
        IntPtr(Integer(Piece) + sizeof(TPieceHeader) + Integer(Shift)), MoveSize);
      Inc(WriteCount, MoveSize);

      Assert(Shift <= Piece.Used);
      if (Shift + MoveSize) > Piece.Used then
        Piece.Used := Shift + MoveSize;
    end;
    Inc(Pos, Piece.Used);
    Piece := Piece.Next;
  end;

  FModified := True;
end;

procedure TBlob.Truncate(NewSize: cardinal);
var
  Piece: PPieceHeader;
  Size: cardinal;
begin
  if FNeedRollback and (Rollback = nil) then
    SaveToRollback;

  if NewSize = 0 then
    Clear
  else begin
    Size := 0;
    Piece := FirstPiece;
    while IntPtr(Piece) <> nil do begin
      if Size + Piece.Used > NewSize then
        Piece.Used := NewSize - Size;
      Inc(Size, Piece.Used);
      Piece := Piece.Next;
    end;
  end;

  FModified := True;
end;

procedure TBlob.Compress;
var
  Piece: PPieceHeader;
  NextPiece: PPieceHeader;
begin
  Piece := FirstPiece;
  while IntPtr(Piece) <> nil do begin
    NextPiece := Piece.Next;
    CompressPiece(Piece);
    Piece := NextPiece;
  end;
end;

procedure TBlob.Defrag; // Move all data to first piece
var
  pc: IntPtr;
  Piece: PPieceHeader;
  NextPiece: PPieceHeader;
begin
  if IntPtr(FirstPiece) = nil then
    Exit; // Is empty

  ReallocPiece(FFirstPiece, GetDataSize);
  pc := IntPtr(Integer(FFirstPiece) + sizeof(TPieceHeader) + Integer(FFirstPiece.Used));

  Piece := FFirstPiece.Next;
  while IntPtr(Piece) <> nil do begin
    CopyBuffer(IntPtr(Integer(Piece) + sizeof(TPieceHeader)), pc, Piece.Used);
    pc := IntPtr(Integer(pc) + Integer(Piece.Used));
    FFirstPiece.Used := FFirstPiece.Used + Piece.Used;

    NextPiece := Piece.Next;
    FreePiece(Piece);
    Piece := NextPiece;
  end;
end;

{ Stream/File }

procedure TBlob.LoadFromStream(Stream: TStream);
var
  Piece: PPieceHeader;
  Remainder: cardinal;
  BufLen: cardinal;
{$IFDEF CLR}
  Buffer: TBytes;
{$ENDIF}
begin
  Clear;

  Stream.Seek(0, soFromBeginning);

  Remainder := Stream.Size;
  while Remainder > 0 do begin
    if Remainder > PieceSize then
      BufLen := PieceSize
    else
      BufLen := Remainder;

    AllocPiece(Piece, BufLen);
  {$IFDEF CLR}
    SetLength(Buffer, BufLen);
    Stream.Read(Buffer{$IFNDEF CLR}[0]{$ENDIF}, BufLen);
    Marshal.Copy(Buffer, 0, IntPtr(Integer(Piece) + Sizeof(TPieceHeader)), BufLen);
  {$ELSE}
    Stream.Read(IntPtr(Integer(Piece) + Sizeof(TPieceHeader))^, BufLen);
  {$ENDIF}
    Piece.Used := BufLen;
    AppendPiece(Piece);

    Dec(Remainder, BufLen);
  end;

  FModified := True;
end;

procedure TBlob.SaveToStream(Stream: TStream);
var
  Piece: PPieceHeader;
  BufLen: cardinal;
{$IFDEF CLR}
  Buffer: TBytes;
{$ENDIF}
begin
  Stream.Size := 0;

  Piece := FirstPiece;

  while IntPtr(Piece) <> nil do begin
    BufLen := Piece.Used;

  {$IFDEF CLR}
    SetLength(Buffer, BufLen);
    Marshal.Copy(IntPtr(Integer(Piece) + Sizeof(TPieceHeader)), Buffer, 0, BufLen);
    Stream.Write(Buffer{$IFNDEF CLR}[0]{$ENDIF}, BufLen);
  {$ELSE}
    Stream.Write(IntPtr(Integer(Piece) + Sizeof(TPieceHeader))^, BufLen);
  {$ENDIF}

    Piece := Piece.Next;
  end;
end;

procedure TBlob.LoadFromFile(const FileName: string);
var
  Stream:TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

procedure TBlob.SaveToFile(const FileName: string);
var
  Stream:TStream;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  try
    SaveToStream(Stream);
  finally
    Stream.Free;
  end;
end;

procedure TBlob.Assign(Source: TBlob);
const
  BufSize = 65536;
var
  Buf: IntPtr;
  Pos: cardinal;
  Size: cardinal;
begin
  Clear;

  Pos := 0;
  Buf := Marshal.AllocHGlobal(BufSize);
  try
    repeat
      Size := Source.Read(Pos, BufSize, Buf);
      if Size > 0 then begin
        Write(Pos, Size, Buf);
        Inc(Pos, Size);
      end;
    until Size = 0;
  finally
    Marshal.FreeHGlobal(Buf);
  end;
end;

{ Cached }

procedure TBlob.CheckCached;
begin
  if not FNeedRollback then
    raise Exception.Create(SBlobMustBeCached);
end;

procedure TBlob.SaveToRollback;
var
  Piece: PPieceHeader;
  CSize: Longint;
begin
  CheckCached;

  Rollback := TBlob.Create;
  Rollback.FIsUnicode := FIsUnicode;
  Rollback.FModified := FModified;

  if IntPtr(FFirstPiece) <> nil then begin
    // make copy of data
    CSize := Size;
    AllocPiece(Piece, CSize);
    Piece.used := CSize;
    Read(0, CSize, IntPtr(Integer(Piece) + sizeof(TPieceHeader)));

    Rollback.FFirstPiece := FFirstPiece;
    FFirstPiece := nil;
    AppendPiece(Piece);
  end;
end;

procedure TBlob.EnableRollback;
begin
  {if FNeedRollback then
    raise Exception.Create(SCachedAlreadyEnabled);}

  FNeedRollback := True;
end;

procedure TBlob.Commit;
begin
  //CheckCached;

  if Rollback <> nil then begin
    Rollback.Free;
    Rollback := nil;
  end;
end;

procedure TBlob.Cancel;
var
  Piece: PPieceHeader;
begin
  //CheckCached;

  if Rollback <> nil then begin
    Piece := Rollback.FFirstPiece;
    Rollback.FFirstPiece := FFirstPiece;
    FFirstPiece := Piece;
    FModified := Rollback.FModified; 

    Rollback.Free;
    Rollback := nil;
  end;
end;

function TBlob.CanRollback: boolean;
begin
  Result := Rollback <> nil;
end;

function TBlob.GetDataSize: cardinal; // sum of pieces.used
var
  Piece: PPieceHeader;
begin
  Result := 0;
  Piece := FFirstPiece;
  while IntPtr(Piece) <> nil do begin
    Inc(Result, Piece.Used);
    Piece := Piece.Next;
  end;
end;

function TBlob.GetSize: cardinal;
begin
  Result := GetDataSize;
end;

procedure TBlob.SetSize(Value: cardinal);
var
  Piece: PPieceHeader;
  OldSize: cardinal;
begin
  OldSize := Size;
  if OldSize > Value then
    Truncate(Value)
  else
    if OldSize < Value then begin
      AllocPiece(Piece, Value - OldSize);
      Piece.Used := Value - OldSize;
      FillChar(IntPtr(Integer(Piece) + Sizeof(TPieceHeader)), Value - OldSize, 0);
      AppendPiece(Piece);
    end;
end;

procedure TBlob.SetIsUnicode(Value: boolean);
begin
  if Value = IsUnicode then
    Exit;

  if Size > 0 then
    DataError(SCannotChangeIsUnicode);

  FIsUnicode := Value;
end;

procedure TBlob.InternalClear;
var
  Piece: PPieceHeader;
begin
  while IntPtr(FFirstPiece) <> nil do begin
    Piece := FFirstPiece;
    FFirstPiece := FFirstPiece.Next;
    Marshal.FreeHGlobal(Piece);
  end;
end;

function TBlob.TranslatePosition(Position: integer): integer; // Ansi to Unicode
var
  Piece: PPieceHeader;
  CurPosAnsi, CurPosUni, i: integer;
  p: IntPtr;
  w: WideString;
  s: string;
begin
  Assert(FIsUnicode);

  if {$IFNDEF CLR}not SysLocale.FarEast{$ELSE}(LeadBytes = []){$ENDIF} or (Position = 0) then begin
    Result := Position * 2;
    Exit;
  end;

  CurPosAnsi := 0;
  CurPosUni := 0;
  Piece := FFirstPiece;
  while IntPtr(Piece) <> nil do begin
    p := IntPtr(Integer(Piece) + Sizeof(TPieceHeader));
    for i := 0 to Cardinal((Piece.Used div 2) - 1) do begin
      w := Marshal.PtrToStringUni(IntPtr(Integer(p) + i * 2), 1);
      s := w;
      Inc(CurPosUni, 2);
      Inc(CurPosAnsi, Length(s));
      if CurPosAnsi = Position then begin
        Result := CurPosUni;
        Exit;
      end;
      if CurPosAnsi > Position then
        raise Exception.Create(SInvalidBlobPosition);
    end;
    Piece := Piece.Next;
  end;
  raise Exception.Create(SInvalidBlobPosition);
end;

function TBlob.GetSizeAnsi: integer;
var
  Piece: PPieceHeader;
  i: integer;
  p: IntPtr;
  w: WideString;
  s: string;
begin
  Assert(FIsUnicode);

  if {$IFNDEF CLR}not SysLocale.FarEast{$ELSE}(LeadBytes = []){$ENDIF} then begin
    Result := Cardinal(Size div 2);
    Exit;
  end;

  Result := 0;
  Piece := FFirstPiece;
  while IntPtr(Piece) <> nil do begin
    p := IntPtr(Integer(Piece) + Sizeof(TPieceHeader));
    for i := 0 to Cardinal((Piece.Used div 2) - 1) do begin
      w := Marshal.PtrToStringUni(IntPtr(Integer(p) + i * 2), 1);
      s := w;
      Inc(Result, Length(s));
    end;
    Piece := Piece.Next;
  end;
end;

function TBlob.GetAsString: string;
var
  Buffer: TBytes;
  Handle: IntPtr;
begin
  SetLength(Buffer, Size);
  Handle := AllocGCHandle(Buffer, True);
  try
    Read(0, 0, GetAddrOfPinnedObject(Handle));
  finally
    FreeGCHandle(Handle);
  end;
  if FIsUnicode then
    Result := Encoding.Unicode.GetString(Buffer, 0, Size)
  else
    Result := Encoding.Default.GetString(Buffer, 0, Size);
end;

procedure TBlob.SetAsString(Value: string);
var
  Ws: WideString;
  Buffer: IntPtr;
  Size: cardinal;
begin
  Clear;
  if FIsUnicode then begin
    Ws := Value;
    Buffer := Marshal.StringToHGlobalUni(Ws);
    Size := (Length(Value) shl 1);
  end
  else begin
    Buffer := Marshal.StringToHGlobalAnsi(Value);
    Size := Length(Value);
  end;
  try
    Write(0, Size, Buffer);
  finally
    Marshal.FreeCoTaskMem(Buffer);
  end;
end;

function TBlob.GetAsWideString: WideString;
var
  Buffer: TBytes;
  Handle: IntPtr;
  CachedSize: integer; // performance optimization
begin
  CachedSize := Size;
  SetLength(Buffer, CachedSize);
  Handle := AllocGCHandle(Buffer, True);
  try
    Read(0, 0, GetAddrOfPinnedObject(Handle));
  finally
    FreeGCHandle(Handle);
  end;
  if FIsUnicode then begin
    Assert(CachedSize mod 2 = 0); // WideString must have even Size
    Result := Encoding.Unicode.{$IFDEF CLR}GetString{$ELSE}GetWideString{$ENDIF}(Buffer);
  end
  else
    Result := Encoding.Default.GetString(Buffer);
end;

procedure TBlob.SetAsWideString(Value: WideString);
{$IFDEF WIN32}
var
  s: string;
{$ENDIF}
begin
{$IFDEF CLR}
  SetAsString(Value);
{$ENDIF}
{$IFDEF WIN32}
  Clear;
  if not FIsUnicode then begin
    s := Value;
    Write(0, Length(s), PChar(s));
  end
  else
    Write(0, Length(Value) shl 1, PWideChar(Value));
{$ENDIF}
end;

procedure TBlob.AddCR;
begin
  if FIsUnicode then
    AddCRUnicode
  else
    AddCRString;

  FModified := True;
end;

procedure TBlob.RemoveCR;
begin
  if FIsUnicode then
    RemoveCRUnicode
  else
    RemoveCRString;

  FModified := True;
end;

procedure TBlob.AddCRString;
var
  SourcePiece: PPieceHeader;
  DestPiece: PPieceHeader;
  LastPiece: PPieceHeader;
  FirstPiece: PPieceHeader;
  TempPiece: PPieceHeader;

  Source: IntPtr;
  SourceStart: IntPtr;
  Dest: IntPtr;
  DestEnd: IntPtr;
  SourceEnd: IntPtr;

  Shift: cardinal;
  Used: cardinal;
  w: word;
  b: byte;
  c: byte;

  procedure AllocDestPiece;
  var
    AUsed, AUsed2: cardinal;
  begin
    AUsed := Used + Cardinal(Integer(SourceStart)) - Cardinal(Integer(Source));
    if Dest <> nil then
      DestPiece.Used := Cardinal(DestPiece.Size) - 1 + Cardinal(Integer(Dest))- Cardinal(Integer(DestEnd));
    if AUsed < PieceSize div 2 then begin
      AUsed2 := AUsed * 2; //temporary for Update 7.1
      AllocPiece(DestPiece, AUsed2)
    end
    else
      AllocPiece(DestPiece, PieceSize);
    Dest := IntPtr(Integer(DestPiece) + SizeOf(TPieceHeader));
    DestEnd := IntPtr(Integer(Dest) + Integer(DestPiece.Size) - 1);
    DestPiece.Blob := Self.GetHashCode;
    DestPiece.Prev := LastPiece;
    if IntPtr(LastPiece) <> nil then
      LastPiece.Next := DestPiece;
    LastPiece := DestPiece;
    if IntPtr(FirstPiece) = nil then
      FirstPiece := DestPiece;
  end;

begin
  CheckValid;   // DEBUG

  CheckValue;

  if (IntPtr(FFirstPiece) = nil) then
    Exit;

  SourcePiece := FFirstPiece;
  FirstPiece := nil;
  LastPiece := nil;
  DestPiece := nil;
  Dest := nil;
  DestEnd := nil;
  Shift := 0;
  Used := Size;

  while (IntPtr(SourcePiece) <> nil) do begin
    if SourcePiece.Used > Shift then begin
      SourceStart := IntPtr(Integer(SourcePiece) + SizeOf(TPieceHeader) + Integer(Shift));
      Source := SourceStart;
      SourceEnd := IntPtr(Integer(Source) + Integer(SourcePiece.Used) - 1 - Integer(Shift));

      while Integer(Source) < Integer(SourceEnd) do begin
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        w := Marshal.ReadInt16(Source);
        if w = CRLF then begin
          Marshal.WriteInt16(Dest, w);
          Source := IntPtr(Integer(Source) + 2);
          Dest := IntPtr(Integer(Dest) + 2);
        end
        else begin
          b := Byte(w);
          if b = LF then begin
            Marshal.WriteInt16(Dest, CRLF);
            Source := IntPtr(Integer(Source) + 1);
            Dest := IntPtr(Integer(Dest) + 2);
          end
          else begin
            Marshal.WriteByte(Dest, b);
            Source := IntPtr(Integer(Source) + 1);
            Dest := IntPtr(Integer(Dest) + 1);
          end;
        end;
      end;

      if Source = SourceEnd then begin
        c := Marshal.ReadByte(Source);
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        Shift := Ord(
          ((c = 13)
            and
            (((IntPtr(SourcePiece.Next) <> nil)
            and
            (
              Marshal.ReadByte(
                IntPtr(Integer(IntPtr(SourcePiece.Next)) + SizeOf(TPieceHeader))
              ) = 10
            ))
            or
            (IntPtr(SourcePiece.Next) = nil)))
          or
          (c = 10)
        );
        if (Shift = 1) then begin
          Marshal.WriteInt16(Dest, CRLF);
          Dest := IntPtr(Integer(Dest) + 2);
        end
        else begin
          Marshal.WriteByte(Dest, c);
          Dest := IntPtr(Integer(Dest) + 1);
        end;
      end else
        Shift := 0;
    end;
    Dec(Used, SourcePiece.Used);
    TempPiece := SourcePiece;
    SourcePiece := SourcePiece.Next;
    Marshal.FreeHGlobal(TempPiece);
  end;
  if Dest <> nil then
    DestPiece.Used := DestPiece.Size - 1 + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
  FFirstPiece := FirstPiece;
end;

procedure TBlob.RemoveCRString;
var
  SourcePiece: PPieceHeader;
  DestPiece: PPieceHeader;
  LastPiece: PPieceHeader;
  FirstPiece: PPieceHeader;
  TempPiece: PPieceHeader;

  SourceStart: IntPtr;
  Source: IntPtr;
  Dest: IntPtr;
  DestEnd: IntPtr;
  SourceEnd: IntPtr;

  Shift: cardinal;
  Used: cardinal;
  w: word;
  c: byte;

  procedure AllocDestPiece;
  var
    AUsed: cardinal;
  begin
    AUsed := Used + Cardinal(Integer(SourceStart)) - Cardinal(Integer(Source));
    if Dest <> nil then
      DestPiece.Used := DestPiece.Size + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
    if AUsed < PieceSize then
      AllocPiece(DestPiece, AUsed)
    else
      AllocPiece(DestPiece, PieceSize);
    Dest := IntPtr(Integer(IntPtr(DestPiece)) + SizeOf(TPieceHeader));
    DestEnd := IntPtr(Integer(Dest) + Integer(DestPiece.Size));
    DestPiece.Blob := Self.GetHashCode;
    DestPiece.Prev := LastPiece;
    if IntPtr(LastPiece) <> nil then
      LastPiece.Next := DestPiece;
    LastPiece := DestPiece;
    if IntPtr(FirstPiece) = nil then
      FirstPiece := DestPiece;
  end;

begin
  CheckValid;   // DEBUG

  CheckValue;

  if (IntPtr(FFirstPiece) = nil) then
    Exit;

  SourcePiece := FFirstPiece;
  FirstPiece := nil;
  LastPiece := nil;
  DestPiece := nil;
  Dest := nil;
  DestEnd := nil;
  Shift := 0;
  Used := Size;

  while (IntPtr(SourcePiece) <> nil) do begin
    if SourcePiece.Used > Shift then begin
      SourceStart := IntPtr(Integer(SourcePiece) + SizeOf(TPieceHeader) + Integer(Shift));
      Source := SourceStart;
      SourceEnd := IntPtr(Integer(Source) + Integer(SourcePiece.Used) - 1 - Integer(Shift));

      while Integer(Source) < Integer(SourceEnd) do begin
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        w := Marshal.ReadInt16(Source);
        if w = CRLF then begin
          Marshal.WriteByte(Dest, LF);
          Source := IntPtr(Integer(Source) + 2);
          Dest := IntPtr(Integer(Dest) + 1);
        end
        else
        begin
          Marshal.WriteByte(Dest, Byte(w));
          Source := IntPtr(Integer(Source) + 1);
          Dest := IntPtr(Integer(Dest) + 1);
        end;
      end;

      if Source = SourceEnd then begin
        c := Marshal.ReadByte(Source);
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        Shift := Ord((c = 13) and (IntPtr(SourcePiece.Next) <> nil)
          and
          (
            Marshal.ReadByte(
              IntPtr(Integer(IntPtr(SourcePiece.Next)) + SizeOf(TPieceHeader))
            ) = 10)
          );
        if Shift = 1 then
          c := 10;
        Marshal.WriteByte(Dest, c);
        Dest := IntPtr(Integer(Dest) + 1);
      end else
        Shift := 0;
    end;
    Dec(Used, SourcePiece.Used);
    TempPiece := SourcePiece;
    SourcePiece := SourcePiece.Next;
    Marshal.FreeHGlobal(TempPiece);
  end;
  if Dest <> nil then
    DestPiece.Used := DestPiece.Size + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
  FFirstPiece := FirstPiece;
end;

procedure TBlob.AddCRUnicode;
var
  SourcePiece: PPieceHeader;
  DestPiece: PPieceHeader;
  LastPiece: PPieceHeader;
  FirstPiece: PPieceHeader;
  TempPiece: PPieceHeader;

  Source: IntPtr;
  SourceStart: IntPtr;
  Dest: IntPtr;
  DestEnd: IntPtr;
  SourceEnd: IntPtr;

  Shift: cardinal; //bytes
  Used: cardinal; //bytes
  w: LongWord;
  b: Word;
  c: Word;
  procedure AllocDestPiece;
  var
    AUsed, AUsed2: cardinal;
  begin
    AUsed := Used + Cardinal(Integer(SourceStart)) - Cardinal(Integer(Source));
    if Dest <> nil then
      DestPiece.Used := DestPiece.Size - sizeof(WideChar) + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
    if AUsed < PieceSize div 2 then begin
      AUsed2 := AUsed * 2; //temporary for Update 7.1
      AllocPiece(DestPiece, AUsed2)
    end
    else
      AllocPiece(DestPiece, PieceSize);
    Dest := IntPtr(Integer(DestPiece) + SizeOf(TPieceHeader));
    DestEnd := IntPtr(Integer(Dest) + Integer(DestPiece.Size) - sizeof(WideChar));
    DestPiece.Blob := Self.GetHashCode;
    DestPiece.Prev := LastPiece;
    if IntPtr(LastPiece) <> nil then
      LastPiece.Next := DestPiece;
    LastPiece := DestPiece;
    if IntPtr(FirstPiece) = nil then
      FirstPiece := DestPiece;
  end;

begin
  CheckValid;   // DEBUG

  CheckValue;

  if (IntPtr(FFirstPiece) = nil) then
    Exit;

  SourcePiece := FFirstPiece;
  FirstPiece := nil;
  LastPiece := nil;
  DestPiece := nil;
  Dest := nil;
  DestEnd := nil;
  Shift := 0;
  Used := Size;

  while (IntPtr(SourcePiece) <> nil) do begin
    if SourcePiece.Used > Shift then begin
      SourceStart := IntPtr(Integer(SourcePiece) + SizeOf(TPieceHeader) + Integer(Shift));
      Source := SourceStart;
      SourceEnd := IntPtr(Integer(Source) + Integer(SourcePiece.Used) - sizeof(WideChar) - Integer(Shift));

      while Integer(Source) < Integer(SourceEnd) do begin
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        w := Marshal.ReadInt32(Source);
        if w = CRLF_UTF16 then begin
          Marshal.WriteInt32(Dest, w);
          Source := IntPtr(Integer(Source) + 4);
          Dest := IntPtr(Integer(Dest) + 4);
        end
        else begin
          b := Word(w);
          if b = LF_UTF16 then begin
            Marshal.WriteInt32(Dest, CRLF_UTF16);
            Source := IntPtr(Integer(Source) + 2);
            Dest := IntPtr(Integer(Dest) + 4);
          end
          else begin
            Marshal.WriteInt16(Dest, b);
            Source := IntPtr(Integer(Source) + 2);
            Dest := IntPtr(Integer(Dest) + 2);
          end;
        end;
      end;

      if Source = SourceEnd then begin
        c := Marshal.ReadInt16(Source);
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        Shift := Ord(
          (c = 13) and (IntPtr(SourcePiece.Next) <> nil) and
          (
            Marshal.ReadInt16(IntPtr(Integer(IntPtr(SourcePiece.Next)) + SizeOf(TPieceHeader))) = 10
          )
        ) * sizeof(WideChar);
        if Shift = sizeof(WideChar) then begin
          Marshal.WriteInt32(Dest, CRLF_UTF16);
          Dest := IntPtr(Integer(Dest) + 4);
        end
        else begin
          Marshal.WriteInt16(Dest, c);
          Dest := IntPtr(Integer(Dest) + 2);
        end;
      end else
        Shift := 0;
    end;
    Dec(Used, SourcePiece.Used);
    TempPiece := SourcePiece;
    SourcePiece := SourcePiece.Next;
    Marshal.FreeHGlobal(TempPiece);
  end;
  if Dest <> nil then
    DestPiece.Used := DestPiece.Size - sizeof(WideChar) + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
  FFirstPiece := FirstPiece;
end;

procedure TBlob.RemoveCRUnicode;
var
  SourcePiece: PPieceHeader;
  DestPiece: PPieceHeader;
  LastPiece: PPieceHeader;
  FirstPiece: PPieceHeader;
  TempPiece: PPieceHeader;

  SourceStart: IntPtr;
  Source: IntPtr;
  Dest: IntPtr;
  DestEnd: IntPtr;
  SourceEnd: IntPtr;

  Shift: cardinal; //bytes
  Used: cardinal;  //bytes
  w: LongWord;
  c: word;

  procedure AllocDestPiece;
  var
    AUsed: cardinal;
  begin
    AUsed := Used + Cardinal(Integer(SourceStart)) - Cardinal(Integer(Source));
    if Dest <> nil then
      DestPiece.Used := DestPiece.Size + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
    if AUsed < PieceSize then
      AllocPiece(DestPiece, AUsed)
    else
      AllocPiece(DestPiece, PieceSize);
    Dest := IntPtr(Integer(DestPiece) + SizeOf(TPieceHeader));
    DestEnd := IntPtr(Integer(Dest) + Integer(DestPiece.Size));
    DestPiece.Blob := Self.GetHashCode;
    DestPiece.Prev := LastPiece;
    if IntPtr(LastPiece) <> nil then
      LastPiece.Next := DestPiece;
    LastPiece := DestPiece;
    if IntPtr(FirstPiece) = nil then
      FirstPiece := DestPiece;
  end;

begin
  CheckValid;   // DEBUG

  CheckValue;

  if (IntPtr(FFirstPiece) = nil) then
    Exit;

  SourcePiece := FFirstPiece;
  FirstPiece := nil;
  LastPiece := nil;
  DestPiece := nil;
  Dest := nil;
  DestEnd := nil;
  Shift := 0;
  Used := Size;

  while (IntPtr(SourcePiece) <> nil) do begin
    if SourcePiece.Used > Shift then begin
      SourceStart := IntPtr(Integer(SourcePiece) + SizeOf(TPieceHeader) + Integer(Shift));
      Source := SourceStart;
      SourceEnd := IntPtr(Integer(Source) + Integer(SourcePiece.Used) - sizeof(WideChar) - Integer(Shift));

      while Integer(Source) < Integer(SourceEnd) do begin
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        w := marshal.ReadInt32(Source);
        if w = CRLF_UTF16 then begin
          Marshal.WriteInt16(Dest, LF_UTF16);
          Source := IntPtr(Integer(Source) + 4);
          Dest := IntPtr(Integer(Dest) + 2);
        end
        else
        begin
          Marshal.WriteInt16(Dest, Word(w));
          Source := IntPtr(Integer(Source) + 2);
          Dest := IntPtr(Integer(Dest) + 2);
        end;
      end;

      if Source = SourceEnd then begin
        c := Marshal.ReadInt16(Source);
        if Integer(Dest) >= Integer(DestEnd) then
          AllocDestPiece;
        Shift := Ord(
          (c = 13) and (IntPtr(SourcePiece.Next) <> nil) and
          (
            Marshal.ReadInt16(IntPtr(Integer(IntPtr(SourcePiece.Next)) + SizeOf(TPieceHeader))) = 10
          )
        ) * sizeof(WideChar);
        if Shift = sizeof(WideChar) then
          c := 10;
        Marshal.WriteInt16(Dest, c);
        Dest := IntPtr(Integer(Dest) + 2);
      end else
        Shift := 0;
    end;
    Dec(Used, SourcePiece.Used);
    TempPiece := SourcePiece;
    SourcePiece := SourcePiece.Next;
    Marshal.FreeHGlobal(TempPiece);
  end;
  if Dest <> nil then
    DestPiece.Used := DestPiece.Size + Cardinal(Integer(Dest)) - Cardinal(Integer(DestEnd));
  FFirstPiece := FirstPiece;
end;

{ TBlobUtils }

class procedure TBlobUtils.SetModified(Blob: TBlob; Value: boolean);
begin
  Blob.FModified := Value;
end;

{$IFDEF HAVE_COMPRESS}

{ TCompressedBlob }

function TCompressedBlob.CompressFrom(source: IntPtr; const sourceLen: longint): boolean;
var
  CPiece: PPieceHeader;
  CSize: integer;
begin
  // see my_compress_alloc
  // *complen=  *len * 120 / 100 + 12;
  CheckZLib;
  CSize := CCompressBlobHeaderSize{header} + sourceLen + (sourceLen div 5) + 12;
  AllocPiece(CPiece, CSize);
  try
    DoCompress(Pointer(Integer(CPiece) + sizeof(TPieceHeader) + CCompressBlobHeaderSize), @CSize, source, sourceLen);
    CPiece.Used := CCompressBlobHeaderSize + CSize;
    Result := LongInt(CPiece.Used) < sourceLen; // Compression is successful
  except
    Result := False;
  end;
  if not Result then begin
    FreePiece(CPiece);
    Exit;
  end;

  // WriteHeader
  CopyBuffer(@CCompressBlobHeaderGuid[0], PByte(Integer(CPiece) + sizeof(TPieceHeader)), CCompressBlobHeaderGuidSize);
  Marshal.WriteInt32(CPiece, sizeof(TPieceHeader) + CCompressBlobHeaderSize - SizeOf(Integer), sourceLen);

  CompressPiece(CPiece);
  if FFirstPiece <> nil then
    FreePiece(FFirstPiece);
  AppendPiece(CPiece);

  FModified := True;
end;

procedure TCompressedBlob.UncompressTo(dest: IntPtr; var destlen: integer);
var
  source: IntPtr;
begin
  Assert(FFirstPiece <> nil);

  Defrag;
  source := PByte(Integer(FFirstPiece) + sizeof(TPieceHeader));

  Assert(FFirstPiece.Next = nil);

  // Check header
  if FFirstPiece.Used <= CCompressBlobHeaderSize then
    DataError(SInvalidComprBlobSize);
  if not CompareMem(source, @CCompressBlobHeaderGuid[0], CCompressBlobHeaderGuidSize) then
    DataError(SInvalidComprBlobHeader);

  CheckZLib;
  try
    DoUncompress(dest, @destlen, IntPtr(Integer(source) + CCompressBlobHeaderSize), FFirstPiece.Used - CCompressBlobHeaderSize);
  except
    DataError(SInvalidComprBlobData);
  end;
end;

function TCompressedBlob.GetSize: cardinal;
begin
  if Compressed then begin
    Result := UnCompressedSize;
    Assert(Result > 0);
  end
  else
    Result := inherited GetSize;
end;

procedure TCompressedBlob.SetSize(Value: cardinal);
begin
  if Compressed then
    Assert((Value = 0) or (Value = Size));

  inherited;
end;

function TCompressedBlob.GetCompressedSize: cardinal;
begin
  if not Compressed then
    DataError(sBlobNotCompressed);
  Result := inherited GetSize;
end;

function TCompressedBlob.GetCompressed: boolean;
begin
  Result :=
    (FFirstPiece <> nil) and
    // (FFirstPiece.Next = nil) and - false, if blob copied from another blob
    (FFirstPiece.Used > CCompressBlobHeaderSize) and
    CompareMem(IntPtr(Integer(FFirstPiece) + sizeof(TPieceHeader)), @CCompressBlobHeaderGuid[0], CCompressBlobHeaderGuidSize);
end;

procedure TCompressedBlob.SetCompressed(Value: boolean);
var
  CPiece: PPieceHeader;
  Count, CSize: integer;
begin
  if (IntPtr(FFirstPiece) = nil) or (Compressed = Value) then
    Exit;

  if Value then begin
    { pack
    (b) small blob without compression (Size < MIN_COMPRESS_LENGTH).
    (c) big blobs without compression (ZIP, JPG etc).
    (d) big blobs with compression (TXT etc).
    }
    Count := Size;
    // (b)
    if Count <= MIN_COMPRESS_LENGTH then
      Exit;

    Defrag;
    CompressFrom(PByte(Integer(FFirstPiece) + sizeof(TPieceHeader)), Count);
  end
  else
  begin
    // unpack
    CheckValid;   // DEBUG
    CheckValue;

    Assert(FFirstPiece <> nil, 'FFirstPiece = nil');

    CSize := UnCompressedSize;
    AllocPiece(CPiece, CSize);
    try
      UncompressTo(IntPtr(Integer(CPiece) + sizeof(TPieceHeader)), CSize);
      CPiece.Used := CSize;
      if CPiece.Used <> CPiece.Size then
        DataError(SInvalidUnComprBlobSize); //DatabaseError(SInvalidUnComprBlobSize);
    except
      FreePiece(CPiece);
      raise;
    end;

    FreePiece(FFirstPiece);
    AppendPiece(CPiece);
  end;

  FModified := True;
end;

function TCompressedBlob.UnCompressedSize: cardinal;
begin
  Assert(Compressed);
  Result := Marshal.ReadInt32(FFirstPiece, sizeof(TPieceHeader) + CCompressBlobHeaderSize - SizeOf(Integer));
end;

procedure TCompressedBlob.Truncate(NewSize: cardinal);
begin
  if Compressed and (NewSize <> 0) then
    Compressed := False;
  inherited;
end;

function TCompressedBlob.Read(Position, Count: cardinal;
  Dest: IntPtr): cardinal;
var
  CSize: Longint;
  ReadAll: boolean;
begin
  // partial read or read all blob?
  ReadAll := (Position = 0) and ((Count = Size) or (Count = 0));
  if Compressed and not ReadAll then
    Compressed := False;

  if Compressed then begin
    Assert(ReadAll);

    // Copied from inherited
    CheckValid;   // DEBUG
    CheckValue;
    Result := 0;

    if (IntPtr(FFirstPiece) = nil) or (Position > Size) then
      Exit;

    if Count = 0 then
      Count := Size;
    //-----------

    CSize := Count;
    UncompressTo(Dest, CSize);
    Assert(Cardinal(CSize) = Count);
    Result := CSize;
  end
  else
    Result := inherited Read(Position, Count, Dest);
end;

procedure TCompressedBlob.Write(Position, Count: cardinal; Source: IntPtr);
begin
  if Compressed then begin
    if (Position <> 0) or ((Count <> Size) and (Size <> 0)) {full rewrite} then begin
      Compressed := False;
      inherited;
      Exit;
    end;
    Clear;

    { pack
    (b) small blob without compression (Size < MIN_COMPRESS_LENGTH).
    (c) big blobs without compression (ZIP, JPG etc).
    (d) big blobs with compression (TXT etc).
    }

    // (b)
    if (Count <= MIN_COMPRESS_LENGTH) or not CompressFrom(Source, Count) then
      inherited;
  end
  else
    inherited;
end;

procedure TCompressedBlob.SaveToRollback;
var
  Piece: PPieceHeader;
  CSize: Longint;
begin
  CheckCached;

  Rollback := TCompressedBlob.Create;
  Rollback.IsUnicode := IsUnicode;
  Rollback.FModified := FModified;

  CSize := Size;
  if (IntPtr(FFirstPiece) <> nil) and (CSize <> 0) then begin
    // make copy of data
    AllocPiece(Piece, CSize);
    Piece.used := CSize;
    Read(0, CSize, IntPtr(Integer(Piece) + sizeof(TPieceHeader)));

    TCompressedBlob(Rollback).FFirstPiece := FFirstPiece;
    FFirstPiece := nil;
    AppendPiece(Piece);
  end;
end;
{$ENDIF}

{$IFDEF VER6}
{$IFDEF MSWINDOWS}
function LCIDToCodePage(ALcid: LongWord): Integer;
const
  CP_ACP = 0;                                // system default code page
  LOCALE_IDEFAULTANSICODEPAGE = $00001004;   // default ansi code page
var
  ResultCode: Integer;
  Buffer: array [0..6] of Char;
begin
  GetLocaleInfo(ALcid, LOCALE_IDEFAULTANSICODEPAGE, Buffer, SizeOf(Buffer));
  Val(Buffer, Result, ResultCode);
  if ResultCode <> 0 then
    Result := CP_ACP;
end;
{$ENDIF}
{$ENDIF}

initialization
  StartWaitProc := nil;
  StopWaitProc := nil;
  ApplicationTitleProc := nil;
{$IFNDEF VER6P}
  ApplicationHandleException := nil;
{$ENDIF}

{$IFDEF VER6}
{$IFDEF MSWINDOWS}
  // Code from Delphi7 system.pas
  // High bit is set for Win95/98/ME
  if not IsWin9x then
  begin
    if {Lo(GetVersion) > 4} Win32MajorVersion > 4 then
      DefaultUserCodePage := 3  // Use CP_THREAD_ACP with Win2K/XP
    else
      // Use thread's current locale with NT4
      DefaultUserCodePage := LCIDToCodePage(GetThreadLocale);
  end
  else
    // Convert thread's current locale with Win95/98/ME
    DefaultUserCodePage := LCIDToCodePage(GetThreadLocale);
{$ENDIF}    
{$ENDIF}

  BoolSymbolLexems := TStringList.Create;
  BoolKeywordLexems := TStringList.Create;

  BoolSymbolLexems.AddObject('=', TObject(Integer(lxEqual)));
  BoolSymbolLexems.AddObject('>', TObject(Integer(lxMore)));
  BoolSymbolLexems.AddObject('<', TObject(Integer(lxLess)));
  BoolSymbolLexems.AddObject('>=', TObject(Integer(lxMoreEqual)));
  BoolSymbolLexems.AddObject('<=', TObject(Integer(lxLessEqual)));
  BoolSymbolLexems.AddObject('<>', TObject(Integer(lxNoEqual)));
  BoolSymbolLexems.AddObject('(', TObject(Integer(lxLeftBracket)));
  BoolSymbolLexems.AddObject(')', TObject(Integer(lxRightBracket)));
  BoolSymbolLexems.AddObject('-', TObject(Integer(lxMinus)));
  BoolSymbolLexems.AddObject('+', TObject(Integer(lxPlus)));
  BoolSymbolLexems.AddObject('[', TObject(Integer(lxLeftSqBracket)));
  BoolSymbolLexems.AddObject(']', TObject(Integer(lxRightSqBracket)));
  BoolSymbolLexems.CustomSort(CRCmpStrings);

  BoolKeywordLexems.AddObject('AND', TObject(Integer(lxAND)));
  BoolKeywordLexems.AddObject('FALSE', TObject(Integer(lxFALSE)));
  BoolKeywordLexems.AddObject('IS', TObject(Integer(lxIS)));
  BoolKeywordLexems.AddObject('NOT', TObject(Integer(lxNOT)));
  BoolKeywordLexems.AddObject('NULL', TObject(Integer(lxNULL)));
  BoolKeywordLexems.AddObject('OR', TObject(Integer(lxOR)));
  BoolKeywordLexems.AddObject('TRUE', TObject(Integer(lxTRUE)));
  BoolKeywordLexems.AddObject('LIKE', TObject(Integer(lxLIKE)));
  BoolKeywordLexems.CustomSort(CRCmpStrings);

  RefreshIteration := 1;

finalization
  BoolSymbolLexems.Free;
  BoolKeywordLexems.Free;
  
{$IFDEF MSWINDOWS}
  {$IFDEF CRDEBUG} if DataCnt <> 0 then MessageBox(0, PChar(IntToStr(DataCnt) + ' Data(s) hasn''t been released'), 'DA warning', MB_OK); {$ENDIF}
  {$IFDEF CRDEBUG} if ShareObjectCnt <> 0 then MessageBox(0, PChar(IntToStr(ShareObjectCnt) + ' ShareObject(s) hasn''t been released'), 'DA warning', MB_OK); {$ENDIF}
{$ENDIF}
end.
