Componentes.Terceros.SDAC/internal/4.10.0.10/1/Source/MemData.pas
2007-10-05 14:48:18 +00:00

9029 lines
248 KiB
ObjectPascal

//////////////////////////////////////////////////
// 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.