//////////////////////////////////////////////////
//  DB Access Components
//  Copyright © 1998-2014 Devart. All right reserved.
//  Memory Data Set
//  Created:            01.02.98
//////////////////////////////////////////////////

{$IFNDEF CLR}
{$IFNDEF FPC}
{$IFNDEF EntityDAC}
{$I Dac.inc}
unit MemDS;
{$ENDIF}
{$ENDIF}
{$ENDIF}

interface

uses
  Classes, SysUtils, {$IFDEF VER6P}Variants,{$ENDIF} DB,
{$IFDEF BDE_SHARED}
  DBTables,
{$ENDIF}
{$IFDEF MSWINDOWS}
  {$IFNDEF FPC}Windows, {$ENDIF}
  {$IFNDEF EntityDAC}
  Win32Timer,
  {$ELSE}
  EntityDAC.Common.Win32Timer,
  {$ENDIF}
{$ENDIF}
{$IFDEF CLR}
  System.Text, System.IO, System.XML, System.Runtime.InteropServices,
  Contnrs, ExtCtrls,
{$ELSE}
  {$IFNDEF EntityDAC}
  CLRClasses, CRXml,
  {$ELSE}
  EntityDAC.Common.CLRClasses, EntityDAC.Common.Xml,
  {$ENDIF}
{$ENDIF}
{$IFDEF VER17P}
  Generics.Collections,
{$ENDIF}
{$IFNDEF EntityDAC}
  CRTypes, CRFunctions, MemData, MemUtils;
{$ELSE}
  EntityDAC.Common.Types, EntityDAC.Common.Functions, EntityDAC.Common.MemData, EntityDAC.Common.MemUtils;
{$ENDIF}

const
  uaDefault = 10; // TUpdateAction
  rlAny = -1; // Data Type Mapping Rule

{$IFNDEF FPC}
{$IFNDEF VER10P}
  ftFixedWideChar = 52;
{$ENDIF}
{$ENDIF}

type
  TDataSetService = class;
  TDataSetServiceClass = class of TDataSetService;
{$IFNDEF EntityDAC}
  TMemDataSet = class;
{$ELSE}
  TEDMemDataSet = class;
{$ENDIF}

{ TMemDataSet }

{$IFDEF FPC}
  TUpdateRecordTypes = set of (rtModified, rtInserted, rtDeleted, rtUnmodified);
  TUpdateErrorEvent = procedure(DataSet: TDataSet; E: EDatabaseError;
    UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction) of object;
  TUpdateRecordEvent = procedure(DataSet: TDataSet; UpdateKind: TUpdateKind;
    var UpdateAction: TUpdateAction) of object;
{$ENDIF}

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

    function GetRecordNumber: longint;
    procedure SetRecordNumber(const Value: longint);
    function GetUpdateStatus: TUpdateStatus;
    procedure SetUpdateStatus(const Value: TUpdateStatus);
    function GetBookmarkFlag: TBookmarkFlag;
    procedure SetBookmarkFlag(const Value: TBookmarkFlag);
    function GetRefComplexFields: boolean;
    procedure SetRefComplexFields(const Value: boolean);
    function GetKeyExclusive: Boolean;
    procedure SetKeyExclusive(const Value: Boolean);

  public
    property RecordNumber: longint read GetRecordNumber write SetRecordNumber;
    property UpdateStatus: TUpdateStatus read GetUpdateStatus write SetUpdateStatus;
    property BookmarkFlag: TBookmarkFlag read GetBookmarkFlag write SetBookmarkFlag;
    property RefComplexFields: boolean read GetRefComplexFields write SetRefComplexFields;
    property KeyExclusive: Boolean read GetKeyExclusive write SetKeyExclusive;

    class operator Implicit(AValue: IntPtr): PRecInfo;
  end;
{$ELSE}
{$IFDEF VER12P}
  TRecordBuffer = PByte;
{$ELSE}
  TRecordBuffer = PAnsiChar;
{$ENDIF}
  PRecInfo = ^TRecInfo;
{$ENDIF}
  TRecInfo = packed record
    RecordNumber: longint;
    UpdateStatus: TUpdateStatus;
    BookmarkFlag: TBookmarkFlag;
    RefComplexFields: boolean;
    KeyExclusive: boolean;
  end;

  TCalcFieldDescMapping = record
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FieldDesc: TFieldDesc;
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    Field: TField;
  end;

  TLocalMDLink = record
    IsNull: boolean;
    Buffer: IntPtr;
    BufferType: Word;
    NativeBuffer: boolean;
    FieldNo: integer;
  end;

  TLocalMDLinks = array of TLocalMDLink;

  TFieldTypeMap = class
  public
    class function GetFieldType(DataType: Word): TFieldType; virtual;
    class function GetDataType(FieldType: TFieldType): integer; virtual;
  end;

  TFieldTypeMapClass = class of TFieldTypeMap;

{ TDADetailDataLink }

  TDADetailDataLink = class (TDetailDataLink)
  private
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    {$IFNDEF EntityDAC}
    FDataSet: TMemDataSet;
    {$ELSE}
    FDataSet: TEDMemDataSet;
    {$ENDIF}

  protected
    procedure ActiveChanged; override;
    procedure RecordChanged(Field: TField); override;
    procedure CheckBrowseMode; override;
    function GetDetailDataSet: TDataSet; override;
  public
    constructor Create(DataSet: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF});
  end;

  TDataSetUpdater = class
  protected
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    {$IFNDEF EntityDAC}
    FDataSet: TMemDataSet;
    {$ELSE}
    FDataSet: TEDMemDataSet;
    {$ENDIF}
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FDataSetService: TDataSetService;

    function PerformAppend: boolean; virtual;
    function PerformDelete: boolean; virtual;
    function PerformUpdate: boolean; virtual;
    function CacheChanged: boolean; virtual;
    function CacheApplied: boolean; virtual;
    function CacheCanceled: boolean; virtual;

    procedure DoPerformAppend;
    procedure DoPerformDelete;
    procedure DoPerformUpdate;
    procedure DoApplyRecord(UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean);
    procedure DoCacheChanged;
    procedure DoCacheApplied;
    procedure DoCacheCanceled;

    function BatchUpdate: boolean; virtual;
    function CanFlushBatch: boolean; virtual;
    procedure FlushBatch; virtual;
  public
    constructor Create(AOwner: TDataSetService); virtual;
  end;

  TDataSetService = class
  protected
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    {$IFNDEF EntityDAC}
    FDataSet: TMemDataSet;
    {$ELSE}
    FDataSet: TEDMemDataSet;
    {$ENDIF}
    FUpdater: TDataSetUpdater;

    procedure CreateDataSetUpdater; virtual;
    procedure SetDataSetUpdater(Value: TDataSetUpdater); virtual;
    procedure FreeDataSetUpdater;

    procedure SetNumberRange(FieldDef: TFieldDef); virtual;
    function GetFieldClass(FieldType: TFieldType; DataType: Word): TFieldClass; virtual;

    procedure PreInitCursor; virtual;

  { XML }
    procedure WriteFieldXMLDataType(Field: TField; FieldDesc: TFieldDesc; const FieldAlias: string;
      XMLWriter: XMLTextWriter); virtual;
    procedure WriteFieldXMLAttributeType(Field: TField; FieldDesc: TFieldDesc; const FieldAlias: string;
      XMLWriter: XMLTextWriter); virtual;
    function GetFieldXMLValue(Field: TField; FieldDesc: TFieldDesc): WideString; virtual;

  public
    constructor Create(AOwner: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}); virtual;
    destructor Destroy; override;

    function SetProp(Prop: integer; const Value: variant): boolean; virtual;
    function GetProp(Prop: integer; var Value: variant): boolean; virtual;

    procedure SaveToXML(Destination: TStream);
  end;

{$IFNDEF EntityDAC}
  TMemDataSet = class(TDataSet)
{$ELSE}
  TEDMemDataSet = class(TDataSet)
{$ENDIF}
  private
    FOldRecBuf: TRecordBuffer;
    FFilterBuffer: TRecordBuffer;
    FRangeStartBuffer: TRecordBuffer;
    FRangeEndBuffer: TRecordBuffer;
    FRangeCurrentBuffer: TRecordBuffer;
    FRanged: Boolean;
    FCachedUpdates: boolean;
    FLocalUpdate: boolean;
    //FInDeferredPost: boolean; to protected
    FInInserting: boolean;
    FInEditing: boolean;
    FIndexFieldNames: string;
    FCalcFieldsMapping: array of TCalcFieldDescMapping;

    FUpdateRecordTypes: TUpdateRecordTypes;
    FOnUpdateError: TUpdateErrorEvent;
    FOnUpdateRecord: TUpdateRecordEvent;

  { Master/Detail }
    FDataLink: TDADetailDataLink;
    FLocalMDLinks: TLocalMDLinks;
    FDetailDelay: integer;
  {$IFDEF MSWINDOWS}
    FDetailRefreshTimer: TWin32Timer;
    procedure CheckRefreshDetailTimer;
  {$ENDIF}
    procedure SetDetailDelay(Value: integer);
    procedure SetMasterSource(Value: TDataSource);
    procedure SetMasterFields(Value: string);
    procedure SetDetailFields(Value: string);

    {function GetBlobData(Field:TField; Buffer: PAnsiChar):TBlobData;
    procedure SetBlobData(Field:TField; Buffer: PAnsiChar; Value:TBlobData);
    procedure ClearBlobCache(Buffer: PAnsiChar);}

    procedure SetCachedUpdates(Value: boolean);
    procedure SetLocalUpdate(Value: boolean);
    function GetUpdatesPending: boolean;
    function GetPrepared: boolean;
    procedure SetPrepared(Value: boolean);
    function ConvertUpdateRecordTypes(Value: TUpdateRecordTypes): TItemTypes;
    function GetUpdateRecordTypes: TUpdateRecordTypes;
    procedure SetUpdateRecordTypes(Value: TUpdateRecordTypes);

  { Range }
    procedure CheckSetKeyMode;
    procedure SetRangeBuffer(Buffer: TRecordBuffer; const Values: array of const; Clear: Boolean);
    function GetKeyExclusive: Boolean;
    procedure SetKeyExclusive(Value: Boolean);
    procedure InitRangeBuffer(Buffer: TRecordBuffer);
    procedure ResetRange;

  protected
    Data: TData;  // FIRecordSet
    FDataSetService: TDataSetService;

    FBookmarkOfs: longint;
    FRecInfoOfs: longint;
{$IFNDEF CLR}
{$IFNDEF VER10P}
    FWideStringOfs: longint;
{$ENDIF}
{$ENDIF}
    FRecBufSize: longint;
    FInDeferredPost: boolean;  // private
    NewCacheRecBuf: TRecordBuffer;
    OldCacheRecBuf: TRecordBuffer;
    OldDeferredPostBuf: TRecordBuffer;
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    {$IFNDEF EntityDAC}
    FParentDataSet: TMemDataSet;
    {$ELSE}
    FParentDataSet: TEDMemDataSet;
    {$ENDIF}
    FLastParentPos: integer;
    FLocalConstraints: boolean;
    FNumberRange: boolean;
    FNeedAddRef: boolean;
    FCacheCalcFields: boolean;
    FCreateCalcFieldDescs: boolean;
    FInSettingDefaultExpressionValues: boolean;

    FMasterFields: string;
    FDetailFields: string;

  {$IFDEF CLR}
    function BookmarkAvailable: Boolean;
    property BookmarkSize;
  {$ENDIF}

    procedure SetModified(Value: Boolean); reintroduce;
    function SetTempState(const Value: TDataSetState): TDataSetState; reintroduce;
    procedure RestoreState(const Value: TDataSetState); reintroduce;
    procedure DoBeforeRefresh; override;
    procedure DoAfterRefresh; override;

    procedure CreateIRecordSet; virtual;
    procedure FreeIRecordSet;
    procedure SetIRecordSet(Value: TData{TRecordSet}); virtual;
    procedure SetIndexFieldNames(const Value: string); virtual;

    function GetDataSetServiceClass: TDataSetServiceClass; virtual;
    procedure CreateDataSetService;
    procedure FreeDataSetService;
    procedure SetDataSetService(Value: TDataSetService); virtual;
    procedure CheckDataSetService;

  { Open/Close DataSet }
    procedure OpenCursor(InfoQuery: boolean); override;
    procedure CloseCursor; override;

  {$IFDEF FPC}
    procedure CreateFields; override;
  {$ENDIF}
    procedure InternalOpen; override;
    procedure InternalClose; override;
    function IsCursorOpen: boolean; override;
    procedure DataReopen; virtual;
    procedure InternalRefresh; override;
    procedure DoAfterOpen; override;

  { Field Management }
    function GetFieldTypeMapClass: TFieldTypeMapClass; virtual;

    procedure InternalInitFieldDefs; override;
    function NeedCreateFieldDefs: boolean; virtual;
    function CreateFieldDef(FieldDesc: TFieldDesc): TFieldDef; virtual;
    procedure CreateFieldDefs; virtual;
  {$IFNDEF FPC}
    procedure UpdateFieldDefList;
  {$ENDIF}
    function  NeedComplexUpdateFieldDefList: boolean; virtual;
  {$IFDEF VER18P}
    procedure ClearCalcFields(Buffer: NativeInt); overload; override;
  {$ENDIF}
    procedure ClearCalcFields(Buffer: TRecordBuffer); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

    function GetObjectFieldDefName(Parent: TFieldDef; Index: integer; ObjType: TObjectType):string; virtual;
    function GetFielDefSize(FieldType: TFieldType; FieldLength: Integer): Integer; virtual;
  {$IFNDEF FPC}
    procedure GetObjectTypeNames(Fields: TFields);
  {$ENDIF}
    function GetFieldType(DataType: word): TFieldType; overload; virtual;
    function GetFieldType(FieldDesc: TFieldDesc; out FieldSize, FieldLength, FieldScale: Integer): TFieldType; overload; virtual;

    function InternalDataConvert(Field: TField; Source, Dest: IntPtr; ToNative: Boolean): boolean; virtual;
  {$IFDEF FPC}
  public
  {$ENDIF}
  {$IFDEF VER17P}
    procedure SetFieldData(Field: TField; Buffer: TValueBuffer);  overload; override;
    procedure SetFieldData(Field: TField; Buffer: TValueBuffer; NativeFormat: Boolean); overload; override;
    procedure DataConvert(Field: TField; Source: TValueBuffer; {$IFDEF VER18P}var{$ENDIF} Dest: TValueBuffer; ToNative: Boolean); overload; override;
  {$ENDIF}
    procedure SetFieldData(Field: TField; Buffer: IntPtr);  overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    procedure SetFieldData(Field: TField; Buffer: IntPtr; NativeFormat: Boolean); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
  {$IFNDEF NEXTGEN}
    procedure DataConvert(Field: TField; Source, Dest: IntPtr; ToNative: Boolean); overload; override;
  {$ENDIF}

  protected
  {$IFDEF NEXTGEN}
    procedure DestroyFields; override;
  {$ENDIF}
    function GetSparseArrays: boolean;
    procedure SetSparseArrays(Value: boolean);
    procedure CheckFieldCompatibility(Field: TField; FieldDef: TFieldDef); {$IFNDEF FPC}override{$ELSE}virtual{$ENDIF};
    function GetFieldClass(FieldType: TFieldType): TFieldClass; overload; override;
    function GetFieldClass(FieldType: TFieldType; DataType: Word): TFieldClass; reintroduce; overload; virtual;
  {$IFDEF VER12P}
    function GetFieldClass(FieldDef: TFieldDef): TFieldClass; overload; override;
  {$ENDIF}

  { Buffer/Record Management }
  {$IFDEF NEXTGEN}
    function AllocRecBuf: TRecBuf; override;
    procedure FreeRecBuf(var Buffer: TRecBuf); override;
  {$ENDIF}
    function AllocRecordBuffer: TRecordBuffer; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    procedure FreeRecordBuffer(var Buffer: TRecordBuffer); {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

    procedure CopyRecordBuffer(SrcBuffer: IntPtr; DstBuffer: IntPtr);

  {$IFDEF VER18P}
    procedure InitRecord(Buffer: TRecBuf); overload; override;
  {$ENDIF}
    procedure InitRecord(Buffer: TRecordBuffer); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
  {$IFDEF VER18P}
    procedure InternalInitRecord(Buffer: TRecBuf); overload; override;
  {$ENDIF}
    procedure InternalInitRecord(Buffer: TRecordBuffer); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

    function GetOldRecord: TRecordBuffer;
    function GetOldRecBuf: TRecordBuffer;
    function GetNewRecBuf: TRecordBuffer;
    function GetActiveRecBuf(out RecBuf: TRecordBuffer): boolean;
  {$IFDEF VER18P}
    function GetRecord(Buffer: TRecBuf; GetMode: TGetMode; DoCheck: Boolean): TGetResult; overload; override;
  {$ENDIF}
    function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: boolean): TGetResult; overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

  {$IFNDEF FPC}
    procedure BlockReadNext; override;
    procedure SetBlockReadSize(Value: integer); override;
  {$ENDIF}
  {$IFDEF FPC}
    procedure SetBufListSize(Value: Longint); override;
  {$ENDIF}

    procedure FreeRefBuffers;
    procedure FreeRefComplexFields(Buffer: TRecordBuffer; WithBlob: boolean = True);

  { Bookmarks }
  {$IFDEF VER17P}
    procedure GetBookmarkData(Buffer: {$IFDEF VER18P}TRecBuf{$ELSE}TRecordBuffer{$ENDIF}; Bookmark: TBookmark); overload; override;
    procedure SetBookmarkData(Buffer: {$IFDEF VER18P}TRecBuf{$ELSE}TRecordBuffer{$ENDIF}; Bookmark: TBookmark); overload; override;
    procedure InternalGotoBookmark(Bookmark: TBookmark); overload; override;
  {$ENDIF}
  {$IFDEF VER18P}
    function GetBookmarkFlag(Buffer: TRecBuf): TBookmarkFlag; override;
    procedure SetBookmarkFlag(Buffer: TRecBuf; Value: TBookmarkFlag); override;
  {$ENDIF}
    procedure GetBookmarkData(Buffer: TRecordBuffer; {$IFDEF CLR}var Bookmark: TBookmark{$ELSE}Bookmark: Pointer{$ENDIF}); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    procedure SetBookmarkData(Buffer: TRecordBuffer; {$IFDEF CLR}const Bookmark: TBookmark{$ELSE}Bookmark: Pointer{$ENDIF}); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    procedure InternalGotoBookmark({$IFDEF CLR}const Bookmark: TBookmark{$ELSE}Bookmark: Pointer{$ENDIF}); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

  { Navigation }
    procedure InternalFirst; override;
    procedure InternalLast; override;

  {$IFDEF VER18P}
    procedure InternalSetToRecord(Buffer: TRecBuf); overload; override;
  {$ENDIF}
    procedure InternalSetToRecord(Buffer: TRecordBuffer); overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

  { Editing }
  {$IFDEF VER17P}
    procedure InternalAddRecord(Buffer: {$IFDEF VER18P}TRecBuf{$ELSE}TRecordBuffer{$ENDIF}; Append: boolean); overload; override;
  {$ENDIF}
    procedure InternalAddRecord(Buffer: IntPtr; Append: boolean); overload;{$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    procedure InternalInsert; override;
    procedure InternalDelete; override;
    procedure InternalEdit; override;

    procedure InternalPost; override;
    procedure InternalCancel; override;

    procedure InternalDeferredPost; virtual;

    procedure SetDefaultExpressionValues; virtual;
    procedure DoOnNewRecord; override;

    procedure DoPerformAppend;
    procedure DoPerformDelete;
    procedure DoPerformUpdate;
    procedure DoApplyRecord(UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean);
    procedure DoCacheChanged;
    procedure DoCacheApplied;
    procedure DoCacheCanceled;

    procedure DoGetCachedFields;
    procedure DoGetCachedBuffer(Buffer: IntPtr; Source: IntPtr = nil);

  { Filter/Find/Locate }
    procedure ActivateFilters; virtual;
    procedure DeactivateFilters; virtual;
    function RecordFilter(RecBuf: IntPtr): boolean;
    procedure SetFilterData(const Text: string; Options:TFilterOptions);

    procedure SetFiltered(Value: boolean); override;
    procedure SetFilterOptions(Value: TFilterOptions); override;
    procedure SetFilterText(const Value: string); override;
    procedure SetOnFilterRecord(const Value: TFilterRecordEvent); override;

    procedure CopyFieldValue(const Value: variant; out ValuePtr: IntPtr; out ValueType: Word;
      FieldType: Word; UseFieldType: boolean = True); virtual;

    //Renamed LocateRecord (CBuilder5 bug - overloaded methods in different sections):
    function InternalLocateRecord(KeyFields: TDAList; KeyValues: variant;
      Options: TLocateExOptions; SavePos: boolean): boolean; overload;

    function LocateRecord(const KeyFields: string; const KeyValues: variant;
      Options: TLocateExOptions; SavePos: boolean): boolean; overload;
    function LocateRecord(const KeyFields: array of TField; const KeyValues: variant;
      Options: TLocateExOptions; SavePos: boolean): boolean; overload;
    function FindRecord(Restart, GoForward: boolean): boolean; override;

    function RecordFilterRange(RecBuf: IntPtr): boolean;

  { Master/Detail }
  {$IFDEF VER17P}
    function AddFieldToList(const FieldName: string; DataSet: TDataSet; List: TList<TField>): boolean;
  {$ELSE}
    function AddFieldToList(const FieldName: string; DataSet: TDataSet; List: TList): boolean;
  {$ENDIF}
    function GetDataSource: TDataSource; override;
    function IsMasterDatasetActive: boolean;
    function IsConnectedToMaster: boolean;
    function SetLocalMDLinks: boolean; virtual;
    function MDLinksRefreshed: boolean; virtual;
    procedure MasterRecordChanged;
    procedure RefreshDetail(Sender: TObject); virtual;
    function LocalDetailFilter(RecBuf: IntPtr): boolean;
    function UseLocalMasterDetailFilter: boolean; virtual;
    procedure SetLocalDetailFilter;
    procedure MDPropertiesChanged; virtual;

    property MasterSource: TDataSource read GetDataSource write SetMasterSource;
    property MasterFields: string read FMasterFields write SetMasterFields;
    property DetailFields: string read FDetailFields write SetDetailFields;
    property DetailDelay: integer read FDetailDelay write SetDetailDelay;

  { CachedUpdates }

    function InternalGetUpdateResult: TUpdateRecAction;
    procedure CheckCachedUpdateMode;

  { Blobs }
    //Renamed GetBlob (CBuilder5 bug - overloaded methods in different sections):
    function InternalGetBlob(FieldDesc: TFieldDesc): TBlob;
    function InternalSetBlob(FieldDesc: TFieldDesc; Blob: TBlob): boolean;
    function SetBlob(Field: TField; Blob: TBlob): boolean;
    procedure CloseBlob(Field: TField); override;

  { Misc }
    function GetRecordCount: integer; override;
    function GetRecordSize: word; override;

    function GetRecNo: integer; override;
    procedure SetRecNo(Value: integer); override;

    procedure InternalHandleException; override;

    procedure AssignTo(Dest: TPersistent); override;

  {$IFDEF CLR}
    procedure DataEvent(Event: TDataEvent; Info: TObject); override;
  {$ELSE} {$IFDEF FPC}
    procedure DataEvent(Event: TDataEvent; Info: PtrInt); override;
  {$ELSE} {$IFDEF VER16P}
    procedure DataEvent(Event: TDataEvent; Info: NativeInt); override;
  {$ELSE}
    procedure DataEvent(Event: TDataEvent; Info: Longint); override;
  {$ENDIF} {$ENDIF} {$ENDIF}

  {$IFNDEF FPC}
    function GetStateFieldValue(State: TDataSetState; Field: TField): Variant; override;
  {$ENDIF}

    function GetMaxFieldCount: integer; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Prepare; virtual;
    procedure UnPrepare; virtual;
    procedure CheckPrepared;

  {$IFDEF VER17P}
    procedure GetDetailLinkFields(MasterFields, DetailFields: TList<TField>); override;
  {$ELSE}{$IFDEF CLR}
    procedure GetDetailLinkFields(MasterFields, DetailFields: TObjectList); override;
  {$ELSE}
    procedure GetDetailLinkFields(MasterFields, DetailFields: TList); {$IFNDEF FPC}override;{$ELSE}virtual;{$ENDIF}
  {$ENDIF}{$ENDIF}

  { Fields }
    function GetFieldDescNo(Field: TField): integer;
    function GetFieldDesc(const Field: TField): TFieldDesc; overload; virtual;
    function GetFieldDesc(const FieldName: string): TFieldDesc; overload;
    function GetFieldDesc(const FieldNo: integer): TFieldDesc; overload; virtual;

    //function Translate(const Src: string; var Dest: string; ToOem: boolean): integer; override;

  {$IFDEF VER17P}
    function GetFieldData(FieldNo: integer; {$IFDEF VER18P}var{$ENDIF} Buffer: TValueBuffer): boolean; overload; override;
    function GetFieldData(Field: TField; {$IFDEF VER18P}var{$ENDIF} Buffer: TValueBuffer): boolean; overload; override;
    function GetFieldData(Field: TField; {$IFDEF VER18P}var{$ENDIF} Buffer: TValueBuffer; NativeFormat: Boolean): Boolean; overload; override;
  {$ENDIF}
    {$IFNDEF FPC}
    function GetFieldData(FieldNo: integer; Buffer: IntPtr): boolean; overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    {$ENDIF}
    function GetFieldData(Field: TField; Buffer: IntPtr): boolean; overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}
    function GetFieldData(Field: TField; Buffer: IntPtr; NativeFormat: Boolean): Boolean; overload; {$IFNDEF NEXTGEN}override;{$ELSE}virtual;{$ENDIF}

    function GetBlob(const FieldName: string): TBlob; overload;
    function GetBlob(Field: TField): TBlob; overload;

  { Edit }
    procedure Post; override;
    procedure Cancel; override;
    procedure DeferredPost;

  { Bookmarks }
  {$IFDEF CLR} // TDataSet bug
    function GetBookmark: TBookmark; override;
    procedure FreeBookmark(var Bookmark: TBookmark); override;
  {$ENDIF}
    function BookmarkValid({$IFDEF CLR}const{$ENDIF} Bookmark: TBookmark): boolean; override;
    function CompareBookmarks({$IFDEF CLR}const{$ENDIF} Bookmark1, Bookmark2: TBookmark): integer; override;

    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;

    function Locate(const KeyFields: string; const KeyValues: variant;
      Options: TLocateOptions): boolean; overload; override;
    function Locate(const KeyFields: array of TField; const KeyValues: variant;
      Options: TLocateOptions): boolean; reintroduce; overload;
    function LocateEx(const KeyFields: string; const KeyValues: variant;
      Options: TLocateExOptions): boolean; overload;
    function LocateEx(const KeyFields: array of TField; const KeyValues: variant;
      Options: TLocateExOptions): boolean; overload;
    function Lookup(const KeyFields: string; const KeyValues: variant;
      const ResultFields: string): variant; override;

  { CachedUpdates }
    function UpdateStatus: TUpdateStatus; override;
    function UpdateResult: TUpdateAction;
    procedure ApplyUpdates; overload; virtual;
    procedure ApplyUpdates(const UpdateRecKinds: TUpdateRecKinds); overload; virtual;
    procedure CommitUpdates;
    procedure CancelUpdates;
    procedure RestoreUpdates;
    procedure RevertRecord;

  { XML }
    procedure SaveToXML(Destination: TStream); overload;
    procedure SaveToXML(const FileName: string); overload;

  { Range }
    procedure SetRange(const StartValues, EndValues: array of const; StartExlusive: Boolean = False; EndExclusive: Boolean = False);
    procedure ApplyRange;
    procedure CancelRange;
    procedure SetRangeStart;
    procedure SetRangeEnd;
    procedure EditRangeStart;
    procedure EditRangeEnd;

    function IsSequenced: boolean; override;
    function InCacheProcessing: boolean;

    property Prepared: boolean read GetPrepared write SetPrepared;
    property CachedUpdates: boolean read FCachedUpdates write SetCachedUpdates default False;
    property UpdatesPending: boolean read GetUpdatesPending;
    property LocalUpdate: boolean read FLocalUpdate write SetLocalUpdate default False;
    property UpdateRecordTypes: TUpdateRecordTypes read GetUpdateRecordTypes write SetUpdateRecordTypes default [rtModified, rtInserted, rtUnmodified];
    property SparseArrays: boolean read GetSparseArrays write SetSparseArrays;
    property Ranged: Boolean read FRanged;
    property KeyExclusive: Boolean read GetKeyExclusive write SetKeyExclusive;

  // obsolete
    property LocalConstraints: boolean read FLocalConstraints write FLocalConstraints default True;

    property OnUpdateError: TUpdateErrorEvent read FOnUpdateError write FOnUpdateError;
    property OnUpdateRecord: TUpdateRecordEvent read FOnUpdateRecord write FOnUpdateRecord;
    property IndexFieldNames: string read FIndexFieldNames write SetIndexFieldNames;
  end;

{ TBlobStream }

  TBlobStream = class(TStream)
  protected
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FField: TBlobField;
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    FFieldDesc: TFieldDesc;
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    {$IFNDEF EntityDAC}
    FDataSet: TMemDataSet;
    {$ELSE}
    FDataSet: TEDMemDataSet;
    {$ENDIF}
    FBuffer: TRecordBuffer;
    FMode: TBlobStreamMode;
    FOpened: boolean;
    FModified: boolean;
    FPosition: longint;
    function GetBlobSize: Longint;

  protected
  {$IFDEF CLR}
    procedure SetSize(NewSize: Int64); override;
  {$ELSE}
    procedure SetSize(NewSize: Longint); override;
  {$ENDIF}
  public
    constructor Create(Field: TBlobField; Mode: TBlobStreamMode);
    destructor Destroy; override;
  {$IFDEF CLR}
    function Read(var Buffer: TBytes; Offset, Count: Longint): Longint; override;
    function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
  {$ELSE}
    function Read(var Buffer; Count: longint): longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;
  {$ENDIF}
    procedure Truncate;
  end;

  TMemDSUtils = class
  public
    class function SetBlob(Obj: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}; Field: TField; Blob: TBlob): boolean;
    class function GetBlob(Obj: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}; FieldDesc: TFieldDesc): TBlob;
  end;

  function ChangeDecimalSeparator(const Value: string; OldSeparator, NewSeparator: string): string;

var
  SendDataSetChangeEventAfterOpen: boolean = True;  ///CR-CRC23525  remove at 18.11.07
  DoNotRaiseExcetionOnUaFail: boolean = False;      ///CR-D23937  remove at 12.12.07
  DefaultExpressionOldBehavior: boolean = False;
  LocateExOldBehavior: boolean = False;
  RefreshParamsOnInsert: boolean = False; // Old behavior

implementation

uses
  Math,
{$IFDEF VER6P}
  DateUtils, FmtBcd,
{$ENDIF}
{$IFNDEF FPC}
  DBConsts,
{$ELSE}
  DBConst,
{$ENDIF}
{$IFNDEF EntityDAC}
  DAConsts;
{$ELSE}
  EntityDAC.Common.Consts;
{$ENDIF}

const
  DataTypeMap: array [TFieldType] of word = (
    // ftUnknown, ftString, ftSmallint, ftInteger, ftWord
    dtUnknown, dtString, dtInt16, dtInteger, dtUInt16,
    // ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime,
    dtBoolean, dtFloat, dtCurrency, dtBCD, dtDate, dtTime, dtDateTime,
    // ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo,
    dtBytes, dtVarBytes, dtInteger, dtBlob, dtMemo, dtBlob, dtMemo,
    // ftParadoxOle, ftDBaseOle, ftTypedBinary, ftCursor, ftFixedChar, ftWideString,
    0, 0, 0, dtCursor, dtString, dtWideString,
    // ftLargeint, ftADT, ftArray, ftReference, ftDataSet, ftOraBlob, ftOraClob,
    dtInt64, dtObject, dtArray, dtReference, dtTable, 0, 0,
    // ftVariant, ftInterface, ftIDispatch, ftGuid
    dtVariant, 0, 0, dtGuid
  {$IFDEF VER6P}
    // ftTimeStamp, ftFMTBcd
    {$IFNDEF FPC}, dtSQLTimeStamp, dtFmtBCD{$ELSE}, 0, 0{$ENDIF}
  {$IFDEF FPC}
    // ftFixedWideChar, ftWideMemo
    , dtWideString, dtWideMemo
  {$ENDIF}
  {$IFDEF VER10P}
    // ftFixedWideChar, ftWideMemo, ftOraTimeStamp, ftOraInterval
    , dtWideString, dtWideMemo, 0, 0
  {$IFDEF VER12P}
    // ftLongWord, ftShortint, ftByte, ftExtended, ftConnection, ftParams, ftStream
    , dtUInt32, dtInt8, dtUInt8, dtFloat, 0, 0, 0
  {$IFDEF VER14P}
    // ftTimeStampOffset, ftObject, ftSingle
    , 0, 0, dtSingle
  {$ENDIF}
  {$ENDIF}
  {$ENDIF}
  {$ENDIF}
    );

{$IFDEF CLR}
{ PRecInfo }

function PRecInfo.GetRecordNumber: longint;
begin
  Result := Marshal.ReadInt32(Ptr);
end;

procedure PRecInfo.SetRecordNumber(const Value: longint);
begin
  Marshal.WriteInt32(Ptr, Value);
end;

function PRecInfo.GetUpdateStatus: TUpdateStatus;
begin
  Result := TUpdateStatus(Marshal.ReadByte(Ptr, sizeof(longint)));
end;

procedure PRecInfo.SetUpdateStatus(const Value: TUpdateStatus);
begin
  Marshal.WriteByte(Ptr, sizeof(longint), byte(Value));
end;

function PRecInfo.GetBookmarkFlag: TBookmarkFlag;
begin
  Result := TBookmarkFlag(Marshal.ReadByte(Ptr, sizeof(integer) + sizeof(TUpdateStatus)));
end;

procedure PRecInfo.SetBookmarkFlag(const Value: TBookmarkFlag);
begin
  Marshal.WriteByte(Ptr, sizeof(integer) + sizeof(TUpdateStatus), byte(Value));
end;

function PRecInfo.GetRefComplexFields: boolean;
begin
  Result := boolean(Marshal.ReadByte(Ptr, sizeof(integer) + sizeof(TUpdateStatus)
   + sizeof(TBookmarkFlag)));
end;

procedure PRecInfo.SetRefComplexFields(const Value: boolean);
begin
  Marshal.WriteByte(Ptr, sizeof(integer) + sizeof(TUpdateStatus)
   + sizeof(TBookmarkFlag), byte(Value));
end;

function PRecInfo.GetKeyExclusive: Boolean;
begin
  Result := Boolean(Marshal.ReadByte(Ptr, sizeof(integer) + sizeof(TUpdateStatus)
   + sizeof(TBookmarkFlag) + sizeof(Boolean)));
end;

procedure PRecInfo.SetKeyExclusive(const Value: Boolean);
begin
  Marshal.WriteByte(Ptr, sizeof(integer) + sizeof(TUpdateStatus)
   + sizeof(TBookmarkFlag) + sizeof(Boolean), byte(Value));
end;

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

{$ENDIF}

{ TFieldTypeMap }

class function TFieldTypeMap.GetFieldType(DataType: Word): TFieldType;
begin
  case DataType of
    dtUnknown:
      Result := ftUnknown;
    dtString:
      Result := ftString;
    dtWideString:
      Result := ftWideString;
    dtUInt8:
    {$IFDEF VER12P}
      Result := ftByte;
    {$ELSE}
      Result := ftSmallint;
    {$ENDIF}
    dtWord:
      Result := ftWord;
    dtUInt32:
    {$IFDEF VER12P}
      Result := ftLongWord;
    {$ELSE}
      Result := ftLargeInt;
    {$ENDIF}
    dtInt8:
    {$IFDEF VER12P}
      Result := ftShortint;
    {$ELSE}
      Result := ftSmallint;
    {$ENDIF}
    dtInt16:
      Result := ftSmallint;
    dtInteger:
      Result := ftInteger;
    dtInt64:
      Result := ftLargeInt;
    dtUInt64:
      Result := ftLargeInt;
    dtFloat:
      Result := ftFloat;
    dtSingle:
    {$IFDEF VER14P}
      Result := ftSingle;
    {$ELSE}
      Result := ftFloat;
    {$ENDIF}
  {$IFDEF VER14P}
    dtExtended:
      Result := ftExtended;
  {$ENDIF}
    dtDate:
      Result := ftDate;
    dtTime:
      Result := ftTime;
    dtDateTime:
      Result := ftDateTime;
    dtMemo:
      Result := ftMemo;
    dtWideMemo:
      Result := {$IFDEF VER10P}ftWideMemo;{$ELSE}ftMemo;{$ENDIF}
    dtBlob:
      Result := ftBlob;
    dtObject:
      Result := ftADT;
    dtReference:
      Result := ftReference;
    dtArray:
      Result := ftArray;
    dtTable:
      Result := ftDataSet;
    dtCursor:
      Result := ftCursor;
    dtBoolean:
      Result := ftBoolean;
    dtVariant:
      Result := ftVariant;
    dtExtString:
      Result := ftString;
    dtExtWideString:
      Result := ftWideString;
    dtBytes:
      Result := ftBytes;
    dtVarBytes:
      Result := ftVarBytes;
    dtExtVarBytes:
      Result := ftVarBytes;
    dtBCD:
      Result := ftBCD;
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    dtFmtBCD:
      Result := ftFMTBcd;
    dtSQLTimeStamp:
      Result := ftTimeStamp;
  {$ENDIF}
  {$ENDIF}
    dtGuid:
      Result := ftGuid;
    dtCurrency:
      Result := ftCurrency;
  else
    Assert(False, SUnknownDataType);
    Result := ftUnknown;
  end;
end;

class function TFieldTypeMap.GetDataType(FieldType: TFieldType): integer;
begin
{$IFNDEF VER10P}
{$IFNDEF FPC}
  if Integer(FieldType) = ftFixedWideChar then
    Result := dtWideString
  else
{$ENDIF}
{$ENDIF}
    Result := DataTypeMap[FieldType];
end;

{ TDADetailDataLink }

constructor TDADetailDataLink.Create(DataSet: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF});
begin
  inherited Create;

  FDataSet := DataSet;
end;

procedure TDADetailDataLink.ActiveChanged;
begin
  if (FDataSet <> nil) and
      FDataSet.Active and
      not (csDestroying in FDataSet.ComponentState) and
      Active
  then
    FDataSet.MasterRecordChanged;
end;

procedure TDADetailDataLink.RecordChanged(Field: TField);
begin
  if ((Field = nil) or (DataSet.Fields.IndexOf(Field) >= 0)) and
    FDataSet.Active and
    not ((Field <> nil) and ((FDataSet.State in [dsEdit, dsInsert]) or
    ((DataSet.State in [dsInsert]) and not RefreshParamsOnInsert) )) then
      FDataSet.MasterRecordChanged;
end;

procedure TDADetailDataLink.CheckBrowseMode;
begin
  if FDataSet.Active and
    not((DataSet.State in [dsInsert]) and (FDataSet.State in [dsEdit,dsInsert]))
  then // Prevent post detail before post master
    FDataSet.CheckBrowseMode;
end;

function TDADetailDataLink.GetDetailDataSet: TDataSet;
begin
  Result := FDataSet;
end;

{ TMemDataSet }

constructor {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FUpdateRecordTypes := [rtModified, rtInserted, rtUnmodified];
  FLocalConstraints := True;
  FCreateCalcFieldDescs := True;

  FDataLink := TDADetailDataLink.Create(Self);
end;

destructor {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Destroy;
begin
{$IFDEF MSWINDOWS}
  FDetailRefreshTimer.Free;
{$ENDIF}

  FDataLink.Free;

  UnPrepare;

  FreeIRecordSet;
  SetIRecordSet(nil);
  SetDataSetService(nil);

  inherited;
end;

{$IFDEF CLR}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.BookmarkAvailable: Boolean;
begin
  Result := inherited BookmarkAvailable;
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetModified(Value: Boolean);
begin
  inherited;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetTempState(const Value: TDataSetState): TDataSetState;
begin
  Result := inherited SetTempState(Value);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.RestoreState(const Value: TDataSetState);
begin
  inherited;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoBeforeRefresh;
begin
  inherited;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoAfterRefresh;
begin
  inherited;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CreateIRecordSet;
begin
  SetIRecordSet(TMemData.Create);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeIRecordSet;
begin
  Data.Free;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetIRecordSet(Value: TData);
begin
  Data := Value;

  if Data <> nil then begin
    Data.CachedUpdates := FCachedUpdates;
    Data.LocalUpdate := FLocalUpdate;
    Data.SparseArrays := SparseArrays;
    Data.FilterItemTypes := ConvertUpdateRecordTypes(FUpdateRecordTypes);

    if Data is TMemData then
      TMemData(Data).SetIndexFieldNames(FIndexFieldNames);

    Data.OnAppend := DoPerformAppend;
    Data.OnDelete := DoPerformDelete;
    Data.OnUpdate := DoPerformUpdate;
    Data.OnApplyRecord := DoApplyRecord;
    Data.OnCacheChanged := DoCacheChanged;
    Data.OnCacheApplied := DoCacheApplied;
    Data.OnCacheCanceled := DoCacheCanceled;
    if FCreateCalcFieldDescs then begin
      Data.OnGetCachedFields := DoGetCachedFields;
      Data.OnGetCachedBuffer := DoGetCachedBuffer;
    end;
  end;
  SetLocalDetailFilter;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetDataSetServiceClass: TDataSetServiceClass;
begin
  Result := TDataSetService;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CreateDataSetService;
begin
  SetDataSetService(GetDataSetServiceClass.Create(Self));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeDataSetService;
begin
  FDataSetService.Free;
  FDataSetService := nil;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetDataSetService(Value: TDataSetService);
begin
  FreeDataSetService;

  FDataSetService := Value;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CheckDataSetService;
begin
  if not (FDataSetService is GetDataSetServiceClass) then begin
    FreeDataSetService;
    CreateDataSetService;
  end;
end;

{ Open/Close DataSet }

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Prepare;
begin
  if (not Prepared) and (Data <> nil) then begin
    Data.Prepare;

    if FDataSetService <> nil then
      FDataSetService.PreInitCursor;
    CreateFieldDefs;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.UnPrepare;
begin
  if Active then
    Close;

  if Data <> nil then
    Data.UnPrepare;

  if not (csDestroying in ComponentState) then // This line may be called after destroing FieldDefs. For details see TMemDataSet.Destroy
    FieldDefs.Updated := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CheckPrepared;
begin
  if not Prepared then
    DatabaseError(SDataSetIsNotPrepared);
end;

{$IFDEF FPC}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CreateFields;
const
  ObjectFieldTypes = [ftADT, ftArray, ftReference, ftDataSet];
var
  Field: TField;
  i: longint;
begin
  for i := 0 to FieldDefs.Count - 1 do
    with FieldDefs[i] do begin
      if (DataType <> ftUnknown) and not (DataType in ObjectFieldTypes) and
        not ((faHiddenCol in Attributes) and not FieldDefs.HiddenFields) then
        Field := CreateField(Self);

      if ((faFixed in Attributes) or (DataType = ftFixedChar)) and (Field <> nil) and (Field is TStringField) then
        TStringField(Field).FixedChar := True;
    end;
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalOpen;
var
  i: integer;
  Field: TField;
  FieldDesc: TFieldDesc;
begin
  Assert(Data <> nil);
  Data.Open;

  // FDataSetService is not set in TVirtualTable and TMetaData
  if FDataSetService <> nil then
    FDataSetService.PreInitCursor;

  CreateFieldDefs;

  // Update FieldDefs once to avoid multiple Update calls when working with FieldDefsList
  // (Perfomance optimization)
//  FieldDefs.Updated := False;
//  FieldDefs.Update;

{$IFDEF VER20P}
  if not (lcPersistent in Fields.LifeCycles) then
{$ELSE}
  if DefaultFields then
{$ENDIF}
    CreateFields
  else // Setting actual size
    for i := 0 to FieldDefs.Count - 1 do
      if FieldDefs[i].DataType = ftString then begin
        Field := FindField(FieldDefs[i].Name);
        if (Field <> nil) and (Field.FieldKind = fkData) then begin
          CheckFieldCompatibility(Field, FieldDefs[i]);
          Field.Size := FieldDefs[i].Size;
        end;
      end;

  // if Fielddesc is found in the dataset then it can be used in queries for data update
  for i := 0 to Data.Fields.Count - 1 do begin
    FieldDesc := Data.Fields[i];
    if FieldDesc.ParentField = nil then
      FieldDesc.Updateable := FindField(FieldDesc.Name) <> nil
    else
      FieldDesc.Updateable := FindField(FieldDesc.ActualName) <> nil;
  end;

  // Set number specific
  if FNumberRange and (FDataSetService <> nil) then
    for i := 0 to FieldDefs.Count - 1 do
      FDataSetService.SetNumberRange(FieldDefs[i]);

  BindFields(True);

  if (Data is TMemData) and (Data.Fields.Count > 0) and (Data.Fields[Data.Fields.Count - 1].FieldDescKind = fdkCached) then
    TMemData(Data).UpdateCachedBuffer(nil, nil);

{$IFNDEF FPC}
  if ObjectView then
    GetObjectTypeNames(Fields);
{$ENDIF}

  if (Data is TMemData) and (Length(TMemData(Data).IndexFields) > 0) then
    TMemData(Data).SortItems;

  BookmarkSize := SizeOf(TRecBookmark);

  //FBlobCacheOfs := Data.RecordSize + CalcFieldsSize;
  if not FCreateCalcFieldDescs then
    FRecInfoOfs := Data.RecordSize + CalcFieldsSize
  else
    if FCacheCalcFields then
      FRecInfoOfs := Data.RecordSize
    else
      FRecInfoOfs := Data.RecordSize + Data.CalcRecordSize; //FBlobCacheOfs + BlobFieldCount * SizeOf(Pointer);

  FBookmarkOfs := FRecInfoOfs + SizeOf(TRecInfo);
  FRecBufSize := FBookmarkOfs + BookmarkSize;

{$IFNDEF CLR}
{$IFNDEF VER10P}
  if not FCreateCalcFieldDescs then begin
    FWideStringOfs := FRecBufSize;

    if CalcFieldsSize > 0 then
      for i := 0 to Fields.Count - 1 do begin
        Field := Fields[i];
        if (Field.DataType = ftWideString) and (Field.FieldKind in [fkCalculated, fkLookUp]) then
          FRecBufSize := FRecBufSize + (Field.Size + 1) * sizeof(WideChar);
      end;
  end;
{$ENDIF}
{$ENDIF}

  if Filtered then begin
    ActivateFilters;
    Data.FilterUpdated;
  end;
  if IsMasterDatasetActive and UseLocalMasterDetailFilter then begin
    MDLinksRefreshed;
    Data.FilterUpdated;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.IsCursorOpen: boolean;
begin
  if Data <> nil then
    Result := Data.Active
  else
    Result := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalClose;
var
  i: integer;
begin
  try
    if Data <> nil then begin
      BindFields(False);
    {$IFDEF VER20P}
      if not (lcPersistent in Fields.LifeCycles) then
    {$ELSE}
      if DefaultFields then
    {$ENDIF}
        DestroyFields;

      Data.Close;
    end;
  finally
    for i := 0 to Length(FLocalMDLinks) - 1 do
      if not FLocalMDLinks[i].NativeBuffer then
        Marshal.FreeHGlobal(FLocalMDLinks[i].Buffer);
    SetLength(FLocalMDLinks, 0);
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.OpenCursor(InfoQuery: boolean);
begin
  inherited;

  if not InfoQuery then begin
    if FOldRecBuf <> nil then begin
      FreeRecordBuffer(FOldRecBuf);
      FOldRecBuf := nil;
    end;
    FOldRecBuf := AllocRecordBuffer;
    if FRangeStartBuffer <> nil then begin
      FreeRecordBuffer(FRangeStartBuffer);
      FRangeStartBuffer := nil;
    end;
    FRangeStartBuffer := AllocRecordBuffer;
    if FRangeEndBuffer <> nil then begin
      FreeRecordBuffer(FRangeEndBuffer);
      FRangeEndBuffer := nil;
    end;
    FRangeEndBuffer := AllocRecordBuffer;
  end;
  // DataEvent(deDataSetChange, 0); // Notify nested datasets // DEBUG
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CloseCursor;
begin
{$IFDEF MSWINDOWS}
  if FDetailRefreshTimer <> nil then
    FDetailRefreshTimer.Enabled := False;
{$ENDIF}

  if Data <> nil then begin // Data can be nil if an exception occurs in OpenCursor
    // free complex fields if call Close in dsInsert or dsEdit mode
    // TDataSet.Close doesn't call Cancel
    if Data.HasComplexFields then begin
      if FInInserting or FInEditing then begin
        FreeRefComplexFields(IntPtr(ActiveBuffer), FInInserting); // on Editing Blobs isn't created, only on Inserting
        Data.FreeComplexFields(IntPtr(ActiveBuffer), FInInserting);
      end;

      FInInserting := False;
      FInEditing := False;
    end;

    if FOldRecBuf <> nil then begin
      FreeRecordBuffer(FOldRecBuf);
      FOldRecBuf := nil;
    end;
    if FRangeStartBuffer <> nil then begin
      FreeRecordBuffer(FRangeStartBuffer);
      FRangeStartBuffer := nil;
    end;
    if FRangeEndBuffer <> nil then begin
      FreeRecordBuffer(FRangeEndBuffer);
      FRangeEndBuffer := nil;
    end;
  end;

  try
    inherited;
  finally
    FParentDataSet := nil;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataReopen;
begin
  Assert(Data <> nil);
  Data.Reopen;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalRefresh;
begin
  FreeRefBuffers;
  //ClearBuffers; /// CR11512
  DataReopen;
  if (Data is TMemData) and (Length(TMemData(Data).IndexFields) > 0) then
    TMemData(Data).SortItems;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoAfterOpen;
{$IFNDEF FPC}
var
  i: integer;
{$ENDIF}
begin
  inherited;

  if SendDataSetChangeEventAfterOpen then
  {$IFDEF CLR}
    DataEvent(deDataSetChange, TObject(Integer(0)));
  {$ELSE}
    DataEvent(deDataSetChange, 0);
  {$ENDIF}

{$IFNDEF FPC}
  for i := 0 to NestedDataSets.Count - 1 do
    with TDataSet(NestedDataSets[i]) do
      if Active then
        DataEvent(deParentScroll, {$IFDEF CLR}nil{$ELSE}0{$ENDIF});
{$ENDIF}
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeRefBuffers;
var
  i: integer;
begin
  if FNeedAddRef then
    for i := 0 to BufferCount do
      FreeRefComplexFields(IntPtr(Buffers[i]));
  FreeRefComplexFields(IntPtr(TempBuffer));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeRefComplexFields(Buffer: TRecordBuffer; WithBlob: boolean = True);
var
  RecInfo: PRecInfo;
begin
{$IFNDEF FPC}
  Assert(Data <> nil);
{$ELSE}
  if Data = nil then
    Exit;
{$ENDIF}

  RecInfo := PRecInfo(PtrOffset(Buffer, FRecInfoOfs));
  if {$IFDEF FPC}(Buffer <> nil) and (RecInfo <> nil) and {$ENDIF} RecInfo.RefComplexFields then begin
    Data.ReleaseRef(Buffer);
    Data.FreeComplexFields(Buffer, WithBlob);
    RecInfo.RefComplexFields := False;
  end;
end;

{ Field Management }

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldTypeMapClass: TFieldTypeMapClass;
begin
  Result := TFieldTypeMap;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalInitFieldDefs;
var
  CheckDefs: boolean;
  i: integer;
  OldFieldNames: array of string;
  OldFieldCount: integer;

begin
  // can't CreateFieldDefs if FieldDefs.Update(InitFieldDefs)
  Assert(Data <> nil);
  if not Data.Active then begin
    OldFieldCount := Data.Fields.Count;
    CheckDefs := (FieldDefs <> nil) and not FieldDefs.Updated;
    if CheckDefs then begin
      SetLength(OldFieldNames, OldFieldCount);
      for i := 0 to OldFieldCount - 1 do
        OldFieldNames[i] := Data.Fields[i].Name;
    end;

    Data.ExplicitInitFields;

    if OldFieldCount <> Data.Fields.Count then
      FieldDefs.Updated := False
    else
      if CheckDefs then
        for i := 0 to OldFieldCount - 1 do
          if OldFieldNames[i] <> Data.Fields[i].Name then begin
            FieldDefs.Updated := False;
            Break;
          end;

    if FDataSetService <> nil then
      FDataSetService.PreInitCursor;

    CreateFieldDefs;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldType(DataType: word): TFieldType;
begin
  Result := GetFieldTypeMapClass.GetFieldType(DataType);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldType(FieldDesc: TFieldDesc; out FieldSize, FieldLength, FieldScale: Integer): TFieldType;
begin
  Result := GetFieldType(FieldDesc.DataType);
  FieldSize := GetFielDefSize(Result, FieldDesc.Length);
  FieldLength := FieldDesc.Length;
  FieldScale := FieldDesc.Scale;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetObjectFieldDefName(Parent: TFieldDef; Index: integer; ObjType: TObjectType): string;
{$IFNDEF FPC}
var
  ParentDef: TFieldDef;
{$ENDIF}
begin
{$IFNDEF FPC}
  if NeedComplexUpdateFieldDefList and (ObjType.DataType = dtArray) then begin
    Result := Parent.Name;

    // if array is property of object
    ParentDef := Parent.ParentDef;
    while (ParentDef <> nil) and (ParentDef.DataType <> ftArray) do begin
      Result := ParentDef.Name + '.' + Result;
      ParentDef := ParentDef.ParentDef;
    end;

    Result := Result + '[' + IntToStr(Index) + ']';
    //To correct DB.pas ADT name handling
  end
  else
{$ENDIF}
    Result := IntToStr(Index);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFielDefSize(FieldType: TFieldType; FieldLength: Integer): Integer;
begin
  Result := 0;
  case FieldType of
    ftString, ftWideString, ftGuid: begin
      Result := FieldLength;
      if Result = 0 then
        Result := 1;  // For SELECT NULL FROM ...
    end;
    ftBytes, ftVarBytes:
      Result := FieldLength;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.NeedCreateFieldDefs: boolean;
begin
  Result := not FieldDefs.Updated or Data.FieldListDependsOnParams;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CreateFieldDef(FieldDesc: TFieldDesc): TFieldDef;

  function GetFieldName(ParentFieldName, FieldName: string; Index: integer): string;
  var
    NewFieldName: string;
  begin
    if Index = 0 then
      NewFieldName := FieldName
    else
      NewFieldName := FieldName + '_' + IntToStr(Index);

    if (FieldDefs.IndexOf(ParentFieldName + '.' + NewFieldName) <> -1) then
      Result := GetFieldName(ParentFieldName, FieldName, Index + 1)
    else
      Result := NewFieldName;
  end;

  procedure CreateObjectFields(ObjType: TObjectType; Parent: TFieldDef);
  var
    i: integer;
    Attribute: TAttribute;
    FieldDef: TFieldDef;
    FieldType: TFieldType;
    aSize: word;
    Item,CountItem: integer;
    FieldName: string;
  begin
    if (ObjType.DataType = dtObject) or SparseArrays then
      CountItem := 1
    else begin
      CountItem := ObjType.Size;
      if CountItem > MaxArrayItem then // Restriction of array length
        CountItem := MaxArrayItem;
    end;

    for i := 0 to ObjType.AttributeCount - 1 do begin
      for Item := 0 to CountItem - 1 do begin
        Attribute := ObjType.Attributes[i];

        FieldType := GetFieldType(Attribute.DataType);

        aSize := 0;
        case FieldType of
          ftString, ftWideString:
            aSize := Attribute.Length;
        end;

        FieldDef := TFieldDef.Create(Parent.{$IFNDEF FPC}ChildDefs{$ELSE}Collection{$ENDIF});
        if ObjType.DataType = dtObject then
          FieldName := Attribute.Name
        else
          FieldName := GetObjectFieldDefName(Parent, Item, ObjType);
        FieldDef.Name := GetFieldName(Parent.Name, FieldName, 0);

        FieldDef.DataType := FieldType;
        FieldDef.Size := aSize;
        FieldDef.Required := False;
        if DB.faReadonly in Parent.Attributes then
          FieldDef.Attributes := FieldDef.Attributes + [DB.faReadonly];

        if FieldType in [ftADT,ftArray] then
          CreateObjectFields(Attribute.ObjectType, FieldDef);
      end;
    end;
  end;

var
  FieldSize: Integer;
  FieldLength: Integer;
  FieldScale: Integer;
  FieldType: TFieldType;
begin
  Result := nil;
  FieldType := GetFieldType(FieldDesc, FieldSize, FieldLength, FieldScale);
  if FieldType <> ftUnknown then begin
    // FieldNo 1..
    Result := TFieldDef.Create(FieldDefs, FieldDesc.Name, FieldType, FieldSize,
      FieldDesc.Required and FLocalConstraints, FieldDesc.FieldNo);

    if FieldDesc.ReadOnly then
      Result.Attributes := Result.Attributes + [DB.faReadonly];

    if FieldType in [ftSmallint, ftWord, ftInteger, ftLargeint,
      {$IFDEF VER12P}ftByte, ftShortint, ftLongword,{$ENDIF}
      {$IFDEF VER14P}ftSingle,{$ENDIF} ftFloat]
    then
      Result.Precision := FieldLength
    else if FieldType in [ftBCD{$IFDEF VER6P}, ftFMTBCD{$ENDIF}] then begin
      Result.Precision := FieldLength;
      Result.Size := FieldScale;
    end
    else
      if FieldType in [ftADT, ftArray] then
        CreateObjectFields(FieldDesc.ObjectType, Result);

    if FieldDesc.Hidden then
      Result.Attributes := Result.Attributes + [DB.faHiddenCol];
    if FieldDesc.Fixed then
      Result.Attributes := Result.Attributes + [DB.faFixed];
  end
  else
    DatabaseError(SDataTypeNotSupported, Self);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CreateFieldDefs;
var
  i: integer;
  FieldDesc: TFieldDesc;
begin
  Assert(Data <> nil);
  if not NeedCreateFieldDefs then
    Exit;

  FieldDefs.BeginUpdate;
  try
    FieldDefs.Clear;
    for i := 0 to Data.Fields.Count - 1 do begin
      FieldDesc := Data.Fields[i];
      if not FieldDesc.HiddenObject and
        (FieldDesc.FieldDescKind = fdkData) and
        (not FieldDesc.HasParent or FieldDesc.ParentField.HiddenObject)
      then
        CreateFieldDef(FieldDesc);

    end;
  finally
    FieldDefs.EndUpdate;
  end;
{$IFNDEF FPC}
  FieldDefs.Updated := False;
  UpdateFieldDefList;
{$ENDIF}
  FieldDefs.Updated := Data.Fields.Count <> 0; // bug with prepare method that does not get fields fixed
end;

{$IFNDEF FPC}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.UpdateFieldDefList;

  procedure BeforeUpdateFieldDefList(FieldDefs: TFieldDefs);
  var
    i: integer;
  begin
    // Update FieldDefs before FieldDefList updating
    for i := 0 to FieldDefs.Count - 1 do begin
      if FieldDefs[i].DataType = ftArray then begin
        FieldDefs[i].DataType := ftADT;
        FieldDefs[i].Size := 0;
        FieldDefs[i].Attributes := FieldDefs[i].Attributes + [faUnNamed];
      end;

      if FieldDefs[i].HasChildDefs then
        BeforeUpdateFieldDefList(FieldDefs[i].ChildDefs);
    end;
  end;

  procedure AfterUpdateFieldDefList(FieldDefs: TFieldDefs);
  var
    i: integer;
  begin
    // Update FieldDefs after FieldDefList updating
    for i := 0 to FieldDefs.Count - 1 do begin
      if (FieldDefs[i].DataType = ftADT) and (faUnNamed in FieldDefs[i].Attributes) then begin
        FieldDefs[i].DataType := ftArray;
        FieldDefs[i].Size := 0;
        FieldDefs[i].Attributes := FieldDefs[i].Attributes - [faUnNamed];
      end;

      if FieldDefs[i].HasChildDefs then
        AfterUpdateFieldDefList(FieldDefs[i].ChildDefs);
    end;
  end;

begin
  if NeedComplexUpdateFieldDefList then
    BeforeUpdateFieldDefList(FieldDefs);

  FieldDefList.Update;

  if NeedComplexUpdateFieldDefList then
    AfterUpdateFieldDefList(FieldDefs);
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.NeedComplexUpdateFieldDefList;
begin
  Result := false;
end;

{$IFNDEF FPC}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetObjectTypeNames(Fields: TFields);
var
  i: integer;
  Field: TField;
  ObjectField: TObjectField;
begin
  for i := 0 to Fields.Count - 1 do begin
    Field := Fields[i];
    if Field is TObjectField then begin
      ObjectField := TObjectField(Field);

      ObjectField.ObjectType := GetFieldDesc(ObjectField).ObjectType.Name;

      with ObjectField do
        if DataType in [ftADT, ftArray] then begin
          if (DataType = ftArray) and SparseArrays and
             (Fields[0].DataType = ftADT)
          then
            GetObjectTypeNames(TObjectField(Fields[0]).Fields)
        else
          GetObjectTypeNames(Fields);
      end;
    end;
  end;
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldDescNo(Field: TField): integer;
var
  FieldDesc: TFieldDesc;
begin
  FieldDesc := GetFieldDesc(Field);
  if FieldDesc <> nil then
    Result := FieldDesc.FieldNo
  else
    raise Exception.Create(Format(SFieldNotFound, [Field.FieldName]));
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldDesc(const Field: TField): TFieldDesc;
var
  FieldDesc: TFieldDesc;
  i: integer;
  Found: boolean;
begin
  Assert(Data <> nil, 'FIRecordSet must be setted to this time');
  Assert(Field <> nil, 'Field cannot be nil');
  Assert((Field.DataSet = Self) or (Field.DataSet = nil {CR 22356}), 'Wrong DataSet');
  {if Field.DataSet <> Self then
    for i := 0 to Data.FieldCount - 1 do
      if _LowerCase(Field.FieldName) = _LowerCase(Data.Fields[i].Name) then begin /// Field.FieldName must be equal with FieldDesc.Name
        Result := Data.Fields[i];
        Exit;
      end;}

  Result := nil;
  if Field.FieldNo > 0 then
    Result := TFieldDesc(Data.Fields[Field.FieldNo - 1])
  else
  begin
  {$IFDEF VER20P}
    if not (lcPersistent in Fields.LifeCycles) and
  {$ELSE}
    if DefaultFields and
  {$ENDIF}
       (Fields.Count = Data.Fields.Count {just in case; should be moved to Assert}) 
    then begin
      i := Fields.IndexOf(Field);
      Result := Data.Fields[i];
    end
    else
      for i := 0 to Data.Fields.Count - 1 do begin
        FieldDesc := Data.Fields[i];
        Found := SameText(Field.FieldName, FieldDesc.Name); /// Field.FieldName must be equal with FieldDesc.Name
        if (Field.FieldKind <> fkData) then begin
          if FieldDesc.FieldDescKind = fdkData then
            Found := False
          else
          if not Found then
            Found := SameText(Field.FieldName, FieldDesc.ActualName);
        end;
        if Found then begin
          Result := FieldDesc;
          Break;
        end;
      end;
  end;
  // Assert(_LowerCase(Field.FieldName) = _LowerCase(Result.Name), Format('Field.FieldName <> FieldDesc.Name' + DALineSeparator + '"%s" <> "%s"' + DALineSeparator, [Field.FieldName, Result.Name]));
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldDesc(const FieldName: string): TFieldDesc;
begin
  if Data = nil then
    raise Exception.Create(Format(SFieldNotFound, [FieldName]));

  Result := Data.FieldByName(FieldName);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldDesc(const FieldNo: integer): TFieldDesc;
begin
  if (Data = nil) or (FieldNo <= 0) then {fkCalculated, fkLookup}
    Result := nil
  else
    Result := TFieldDesc(Data.Fields[FieldNo - 1])
end;

{$IFDEF VER17P}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldData(FieldNo: integer; {$IFDEF VER18P}var{$ENDIF} Buffer: TValueBuffer): boolean;
begin
  Result := GetFieldData(FieldNo, IntPtr(Buffer));
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldData(Field: TField; {$IFDEF VER18P}var{$ENDIF} Buffer: TValueBuffer): boolean;
begin
  Result := GetFieldData(Field, IntPtr(Buffer));
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldData(Field: TField; {$IFDEF VER18P}var{$ENDIF} Buffer: TValueBuffer; NativeFormat: Boolean): Boolean;
begin
  if (Field.DataType = ftWideString) and (Buffer <> nil) then
    Result := inherited GetFieldData(Field, Buffer, True)
  else
    Result := inherited GetFieldData(Field, Buffer, NativeFormat);
end;
{$ENDIF}

{$IFNDEF FPC}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldData(FieldNo: integer; Buffer: IntPtr): boolean;
var
  IsBlank: boolean;
  RecBuf: TRecordBuffer;
  FieldDesc: TFieldDesc;
  Field: TField;
begin
//if BlockReadSize > 0 then

  Result := GetActiveRecBuf(RecBuf);
  if Result then begin
    Assert(Data <> nil);

    FieldDesc := GetFieldDesc(FieldNo);
    Data.GetField(FieldDesc, RecBuf, Buffer, True, IsBlank);
    Result := not IsBlank;

    if Result then begin
      Field := FieldByNumber(FieldNo);
      if Field.DataType = ftBCD then
        Result := InternalDataConvert(Field, Buffer, Buffer, True);
    end;
  end;
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldData(Field: TField; Buffer: IntPtr): boolean;
var
  IsBlank: boolean;
  RecBuf: TRecordBuffer;
  FieldBuf: IntPtr;
{$IFNDEF CLR}
{$IFNDEF VER10P}
  DataOffset: LongInt;
{$ENDIF}
{$ENDIF}
  i: integer;
  FieldDesc: TFieldDesc;
begin
  Result := False;
  if not GetActiveRecBuf(RecBuf) then
    Exit;

  Assert(Data <> nil);
  if Field.FieldNo > 0 then begin
    FieldDesc := GetFieldDesc(Field.FieldNo);
    Data.GetField(FieldDesc, RecBuf, Buffer, True, IsBlank);
    Result := not IsBlank;
  end
  else if State in  [dsBrowse, dsEdit, dsInsert, dsCalcFields, dsFilter, dsBlockRead] then
    if FCreateCalcFieldDescs then begin
      FieldDesc := nil;
      for i := 0 to Length(FCalcFieldsMapping) - 1 do
        if FCalcFieldsMapping[i].Field = Field then begin
          FieldDesc := FCalcFieldsMapping[i].FieldDesc;
          break;
        end;
      if FieldDesc <> nil then
        Data.GetField(FieldDesc, RecBuf, Buffer, True, IsBlank)
      else
        Data.GetField(GetFieldDesc(Field), RecBuf, Buffer, True, IsBlank);
      Result := not IsBlank;
    end
    else begin
      FieldBuf := PtrOffset(RecBuf, RecordSize + Field.Offset);
      Result := Boolean(Marshal.ReadByte(FieldBuf));
      if Result and (Buffer <> nil) then
      {$IFNDEF CLR}
      {$IFNDEF VER10P}
        if Field.DataType = ftWideString then begin
          DataOffset := Integer(PtrOffset(FieldBuf, 1)^);
          CopyBuffer(PtrOffset(RecBuf, FWideStringOfs + DataOffset), Buffer, (Field.Size + 1) * SizeOf(WideChar));
        end
        else
      {$ENDIF}
      {$ENDIF}
          CopyBuffer(PtrOffset(FieldBuf, 1), Buffer, Field.DataSize);
    end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldData(Field: TField; Buffer: IntPtr; NativeFormat: Boolean): Boolean;
{$IFNDEF CLR}
{$IFNDEF FPC}
{$IFNDEF VER10P}
var
  Temp: PWideChar;
{$ENDIF}
{$ENDIF}
{$ENDIF}
begin
  if (Field.DataType = ftWideString) and (Buffer <> nil) then begin
  {$IFDEF CLR}
    Result := inherited GetFieldData(Field, Buffer, True);
  {$ELSE}
    {$IFDEF VER10P}
      Result := inherited GetFieldData(Field, {$IFDEF NEXTGEN}TBytes{$ENDIF}(Buffer), True);
    {$ELSE}
    {$IFDEF FPC}
      Result := inherited GetFieldData(Field, Buffer, True);
    {$ELSE}
      { Cannot copy direct - may be conflict with Delphi string manager
      SetLength(WideString(Buffer^), Field.Size * sizeof(WideChar));
      Result := inherited GetFieldData(Field, PWideChar(WideString(Buffer^)), True);}
      GetMem(Temp, (Field.Size + 1 {#0} + 8) * sizeof(WideChar));//+ 8 for numbers
      try
        Result := inherited GetFieldData(Field, Temp, True);
        if Result then
          WideString(Buffer^) := Temp
        else
          WideString(Buffer^) := '';
      finally
        FreeMem(Temp);
      end;
    {$ENDIF}
    {$ENDIF}
  {$ENDIF}
  end
  else
    Result := inherited GetFieldData(Field, {$IFDEF NEXTGEN}TBytes{$ENDIF}(Buffer), NativeFormat);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalDataConvert(Field: TField; Source, Dest: IntPtr; ToNative: Boolean): boolean;
{$IFDEF CLR}
var
  TimeStamp: TTimeStamp;
  TempDouble: Double;
  TempBcd: TBcd;
{$ENDIF}
begin
  Result := True;

  case Field.DataType of
  {$IFDEF CLR}
    ftDate:
      if ToNative then
      begin
        TimeStamp := DateTimeToTimeStamp(
          BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Source)));
        Marshal.WriteInt32(Dest, TimeStamp.Date);
      end
      else
      begin
        TimeStamp.Time := 0;
        TimeStamp.Date := Marshal.ReadInt32(Source);
        Marshal.WriteInt64(Dest,
          BitConverter.DoubleToInt64Bits(MemUtils.TimeStampToDateTime(TimeStamp)));
      end;
    ftTime:
      if ToNative then
      begin
        TimeStamp := DateTimeToTimeStamp(
          BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Source)));
        Marshal.WriteInt32(Dest, TimeStamp.Time);
      end
      else
      begin
        TimeStamp.Time := Marshal.ReadInt32(Source);
        TimeStamp.Date := DateDelta;
        Marshal.WriteInt64(Dest,
          BitConverter.DoubleToInt64Bits(MemUtils.TimeStampToDateTime(TimeStamp)));
      end;
    ftDateTime:
      if ToNative then
      begin
        TempDouble := TimeStampToMSecs(DateTimeToTimeStamp(
          BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Source))));
        Marshal.WriteInt64(Dest, BitConverter.DoubleToInt64Bits(TempDouble));
      end
      else
      begin
        TimeStamp := MSecsToTimeStamp(
          Trunc(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Source))));
        Marshal.WriteInt64(Dest,
          BitConverter.DoubleToInt64Bits(MemUtils.TimeStampToDateTime(TimeStamp)));
      end;
  {$ENDIF}
    ftBCD:
      if ToNative then begin
        if Source <> nil then begin
        {$IFNDEF CLR}
          Result := CurrToBCD(Currency(Source^), TBcd(Dest^), 32, Field.Size)
        {$ELSE}
          Result := CurrToBcd(Currency(Marshal.ReadInt64(Source)), TempBcd, 32, Field.Size);
          Marshal.Copy(TBcd.ToBytes(TempBcd), 0, Dest, SizeOfTBcd);
        {$ENDIF}
        end;
      end
      else
        Marshal.WriteInt64(Dest, Marshal.ReadInt64(Source));
  {$IFDEF VER12P}
    ftWideString, ftFixedWideChar:
      StrLCopyW(PWChar(Dest), PWChar(Source), Field.Size);
    ftString:
      StrLCopy(PAChar(Dest), PAChar(Source), Field.Size);
  {$ENDIF}
    else
      Result := False;
  end;
end;

{$IFDEF VER17P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFieldData(Field: TField; Buffer: TValueBuffer);
begin
  SetFieldData(Field, IntPtr(Buffer));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFieldData(Field: TField; Buffer: TValueBuffer; NativeFormat: Boolean);
begin
  if Field is TBcdField then
    SetFieldData(Field, Buffer)
  else
    inherited SetFieldData(Field, Buffer, NativeFormat);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataConvert(Field: TField; Source: TValueBuffer; {$IFDEF VER18P}var{$ENDIF} Dest: TValueBuffer; ToNative: Boolean);
begin
  if not InternalDataConvert(Field, Source, Dest, ToNative) then
    inherited DataConvert(Field, Source, Dest, ToNative);
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFieldData(Field: TField; Buffer: IntPtr);
var
  RecBuf: TRecordBuffer;
  FieldBuf: IntPtr;
{$IFNDEF CLR}
{$IFNDEF VER10P}
  DataOffset, WideStringSize: integer;
{$ENDIF}
{$ENDIF}
  FieldDesc: TFieldDesc;
  i: integer;
begin
  if not (State in dsWriteModes) then
    DatabaseError(SNotEditing);
  Assert(Data <> nil);         // must be after state check if fields was created in DT, but SQL removed
  GetActiveRecBuf(RecBuf);
  if Field.FieldNo > 0 then begin
    if State = dsCalcFields then
      DatabaseError(SNotEditing);
    if Field.ReadOnly and not ((State in [dsSetKey, dsFilter]) or FInSettingDefaultExpressionValues) then
      DatabaseErrorFmt(SFieldReadOnly, [Field.DisplayName]);
    Field.Validate(Buffer);
    if Field.FieldKind <> fkInternalCalc then begin
      FieldDesc := GetFieldDesc(Field.FieldNo);
      Data.PutField(FieldDesc, RecBuf, Buffer, True);
    end;
  end
  else
    if FCreateCalcFieldDescs then begin
      FieldDesc := nil;
      for i := 0 to Length(FCalcFieldsMapping) - 1 do
        if FCalcFieldsMapping[i].Field = Field then begin
          FieldDesc := FCalcFieldsMapping[i].FieldDesc;
          break;
        end;
      if FieldDesc <> nil then
        Data.PutField(FieldDesc, RecBuf, Buffer, True)
      else begin
        FieldDesc := GetFieldDesc(Field.FieldNo);
        Data.PutField(FieldDesc, RecBuf, Buffer, True);
      end;
    end
    else begin
      FieldBuf := PtrOffset(RecBuf, RecordSize + Field.Offset);
      Marshal.WriteByte(FieldBuf, Byte(IntPtr(Buffer) <> nil));
      if IntPtr(Buffer) <> nil then
      {$IFNDEF CLR}
      {$IFNDEF VER10P}
        if Field.DataType = ftWideString then begin
          WideStringSize := Length(WideString(Buffer^)) * Sizeof(WideChar);
          DataOffset := Integer(PtrOffset(FieldBuf, 1)^);

          CopyBuffer(IntPtr(Buffer^), PtrOffset(RecBuf, FWideStringOfs + DataOffset), WideStringSize);
          Marshal.WriteInt16(PtrOffset(RecBuf, FWideStringOfs + DataOffset + WideStringSize), 0);
        end
        else
      {$ENDIF}
      {$ENDIF}
          CopyBuffer(Buffer, PtrOffset(FieldBuf, 1), Field.DataSize);
    end;

  if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
  {$IFDEF CLR}
    DataEvent(deFieldChange, Field);
  {$ELSE} {$IFDEF FPC}
    DataEvent(deFieldChange, PtrInt(Field));
  {$ELSE} {$IFDEF VER16P}
    DataEvent(deFieldChange, NativeInt(Field));
  {$ELSE}
    DataEvent(deFieldChange, Longint(Field));
  {$ENDIF} {$ENDIF} {$ENDIF}
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFieldData(Field: TField; Buffer: IntPtr; NativeFormat: Boolean);
begin
  if Field is TWideStringField then begin
    //Assert(Buffer <> nil);
    inherited SetFieldData(Field, Buffer, {$IFDEF VER12P}NativeFormat{$ELSE}True{$ENDIF});
  end
  else if Field is TBcdField then
    SetFieldData(Field, Buffer)
  else
    inherited SetFieldData(Field, Buffer, NativeFormat);
end;

{$IFNDEF NEXTGEN}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataConvert(Field: TField; Source, Dest: IntPtr; ToNative: Boolean);
begin
  if not InternalDataConvert(Field, Source, Dest, ToNative) then
    inherited DataConvert(Field, Source, Dest, ToNative);
end;
{$ENDIF}

{$IFDEF NEXTGEN}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DestroyFields;
var
  i: integer;
begin
  // XE4 bug (rm-23646)
  for i := 0 to Fields.Count - 1 do
    RemoveComponent(Fields[i]);

  inherited;
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetMaxFieldCount: integer;
begin
  Result := MaxInt;
end;

{$IFNDEF FPC}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetStateFieldValue(State: TDataSetState; Field: TField): Variant;
begin
  if ((Self.State = dsInsert) and (State = dsOldValue)) or (State = dsInsert) then
    Result := NULL
  else
    Result := inherited GetStateFieldValue(State, Field);
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetSparseArrays: boolean;
begin
{$IFNDEF FPC}
  Result := inherited SparseArrays;
{$ELSE}
  Result := False;
{$ENDIF}
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetSparseArrays(Value: boolean);
begin
{$IFNDEF FPC}
  if SparseArrays <> Value then begin
    UnPrepare;

    inherited SparseArrays := Value;

    if Data <> nil then
      Data.SparseArrays := Value;
  end;
{$ENDIF}
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CheckFieldCompatibility(Field: TField; FieldDef: TFieldDef);
{$IFDEF FPC}
const
  BaseFieldTypes: array[TFieldType] of TFieldType = (
    ftUnknown, ftString, ftInteger, ftInteger, ftInteger, ftBoolean, ftFloat,
    ftFloat, ftBCD, ftDateTime, ftDateTime, ftDateTime, ftBytes, ftVarBytes,
    ftInteger, ftBlob, ftBlob, ftBlob, ftBlob, ftBlob, ftBlob, ftBlob, ftUnknown,
    ftString, ftUnknown, ftLargeInt, ftADT, ftArray, ftReference, ftDataSet,
    ftBlob, ftBlob, ftVariant, ftInterface, ftInterface, ftString, ftTimeStamp, ftFMTBcd,
    ftFixedWideChar, ftWideMemo);

  CheckTypeSizes = [ftBytes, ftVarBytes, ftBCD, ftReference];
{$ENDIF}
var
  Compatible: Boolean;
begin
  case Field.DataType of
    ftFloat, ftCurrency:
      Compatible := FieldDef.DataType in [ftFloat, ftCurrency];
    ftBCD
  {$IFDEF VER14P}
    , ftSingle
  {$ENDIF}
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    ,ftFMTBcd
  {$ENDIF}
  {$ENDIF}
    : Compatible := FieldDef.DataType = Field.DataType;
  else
    Compatible := False;
  end;
  if not Compatible then
{$IFDEF FPC}
  begin
    if (BaseFieldTypes[Field.DataType] <> BaseFieldTypes[FieldDef.DataType]) then
      DatabaseErrorFmt(SFieldTypeMismatch, [Field.DisplayName,
        FieldTypeNames[Field.DataType], FieldTypeNames[FieldDef.DataType]], Self);
    if (Field.DataType in CheckTypeSizes) and (Field.Size <> FieldDef.Size) then
        DatabaseErrorFmt(SFieldSizeMismatch, [Field.DisplayName, Field.Size,
          FieldDef.Size], Self);
  end;
{$ELSE}
  inherited;
{$ENDIF}
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldClass(FieldType: TFieldType): TFieldClass;
begin
  Result := GetFieldClass(FieldType, dtUnknown);
end;

{$IFDEF VER12P}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldClass(FieldDef: TFieldDef): TFieldClass;
var
  FieldDesc: TFieldDesc;
begin
  if Assigned(Data) and (Data.Fields.Count >= FieldDef.FieldNo) then begin
    FieldDesc := Data.Fields[FieldDef.FieldNo - 1];
    Result := GetFieldClass(FieldDef.DataType, FieldDesc.DataType);
  end
  else
    Result := GetFieldClass(FieldDef.DataType, dtUnknown);
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetFieldClass(FieldType: TFieldType; DataType: Word): TFieldClass;
begin
  CheckDataSetService;
  Result := FDataSetService.GetFieldClass(FieldType, DataType);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalGetBlob(FieldDesc: TFieldDesc): TBlob;
var
  RecBuf: TRecordBuffer;
  Ptr: IntPtr;
  IsBlank: boolean;
begin
  Assert(Data <> nil);
  Ptr := Marshal.AllocHGlobal(sizeof(IntPtr));
  try
    if GetActiveRecBuf(RecBuf) then begin
      if not FieldDesc.IsBlob then
        DatabaseError(SNeedBlobType);

      Data.GetField(FieldDesc, RecBuf, Ptr, False, IsBlank);
      Result := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(Ptr)));
    end
    else
      Result := nil;
  finally
    Marshal.FreeHGlobal(Ptr);
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBlob(const FieldName: string): TBlob;
var
  FieldDesc: TFieldDesc;
begin
  Assert(Data <> nil);
  FieldDesc := Data.FieldByName(FieldName);
  Result := InternalGetBlob(FieldDesc);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBlob(Field: TField): TBlob;
var
  FieldDesc: TFieldDesc;
begin
  if (Field <> nil) and Active then begin
    FieldDesc := GetFieldDesc(Field);
    if FieldDesc <> nil then
      Result := InternalGetBlob(FieldDesc)
    else
      Result := nil;
  end
  else
    Result := nil;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalSetBlob(FieldDesc: TFieldDesc; Blob: TBlob): boolean;
var
  OldBlob: TBlob;
  RecBuf: TRecordBuffer;
begin
  Assert(FieldDesc <> nil);
  Assert(Blob <> nil);
  OldBlob := InternalGetBlob(FieldDesc);
  Assert(OldBlob <> nil);
  Result := Blob.ClassType = OldBlob.ClassType;
  if GetActiveRecBuf(RecBuf) then begin
    if not FieldDesc.IsBlob then
      DatabaseError(SNeedBlobType);

    Blob.AddRef;
    OldBlob.Free;
    Marshal.WriteIntPtr(RecBuf, FieldDesc.Offset, Blob.GCHandle);
  end
  else
    Result := False;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBlob(Field: TField; Blob: TBlob): boolean;
var
  FieldDesc: TFieldDesc;
begin
  if Field <> nil then begin
    FieldDesc := GetFieldDesc(Field);
    if FieldDesc = nil then
      raise Exception.Create(Format(SFieldNotFound, [Field.FieldName]));
    Result := InternalSetBlob(FieldDesc, Blob);
  end
  else
    Result := False;
end;

{function TMemDataSet.Translate(const Src: string; var Dest: string; boolean): integer;
begin
  inherited Translate(Src, Dest, ToOem);
  Result := StrLen(Src);
{  if ToOem then
    AnsiToNativeBuf(Locale, Src, Dest, Result)
  else
    NativeToAnsiBuf(Locale, Src, Dest, Result);
  if Src <> Dest then
    Dest[Result] := #0;
end;}

{ Buffer/Record Management }

{$IFDEF NEXTGEN}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.AllocRecBuf: TRecBuf;
begin
  Result := NativeInt(AllocRecordBuffer);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeRecBuf(var Buffer: TRecBuf);
begin
  FreeRecordBuffer(TRecordBuffer(Buffer));
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.AllocRecordBuffer: TRecordBuffer;
begin
  Result := Marshal.AllocHGlobal(FRecBufSize);
  PRecInfo(PtrOffset(Result, FRecInfoOfs)).RefComplexFields := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeRecordBuffer(var Buffer: TRecordBuffer);
begin
  FreeRefComplexFields(Buffer);
  Marshal.FreeHGlobal(Buffer);
  Buffer := nil;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CopyRecordBuffer(SrcBuffer: IntPtr; DstBuffer: IntPtr);
begin
  Assert(Data <> nil);
  Data.FreeComplexFields(DstBuffer, False);
  CopyBuffer(SrcBuffer, DstBuffer, FRecBufSize);
  PRecInfo(PtrOffset(DstBuffer, FRecInfoOfs)).RefComplexFields := True;
  Data.CreateComplexFields(DstBuffer, False);  //copy complex fields
  Data.CopyComplexFields(SrcBuffer, DstBuffer, False);
end;

{$IFDEF VER18P}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetRecord(Buffer: TRecBuf; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
begin
  Result := GetRecord(TRecordBuffer(Buffer), GetMode, DoCheck);
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
var
  RecInfo: PRecInfo;
begin
  Assert(Data <> nil);
  FreeRefComplexFields(Buffer);
  case GetMode of
    gmCurrent:
      Data.GetRecord(Buffer);
    gmNext:
      Data.GetNextRecord(Buffer);
    gmPrior:
      Data.GetPriorRecord(Buffer);
  end;
  if Data.BOF then
    Result := grBOF
  else
    if Data.EOF then
      Result := grEOF
    else begin
      RecInfo := PRecInfo(PtrOffset(Buffer, FRecInfoOfs));
      RecInfo.RecordNumber := Data.RecordNo;
      RecInfo.UpdateStatus := TUpdateStatus(Data.GetUpdateStatus);
      RecInfo.BookmarkFlag := bfCurrent;
      if FNeedAddRef then begin
        RecInfo.RefComplexFields := True;
        Data.AddRefComplexFields(Buffer, True);
        Data.AddRef(Buffer);
      end;

      //ClearBlobCache(Buffer);
      if not FCacheCalcFields then
        GetCalcFields({$IFDEF VER18P}NativeInt{$ENDIF}(Buffer));
      //SetBookmarkFlag(Buffer, bfCurrent);
      Data.GetBookmark(PRecBookmark(PtrOffset(Buffer, FBookmarkOfs)));

      Result := grOK;
    end;
end;

{$IFDEF VER18P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalInitRecord(Buffer: TRecBuf);
begin
  InternalInitRecord(TRecordBuffer(Buffer));
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalInitRecord(Buffer: TRecordBuffer);
begin
  Assert(Data <> nil);
  //FInDeferredPost := False; moved to InternalInsert
  //FInInserting := True;
  FreeRefComplexFields(Buffer);
  Data.InitRecord(Buffer);
  if Data.HasComplexFields then
    Data.CreateComplexFields(Buffer, True);
end;

{$IFDEF VER18P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InitRecord(Buffer: TRecBuf);
begin
  InitRecord(TRecordBuffer(Buffer));
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InitRecord(Buffer: TRecordBuffer);
var
  RecInfo: PRecInfo;
begin
  inherited InitRecord({$IFDEF NEXTGEN}NativeInt{$ENDIF}(Buffer));

  Assert(Data <> nil);
  //ClearBlobCache(Buffer);
  RecInfo := PRecInfo(PtrOffset(Buffer, FRecInfoOfs));
  RecInfo.RecordNumber := -1;
  RecInfo.UpdateStatus := TUpdateStatus(usInserted);
  RecInfo.BookMarkFlag := bfInserted;
  Data.InitRecord(IntPtr(FOldRecBuf));  // clear OldRecBuf
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetOldRecBuf: TRecordBuffer;
begin
  if FInDeferredPost then
    Result := OldDeferredPostBuf
  else
  if InCacheProcessing then
    Result := OldCacheRecBuf
  else
    Result := GetOldRecord;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetNewRecBuf: TRecordBuffer;
begin
  if InCacheProcessing then
    Result := NewCacheRecBuf
  else
    Result := IntPtr(ActiveBuffer);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetActiveRecBuf(out RecBuf: TRecordBuffer): boolean;
begin
  case State of
    dsBlockRead:
      if IsEmpty then
        RecBuf := nil
      else
        RecBuf := IntPtr(ActiveBuffer);
    dsBrowse:
      if InCacheProcessing then
        RecBuf := NewCacheRecBuf
      else
        if IsEmpty or not IsCursorOpen then
          RecBuf := nil
        else
          RecBuf := IntPtr(ActiveBuffer);
    dsEdit, dsInsert:
      RecBuf := IntPtr(ActiveBuffer);
    dsCalcFields:
      RecBuf := IntPtr(CalcBuffer);
    dsFilter:
      RecBuf := FFilterBuffer;
    dsNewValue:
      RecBuf := GetNewRecBuf;
    dsOldValue:
      RecBuf := GetOldRecBuf;
    dsSetKey:
      RecBuf := FRangeCurrentBuffer;
  else
    RecBuf := nil;
  end;
  Result := RecBuf <> nil;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetOldRecord: TRecordBuffer;
begin
  Assert(Data <> nil);
  UpdateCursorPos;
  Data.GetOldRecord(IntPtr(FOldRecBuf));
  Result := IntPtr(FOldRecBuf);
end;

{$IFDEF VER18P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ClearCalcFields(Buffer: NativeInt);
begin
  ClearCalcFields(TRecordBuffer(Buffer));
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ClearCalcFields(Buffer: TRecordBuffer);
var
  i: integer;
{$IFNDEF CLR}
{$IFNDEF VER10P}
  DataOffset: integer;
  Field: TField;
{$ENDIF}
{$ENDIF}
  FieldDesc: TFieldDesc;
begin
  if FCreateCalcFieldDescs then begin
    for i := 0 to Data.Fields.Count - 1 do begin
      FieldDesc := Data.Fields[i];
      if FieldDesc.FieldDescKind <> fdkData then
        Data.SetNull(FieldDesc, Buffer, True);
    end;
  end
  else begin
    FillChar(PtrOffset(Buffer, RecordSize), CalcFieldsSize, 0);

  {$IFNDEF CLR}
  {$IFNDEF VER10P}
    DataOffset := 0;
    for i := 0 to Fields.Count - 1 do begin
      Field := Fields[i];
      if (Field.DataType = ftWideString) and (Field.FieldKind in [fkCalculated, fkLookUp]) then begin
        Marshal.WriteInt32(PtrOffset(Buffer, RecordSize + Field.Offset + 1), DataOffset);
        DataOffset := DataOffset + (Field.Size + 1) * sizeof(WideChar);
      end;
    end;
    FillChar(PtrOffset(Buffer, FWideStringOfs), DataOffset, 0);
  {$ENDIF}
  {$ENDIF}
  end;
end;

// WAR don't support BlockRead

{$IFNDEF FPC}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBlockReadSize(Value: Integer);
begin
  if Value <> BlockReadSize then
    if (Value > 0) or (Value < -1) then begin
      UpdateCursorPos;
      inherited;
    end
    else
      inherited;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.BlockReadNext;
begin
  inherited;
end;
{$ENDIF}

{$IFDEF FPC}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBufListSize(Value: Longint);
const
  DefaultBufferCount = 10;
begin
  if (BufferCount = -1) and (Value = DefaultBufferCount) then
    Value := 1;

  inherited SetBufListSize(Value);
end;
{$ENDIF}

{ Bookmarks }

{$IFDEF VER17P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBookmarkData(Buffer: {$IFDEF VER18P}TRecBuf{$ELSE}TRecordBuffer{$ENDIF}; Bookmark: TBookmark);
begin
  GetBookmarkData(TRecordBuffer(Buffer), IntPtr(Bookmark));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBookmarkData(Buffer: {$IFDEF VER18P}TRecBuf{$ELSE}TRecordBuffer{$ENDIF}; Bookmark: TBookmark);
begin
  SetBookmarkData(TRecordBuffer(Buffer), IntPtr(Bookmark));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalGotoBookmark(Bookmark: TBookmark);
begin
  InternalGotoBookmark(IntPtr(Bookmark));
end;
{$ENDIF}

{$IFDEF VER18P}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBookmarkFlag(Buffer:  TRecBuf): TBookmarkFlag;
begin
  Result := GetBookmarkFlag(TRecordBuffer(Buffer));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBookmarkFlag(Buffer: TRecBuf; Value: TBookmarkFlag);
begin
  SetBookmarkFlag(TRecordBuffer(Buffer), Value);
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBookmarkData(Buffer: TRecordBuffer;
  {$IFDEF CLR}var Bookmark: TBookmark{$ELSE}Bookmark: Pointer{$ENDIF});
begin
  CopyBuffer(PtrOffset(Buffer, FBookmarkOfs), Bookmark, BookmarkSize);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBookmarkData(Buffer: TRecordBuffer;
  {$IFDEF CLR}const Bookmark: TBookmark{$ELSE}Bookmark: Pointer{$ENDIF});
begin
  CopyBuffer(Bookmark, PtrOffset(Buffer, FBookmarkOfs), BookmarkSize);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalGotoBookmark(
  {$IFDEF CLR}const Bookmark: TBookmark{$ELSE}Bookmark: Pointer{$ENDIF});
begin
  Assert(Data <> nil);
  Data.SetToBookMark(PRecBookmark(Bookmark));
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
begin
  Result := PRecInfo(PtrOffset(Buffer, FRecInfoOfs)).BookmarkFlag;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
begin
  PRecInfo(PtrOffset(Buffer, FRecInfoOfs)).BookmarkFlag := Value;
end;

function ChangeDecimalSeparator(const Value: string; OldSeparator, NewSeparator: string): string;
begin
  if OldSeparator <> NewSeparator then
    Result := StringReplace(Value, OldSeparator, NewSeparator, [rfReplaceAll])
  else
    Result := Value;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SaveToXML(const FileName: string);
var
  FileStream: TFileStream;
begin
  FileStream := TFileStream.Create(FileName, fmCreate);
  try
    SaveToXML(FileStream);
  finally
    FileStream.Free;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SaveToXML(Destination: TStream);
begin
  CheckDataSetService;
  FDataSetService.SaveToXML(Destination);
end;

{$IFDEF CLR} // TDataSet bug
var
  NativeDBBuffers: TDBBufferList;

function NativeBuffers: TDBBufferList;
begin
  if NativeDBBuffers <> nil then
    Result := NativeDBBuffers
  else
  begin
    Result := TDBBufferList.Create;
    NativeDBBuffers := Result; // fix d9 bug
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetBookmark: TBookmark;
begin
  if BookmarkAvailable then
  begin
    Result := NativeBuffers.AllocHGlobal(BookmarkSize);
    GetBookmarkData(ActiveBuffer, Result);
  end else
    Result := nil;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FreeBookmark(var Bookmark: TBookmark);
begin
  NativeBuffers.FreeHGlobal(Bookmark);
end;
{$ENDIF}

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.BookmarkValid({$IFDEF CLR}const{$ENDIF} Bookmark: TBookmark): boolean;
begin
  if Data <> nil then
    Result := Data.BookmarkValid(PRecBookmark(Bookmark))
  else
    Result := False;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CompareBookmarks({$IFDEF CLR}const{$ENDIF} Bookmark1, Bookmark2: TBookmark): integer;
begin
  if Data <> nil then
    Result := Data.CompareBookmarks(PRecBookmark(Bookmark1), PRecBookmark(Bookmark2))
  else
    Result := 0;
end;

{ Navigation }

{$IFDEF VER18P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalSetToRecord(Buffer: TRecBuf);
begin
  InternalSetToRecord(TRecordBuffer(Buffer));
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalSetToRecord(Buffer: TRecordBuffer);
begin
  InternalGotoBookmark(PtrOffset(Buffer, FBookmarkOfs));

  if Data.NeedGetRecordAfterGotoBookmark and (GetBookmarkFlag(Buffer) <> bfInserted) then
    GetRecord(Buffer, gmCurrent, True);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetIndexFieldNames(const Value: string);
begin
  if FIndexFieldNames <> Value then begin
    if Active then begin
      CheckBrowseMode;
      UpdateCursorPos;
    end;
    if (Data <> nil) and (Data is TMemData) then begin
      if FRanged then begin
        ResetRange;
        TMemData(Data).ClearItemsOmittedStatus;
      end;
      TMemData(Data).SetIndexFieldNames(Value);
      if Active and (Length(TMemData(Data).IndexFields) > 0) then
        Resync([]);
    end;
    FIndexFieldNames := Value;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CheckSetKeyMode;
begin
  if State <> dsSetKey then
    DatabaseError(SNotEditing, Self);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetRangeBuffer(Buffer: TRecordBuffer; const Values: array of const; Clear: Boolean);
var
  i: integer;
begin
  if High(TMemData(Data).IndexFields) < 0 then
    DatabaseError(SNoFieldIndexes, Self);
  SetState(dsSetKey);
  FRangeCurrentBuffer := Buffer;
  if Clear then
    InitRangeBuffer(FRangeCurrentBuffer);
  for i := 0 to High(Values) do
    FieldByNumber(TMemData(Data).IndexFields[i].FieldDesc.FieldNo).AssignValue({$IFDEF CLR}Variant(Values[i]){$ELSE}Values[i]{$ENDIF});
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetKeyExclusive: Boolean;
begin
  CheckSetKeyMode;
  Result := PRecInfo(PtrOffset(FRangeCurrentBuffer, FRecInfoOfs)).KeyExclusive;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetKeyExclusive(Value: Boolean);
begin
  CheckSetKeyMode;
  PRecInfo(PtrOffset(FRangeCurrentBuffer, FRecInfoOfs)).KeyExclusive := Value;
  SetModified(True);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InitRangeBuffer(Buffer: TRecordBuffer);
var
  i: Integer;
begin
  FillChar(Buffer, FRecBufSize, 0);
  for i := 0 to Data.Fields.Count - 1 do
    Data.SetNull(Data.Fields[i], Buffer, True);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ResetRange;
begin
  InitRangeBuffer(FRangeStartBuffer);
  InitRangeBuffer(FRangeEndBuffer);
  Data.FilterRangeFunc := nil;
  FRanged := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalFirst;
begin
  Assert(Data <> nil);
  Data.SetToBegin;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalLast;
begin
  Assert(Data <> nil);
  Data.SetToEnd;
end;

{ Editing }

{$IFDEF VER17P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalAddRecord(Buffer: {$IFDEF VER18P}TRecBuf{$ELSE}TRecordBuffer{$ENDIF}; Append: boolean);
begin
  InternalAddRecord(IntPtr(Buffer), Append);
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalAddRecord(Buffer: IntPtr; Append: boolean);
begin
  Assert(Data <> nil);
  if Append then
    Data.AppendRecord(Buffer)
  else
    Data.InsertRecord(Buffer);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalInsert;
begin
  Assert(Data <> nil);
  FInDeferredPost := False;
  if OldDeferredPostBuf <> nil then begin
    FreeRefComplexFields(OldDeferredPostBuf, False);
    Marshal.FreeHGlobal(OldDeferredPostBuf);
    OldDeferredPostBuf := nil;
  end;
  FInInserting := True;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalEdit;
begin
  Assert(Data <> nil);
  FInDeferredPost := False;
  if OldDeferredPostBuf <> nil then begin
    FreeRefComplexFields(OldDeferredPostBuf, False);
    Marshal.FreeHGlobal(OldDeferredPostBuf);
    OldDeferredPostBuf := nil;
  end;
  FInEditing := True;

  FreeRefComplexFields(IntPtr(ActiveBuffer));
  Data.EditRecord(IntPtr(ActiveBuffer));
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalDelete;
begin
  Assert(Data <> nil);
  if not CanModify then DatabaseError(SDataSetReadOnly, Self);
  Data.DeleteRecord;

  // CR M8107
  FInInserting := False;
  FInEditing := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalPost;
var
  i: integer;
  Blob: TBlob;
  Field: TField;
  FieldDesc: TFieldDesc;
begin
  // if Cancel was called in the BeforePost event
  if State = dsBrowse then
    exit;

{$IFDEF VER6P}
  inherited;
{$ENDIF}

  Assert(Data <> nil);
  for i := 0 to FieldCount - 1 do begin
    Field := Fields[i];
    FieldDesc := GetFieldDesc(Field);
    if (FieldDesc <> nil) and FieldDesc.IsBlob then begin
      Blob := Data.GetObject(FieldDesc, IntPtr(ActiveBuffer)) as TBlob;
      if Blob.CanRollback and (Field is TBlobField) then
        TBlobField(Field).Modified := True;
    end;
  end;
  if State = dsEdit then begin
    Data.PostRecord(IntPtr(ActiveBuffer));
  end
  else
    Data.InsertRecord(IntPtr(ActiveBuffer));

  FInDeferredPost := False;
  if OldDeferredPostBuf <> nil then begin
    FreeRefComplexFields(OldDeferredPostBuf, False);
    Marshal.FreeHGlobal(OldDeferredPostBuf);
    OldDeferredPostBuf := nil;
  end;
  FInInserting := False;
  FInEditing := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Post;
begin
{$IFDEF FPC}
  if State <> dsSetKey then // Lazarus bug
{$ENDIF}
    inherited;

  if State = dsSetKey then begin
    DataEvent(deCheckBrowseMode, {$IFDEF CLR}nil{$ELSE}0{$ENDIF});
    SetState(dsBrowse);
    DataEvent(deDataSetChange, {$IFDEF CLR}nil{$ELSE}0{$ENDIF});
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Cancel;
var
  CancelBuf: IntPtr;
begin
  if Data = nil then
    Exit;
  if (State in [dsEdit, dsInsert]) and Data.HasComplexFields then begin
    CancelBuf := AllocRecordBuffer;
    CopyBuffer(IntPtr(ActiveBuffer), CancelBuf, FRecBufSize);
    Data.AddRefComplexFields(CancelBuf);
  end
  else
    CancelBuf := nil;
  try
    inherited;
  finally
    if CancelBuf <> nil then begin
      Data.FreeComplexFields(CancelBuf, True);
      Marshal.FreeHGlobal(CancelBuf);
    end;
  end;
end;


procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalCancel;
begin
  Assert(Data <> nil);
  FInDeferredPost := False;
  if OldDeferredPostBuf <> nil then begin
    FreeRefComplexFields(OldDeferredPostBuf, False);
    Marshal.FreeHGlobal(OldDeferredPostBuf);
    OldDeferredPostBuf := nil;
  end;
  FInInserting := False;
  FInEditing := False;

  if State = dsEdit then
    Data.CancelRecord(IntPtr(ActiveBuffer));
  if State = dsInsert then
    if Data.HasComplexFields then
      Data.FreeComplexFields(IntPtr(ActiveBuffer), True);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalDeferredPost;
begin
  if State = dsEdit then
    DoPerformUpdate
  else
    DoPerformAppend;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DeferredPost;
  procedure CheckRequiredFields;
  var
    I: Integer;
  begin
    for I := 0 to Fields.Count - 1 do
      with Fields[I] do
        if Required and not ReadOnly and (FieldKind = fkData) and IsNull then
        begin
          FocusControl;
          DatabaseErrorFmt(SFieldRequired, [DisplayName]);
        end;
  end;
begin
  Assert(Data <> nil);
  if not CachedUpdates then begin
    UpdateRecord;
    case State of
      dsEdit, dsInsert:
        begin
        {$IFDEF CLR}
          DataEvent(deCheckBrowseMode, nil);
        {$ELSE}
          DataEvent(deCheckBrowseMode, 0);
        {$ENDIF}
          CheckRequiredFields;
          UpdateCursorPos;

          InternalDeferredPost;

          if OldDeferredPostBuf = nil then begin
            OldDeferredPostBuf := AllocRecordBuffer;
            Data.CreateComplexFields(OldDeferredPostBuf, False);
            PRecInfo(PtrOffset(OldDeferredPostBuf, FRecInfoOfs)).RefComplexFields := True; //own complex fields
          end;
          CopyRecordBuffer(IntPtr(ActiveBuffer), OldDeferredPostBuf);
          FInDeferredPost := True;
        end;
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetDefaultExpressionValues;
var
  i: integer;
  Val: string;
  Field: TField;
begin
  FInSettingDefaultExpressionValues := True;
  try
    for i := 0 to FieldCount - 1 do begin
      Field := Fields[i];
      if Field.DefaultExpression <> '' then begin
        Val := Field.DefaultExpression;
        if not DefaultExpressionOldBehavior then
          Val := AnsiDequotedStr(Val, '''');
        Field.AsString := Val;
      end;
    end;
  finally
    FInSettingDefaultExpressionValues := False;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoOnNewRecord;
var
  DataSet: TDataSet;
  MasterField, DetailField: TField;
  MasterName, DetailName: string;
  MasterPos, DetailPos: integer;

  procedure LinkMDFields(const MasterName, DetailName: string);
  begin
    MasterField := DataSet.FindField(MasterName);
    if Assigned(MasterField) then begin
      DetailField := FindField(DetailName);
      if Assigned(DetailField) and not DetailField.ReadOnly then begin // CR 11917
      {$IFDEF VER6P}
        if DetailField is TLargeintField then
          TLargeintField(DetailField).AsLargeInt := MasterField.Value
        else
      {$ENDIF}
          DetailField.Assign(MasterField);
      end;
    end;
  end;

begin
  SetDefaultExpressionValues;

  try
    inherited;
  except
    InternalCancel;
    raise;
  end;

  if (DataSource <> nil) then begin
    DataSet := DataSource.DataSet;
    if (DataSet <> nil) and DataSet.Active then begin
      //MD link by MasteFields and DetailFields
      if (FMasterFields <> '') and (FDetailFields <> '') then begin
          MasterPos := 1;
          DetailPos := 1;
          while True do begin
            MasterName := ExtractFieldName(FMasterFields, MasterPos);
            DetailName := ExtractFieldName(FDetailFields, DetailPos);
            if (MasterName <> '') and (DetailName <> '') then
              LinkMDFields(MasterName, DetailName)
            else
              break;
          end;
      end;
      //We couldn't link MD fields in case of undefined FMasterFields or FDetailFields
      //cause there is could be field names mismatch
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoPerformAppend;
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoPerformAppend;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoPerformDelete;
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoPerformDelete;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoPerformUpdate;
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoPerformUpdate;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoApplyRecord(UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean);
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoApplyRecord(UpdateKind, Action, LastItem);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoCacheChanged;
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoCacheChanged;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoCacheApplied;
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoCacheApplied;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoCacheCanceled;
begin
  if (FDataSetService <> nil) and (FDataSetService.FUpdater <> nil) then
    FDataSetService.FUpdater.DoCacheCanceled;
end;

{ Filter / Locate / Find }
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoGetCachedFields;
var
  i: Integer;
  Field: TField;
  FieldDesc: TFieldDesc;
  CalcFieldCount: integer;
begin
  if Data = nil then
    exit;

  CalcFieldCount := 0;
{$IFDEF VER20P}
  if lcPersistent in Fields.LifeCycles then begin
{$ELSE}
  if not DefaultFields then begin
{$ENDIF}
    for i := 0 to Fields.Count - 1 do begin
      Field := Fields[i];
      case Field.FieldKind of
        fkCalculated, fkLookup: begin
          FieldDesc := Data.CreateFieldDesc;
          try
            FieldDesc.ActualName := Field.{$IFNDEF FPC}FullName{$ELSE}FieldName{$ENDIF};
            FieldDesc.Name := Field.{$IFNDEF FPC}FullName{$ELSE}FieldName{$ENDIF};
            FieldDesc.FieldNo := Data.Fields.Count + 1;
            FieldDesc.DataType := GetFieldTypeMapClass.GetDataType(Field.DataType);
            case Field.DataType of
              ftWideString:
                FieldDesc.Size := (Field.Size + 1) * SizeOf(WideChar);
              ftDate, ftTime:
                FieldDesc.Size := Field.DataSize * 2;
              else
                FieldDesc.Size := Field.DataSize;
            end;
            FieldDesc.Length := Field.Size;
            if FCacheCalcFields then
              FieldDesc.FieldDescKind := fdkCached
            else
              FieldDesc.FieldDescKind := fdkCalculated;

            // FieldDescs with CachedField=True must be positioned after data FieldDescs
            Data.Fields.Add(FieldDesc);
            Inc(CalcFieldCount);
            SetLength(FCalcFieldsMapping, CalcFieldCount);
            FCalcFieldsMapping[CalcFieldCount - 1].Field := Field;
            FCalcFieldsMapping[CalcFieldCount - 1].FieldDesc := FieldDesc;
          except
            FieldDesc.Free;
            raise;
          end;
        end;
      end;
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DoGetCachedBuffer(Buffer: IntPtr; Source: IntPtr = nil);
var
  RecBuf: IntPtr;
begin
  if (CalcFieldsSize > 0) then begin
    Assert(Data <> nil);
    RecBuf := Marshal.AllocHGlobal(Data.RecordSize + Data.CalcRecordSize + SizeOf(TRecInfo) + BookmarkSize);
    try
      if Source = nil then
        CopyBuffer(Buffer, RecBuf, Data.RecordSize)
      else
        CopyBuffer(Source, RecBuf, Data.RecordSize);

      GetCalcFields({$IFDEF VER18P}NativeInt{$ENDIF}(RecBuf));
      CopyBuffer(RecBuf, Buffer, Data.RecordSize + Data.CalcRecordSize);
    finally
      Marshal.FreeHGlobal(RecBuf);
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ActivateFilters;
begin
  if Data = nil then
    Exit;

  DeactivateFilters;

  if Assigned(OnFilterRecord) then
    Data.FilterFunc := RecordFilter;
  if Trim(Filter) <> '' then
    Data.FilterText := Filter;

  Data.FilterCaseInsensitive := foCaseInsensitive in FilterOptions;
  Data.FilterNoPartialCompare := foNoPartialCompare in FilterOptions;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DeactivateFilters;
begin
  if Data = nil then
    Exit;
  Data.FilterFunc := nil;
  Data.FilterText := '';
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.RecordFilter(RecBuf: IntPtr): boolean;
var
  Accept: boolean;
  SaveState: TDataSetState;
begin
  SaveState := SetTempState(dsFilter);
  FFilterBuffer := RecBuf;
  try
    Accept := True;
    OnFilterRecord(Self, Accept);
  except
    InternalHandleException;
  end;
  RestoreState(SaveState);
  Result := Accept;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.RecordFilterRange(RecBuf: IntPtr): boolean;
var
  i, CompareResult: Integer;
  FieldDesc: TFieldDesc;
  RecInfo: PRecInfo;
begin
  Result := True;
  for i := 0 to High(TMemData(Data).IndexFields) do begin
    FieldDesc := TMemData(Data).IndexFields[i].FieldDesc;
    if not Data.GetNull(FieldDesc, FRangeStartBuffer) then begin
      CompareResult := Data.CompareFields(FRangeStartBuffer, RecBuf, TMemData(Data).IndexFields[i].FieldDesc, []);
      RecInfo := PRecInfo(PtrOffset(FRangeStartBuffer, FRecInfoOfs));
      if (CompareResult > 0) or ((CompareResult = 0) and RecInfo.KeyExclusive) then begin
        Result := False;
        Exit;
      end;
    end;
    if not Data.GetNull(FieldDesc, FRangeEndBuffer) then begin
      CompareResult := Data.CompareFields(FRangeEndBuffer, RecBuf, TMemData(Data).IndexFields[i].FieldDesc, []);
      RecInfo := PRecInfo(PtrOffset(FRangeEndBuffer, FRecInfoOfs));
      if (CompareResult < 0) or ((CompareResult = 0) and RecInfo.KeyExclusive) then begin
        Result := False;
        Exit;
      end;
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFiltered(Value: boolean);
begin
  if Active then begin
    CheckBrowseMode;
    UpdateCursorPos;
  end;

  if Value <> Filtered then begin
    if Value then
      ActivateFilters
    else
      DeactivateFilters;

    inherited SetFiltered(Value);

    if Active then begin
      Assert(Data <> nil);
      Data.FilterUpdated;
      Resync([]);
      First;
      // DoAfterScroll;
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFilterData(const Text: string; Options: TFilterOptions);
begin
  if Active then begin
    CheckBrowseMode;
    UpdateCursorPos;
  end;

  if (Text <> Filter) or (Options <> FilterOptions) then begin

    if Data <> nil then begin
      Data.FilterCaseInsensitive := foCaseInsensitive in Options;
      Data.FilterNoPartialCompare := foNoPartialCompare in Options;

      if Filtered and (Trim(Text) <> '') then
        Data.FilterText := Text
      else
        Data.FilterText := '';
    end;

    inherited SetFilterText(Text);
    inherited SetFilterOptions(Options);

    if Active and Filtered then begin
      Data.FilterUpdated;
      Resync([]);
      First;
      // DoAfterScroll;
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFilterOptions(Value: TFilterOptions);
begin
  SetFilterData(Filter, Value);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetFilterText(const Value: string);
begin
  SetFilterData(Value, FilterOptions);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetOnFilterRecord(const Value: TFilterRecordEvent);
begin
  if Active then begin
    CheckBrowseMode;
    UpdateCursorPos;
  end;

  inherited SetOnFilterRecord(Value);

  if Data = nil then
    Exit;

  if Filtered and Assigned(OnFilterRecord) then
    Data.FilterFunc := RecordFilter
  else
    Data.FilterFunc := nil;

  if Active then begin
    Data.FilterUpdated;
    Resync([]);
    First;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.FindRecord(Restart, GoForward: boolean): boolean;
begin
  CheckBrowseMode;
  DoBeforeScroll;
  SetFound(False);
  UpdateCursorPos;
  CursorPosChanged;
  Assert(Data <> nil);
  if not Filtered then begin
    ActivateFilters;
    if Data is TMemData then
      TMemData(Data).ClearItemsOmittedStatus; /// CR DAC22529
  end;
  try
    if GoForward then begin
      if Restart then
        Data.SetToBegin;
      Data.GetNextRecord(nil);
    end
    else begin
      if Restart then
        Data.SetToEnd;
      Data.GetPriorRecord(nil);
    end;
  finally
    if not Filtered then begin
      DeactivateFilters;
      if Data is TMemData then
        TMemData(Data).ClearItemsOmittedStatus; /// CR DAC22529
    end;
  end;

  if not Data.BOF and not Data.EOF then begin
    Resync([rmExact, rmCenter]);
    SetFound(True);
  end;
  Result := Found;
  if Result then
    DoAfterScroll;
end;

{ Master/Detail }

{$IFDEF VER17P}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.AddFieldToList(const FieldName: string; DataSet: TDataSet; List: TList<TField>): boolean;
{$ELSE}
function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.AddFieldToList(const FieldName: string; DataSet: TDataSet; List: TList): boolean;
{$ENDIF}
var
  Field: TField;
begin
  Field := DataSet.FindField(FieldName);
  if (Field <> nil) then
    List.Add(Field);
  Result := Field <> nil;
end;

{$IFDEF VER17P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetDetailLinkFields(MasterFields, DetailFields: TList<TField>);
{$ELSE}{$IFDEF CLR}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetDetailLinkFields(MasterFields, DetailFields: TObjectList);
{$ELSE}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetDetailLinkFields(MasterFields, DetailFields: TList);
{$ENDIF}{$ENDIF}
var
  DataSet: TDataSet;
  MasterName, DetailName: string;
  MasterPos, DetailPos: integer;
begin
  MasterFields.Clear;
  DetailFields.Clear;
  if (DataSource <> nil) and (DataSource.DataSet <> nil) then begin
    if (Self.MasterFields <> '') and (Self.DetailFields <> '') then begin
      DataSet := DataSource.DataSet;
      if (DataSet <> nil) and DataSet.Active then begin
        MasterPos := 1;
        DetailPos := 1;
        while True do begin
          MasterName := ExtractFieldName(FMasterFields, MasterPos);
          DetailName := ExtractFieldName(FDetailFields, DetailPos);
          if (MasterName = '') or (DetailName = '') then
            Break;
          if AddFieldToList(MasterName, DataSource.DataSet, MasterFields) then
            AddFieldToList(DetailName, Self, DetailFields);
        end;
      end;
    end;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetDataSource: TDataSource;
begin
  Result := FDataLink.DataSource;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.IsMasterDatasetActive: boolean;
begin
  Result := (FDataLink.DataSource <> nil) and (FDataLink.DataSource.DataSet <> nil)
    and FDataLink.DataSource.DataSet.Active;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.IsConnectedToMaster: boolean;
begin
  Result := (MasterSource <> nil) and (FMasterFields <> '') and (FDetailFields <> '');
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetLocalMDLinks: boolean;
var
  MemDataSet: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF};
  i: integer;
  RecBuf: TRecordBuffer;

  MasterField: TField;
  MasterFieldDesc: TFieldDesc;
  DetailField: TField;
  DetailFieldDesc: TFieldDesc;
  MasterPos: integer;
  DetailPos: integer;
  MasterName: string;
  DetailName: string;
  LinksCount: integer;
begin
  Result := True;
  if Length(FLocalMDLinks) > 0 then
    for i := 0 to Length(FLocalMDLinks) - 1 do
      if not FLocalMDLinks[i].NativeBuffer then
        Marshal.FreeHGlobal(FLocalMDLinks[i].Buffer);

  if IsConnectedToMaster and IsMasterDatasetActive then begin
    if not (DataSource.DataSet is {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}) then
      raise Exception.Create(SMDIsNotMemDataSet);

    MemDataSet := {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(DataSource.DataSet);
    LinksCount := 0;
    MasterPos := 1;
    DetailPos := 1;
    while True do begin
      MasterName := ExtractFieldName(FMasterFields, MasterPos);
      DetailName := ExtractFieldName(FDetailFields, DetailPos);
      if (MasterName <> '') and (DetailName <> '') then begin
        MasterField := MemDataSet.FindField(MasterName);
        if Assigned(MasterField) then begin
          DetailField := FindField(DetailName);
          if Assigned(DetailField) then begin
            SetLength(FLocalMDLinks, LinksCount + 1);
            with FLocalMDLinks[LinksCount] do begin
              DetailFieldDesc := Data.FindField(DetailField.FieldName);
              MasterFieldDesc := MemDataSet.GetFieldDesc(MasterField);
              if DetailFieldDesc = nil then
                raise Exception.Create(Format(SFieldNotFound, [DetailField.FieldName]));
              if MasterFieldDesc = nil then
                raise Exception.Create(Format(SFieldNotFound, [MasterField.FieldName])); //TODO: Field : cannot be used for local master/detail link
              FieldNo := DetailFieldDesc.FieldNo;
              Buffer := nil;
              IsNull := MasterField.IsNull;
              NativeBuffer := False;
              if not IsNull then
                if MemDataSet.Data.IsEqualDataType(DetailFieldDesc, MasterFieldDesc) then begin
                  if MemDataSet.GetActiveRecBuf(RecBuf) then
                    Buffer := MemDataSet.Data.GetFieldBuf(RecBuf, MasterFieldDesc, BufferType, IsNull, NativeBuffer);
                end
                else
                  CopyFieldValue(MasterField.Value, Buffer, BufferType, DetailFieldDesc.MappedDataType);
            end;
            Inc(LinksCount);
          end;
        end;
      end
      else
        break;
    end;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.MDLinksRefreshed: boolean;
begin
  Result := SetLocalMDLinks;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.MasterRecordChanged;
var
  DataSet: TDataSet;
begin
  FreeRefBuffers;

  if FDataLink.DataSource <> nil then begin
    DataSet := FDataLink.DataSource.DataSet;
    if DataSet <> nil then
      if DataSet.Active and (DataSet.State <> dsSetKey) then begin
        if MDLinksRefreshed then begin // need refresh
        {$IFDEF MSWINDOWS}
          if (FDetailRefreshTimer <> nil) and (FDetailRefreshTimer.Interval <> 0) then begin
            FDetailRefreshTimer.Enabled := False; //reset time period
            FDetailRefreshTimer.Enabled := True;
          end
          else
        {$ENDIF}
            RefreshDetail(nil);
        end;
      end;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.UseLocalMasterDetailFilter: boolean;
begin
  Result := IsConnectedToMaster;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.RefreshDetail(Sender: TObject);
begin
{$IFDEF MSWINDOWS}
  if FDetailRefreshTimer <> nil then
    FDetailRefreshTimer.Enabled := False;
{$ENDIF}
  if not Active then
    Exit;

  if UseLocalMasterDetailFilter then begin
    Data.FilterUpdated;
    Resync([]);
    First;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.LocalDetailFilter(RecBuf: IntPtr): boolean;
var
  i: integer;
  DataSet: TDataSet;
  FieldDesc: TFieldDesc;
begin
  Result := True;

  if FDataLink.DataSource <> nil then begin
    DataSet := FDataLink.DataSource.DataSet;
    if (DataSet <> nil) and DataSet.Active then begin
      // RefreshParamsOnInsert - returns old behavior
      Result := RefreshParamsOnInsert or (DataSet.RecordCount <> 0);
      if not Result then
        Exit;
    end;
  end;

  for i := 0 to Length(FLocalMDLinks) - 1 do
    with FLocalMDLinks[i] do begin
      if not Result then
        Break;
      FieldDesc := GetFieldDesc(FieldNo);
      if IsNull or Data.GetNull(FieldDesc, RecBuf) then
        Result := IsNull and Data.GetNull(FieldDesc, RecBuf)
      else
        Result := Data.CompareFieldValue(Buffer, BufferType, FieldDesc, RecBuf, []) = 0;
    end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetLocalDetailFilter;
begin
  if Data <> nil then
    if UseLocalMasterDetailFilter then
      Data.FilterMDFunc := LocalDetailFilter
    else
      Data.FilterMDFunc := nil;
end;

{$IFDEF MSWINDOWS}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CheckRefreshDetailTimer;
begin
  if FDetailRefreshTimer = nil then begin
    FDetailRefreshTimer := TWin32Timer.Create(Self);
    FDetailRefreshTimer.Enabled := False;
    FDetailRefreshTimer.OnTimer := RefreshDetail;
  end;
end;
{$ENDIF}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetDetailDelay(Value: integer);
begin
  FDetailDelay := Value;
{$IFDEF MSWINDOWS}
  CheckRefreshDetailTimer;
  FDetailRefreshTimer.Interval := Value;
{$ENDIF}
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.MDPropertiesChanged;
begin
  if Active then begin
    MDLinksRefreshed;
    Data.FilterUpdated;
    Resync([]);
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetMasterSource(Value: TDataSource);
var
  NeedRefresh: boolean;
begin
  if FDataLink.DataSource <> Value then begin
    if IsLinkedTo(Value) then
      DatabaseError(SCircularDataLink);
    NeedRefresh := IsConnectedToMaster;
    FDataLink.DataSource := Value;
    NeedRefresh := NeedRefresh or IsConnectedToMaster;
    SetLocalDetailFilter;

    if NeedRefresh then
      MDPropertiesChanged;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetMasterFields(Value: string);
var
  NeedRefresh: boolean;
begin
  if Value <> FMasterFields then begin
    NeedRefresh := IsConnectedToMaster;
    FMasterFields := Value;
    NeedRefresh := NeedRefresh or IsConnectedToMaster;
    SetLocalDetailFilter;

    if NeedRefresh then
      MDPropertiesChanged;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetDetailFields(Value: string);
var
  NeedRefresh: boolean;
begin
  if Value <> FDetailFields then begin
    NeedRefresh := IsConnectedToMaster;
    FDetailFields := Value;
    NeedRefresh := NeedRefresh or IsConnectedToMaster;
    SetLocalDetailFilter;

    if NeedRefresh then
      MDPropertiesChanged;
  end;
end;

// Allocate memory for Value (ValuePtr - must call FreeMem!) and copy Value to ValuePtr
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CopyFieldValue(const Value: variant; out ValuePtr: IntPtr;
  out ValueType: Word; FieldType: Word; UseFieldType: boolean = True);
var
  sa: AnsiString;
  l: integer;
  Temp: IntPtr;
  ws: WideString;
  b: boolean;
  lw: longword;
  i64: Int64;
  ASingle: single;
{$IFDEF CLR}
  Data: TBytes;
{$ELSE}
  c: currency;
{$ENDIF}
{$IFDEF VER6P}
  ui64: UInt64;
{$IFNDEF FPC}
  bcd: TBcd;
{$ENDIF}
{$ENDIF}
begin
  if VarType(Value) in [varEmpty, varNull] then begin
    ValuePtr := nil;
    Exit;
  end;

  ValueType := FieldType;

  if VarIsStr(Value) then begin
    if UseFieldType then begin
      case FieldType of
        dtInt8, dtUInt8, dtInt16, dtUInt16, dtInt32: begin
          CopyFieldValue(StrToInt(Value), ValuePtr, ValueType, FieldType);
          Exit;
        end;
        dtDate: begin
          CopyFieldValue(StrToDate(Value), ValuePtr, ValueType, FieldType);
          Exit;
        end;
        dtTime: begin
          CopyFieldValue(StrToTime(Value), ValuePtr, ValueType, FieldType);
          Exit;
        end;
        dtDateTime: begin
          CopyFieldValue(StrToDateTime(Value), ValuePtr, ValueType, FieldType);
          Exit;
        end;
        dtFloat, dtBCD{$IFDEF VER6P}{$IFNDEF FPC}, dtFMTBCD{$ENDIF}{$ENDIF}, dtCurrency: begin
          CopyFieldValue(StrToFloat(Value), ValuePtr, ValueType, FieldType);
          Exit;
        end;
        dtSingle: begin
          ASingle := StrToFloat(Value);
          CopyFieldValue(ASingle, ValuePtr, ValueType, FieldType);
          Exit;
        end;
      {$IFDEF VER6P}
        dtInt64, dtUInt64, dtUInt32: begin
          CopyFieldValue(StrToInt64(Value), ValuePtr, ValueType, FieldType);
          Exit;
        end;
      {$ENDIF}
      end;
    end
    else
      if not (FieldType in [dtBoolean, dtBytes, dtVarBytes, dtExtVarBytes, dtWideString, dtExtWideString, dtWideMemo]) then
        ValueType := dtString;
  end;

  case ValueType of
    dtBoolean: begin
      b := Value;
      ValuePtr := Marshal.AllocHGlobal(SizeOf(Byte));
      Marshal.WriteByte(ValuePtr, Byte(b));
    end;
    dtInt8, dtUInt8, dtInt16, dtUInt16, dtInt32: begin
      ValuePtr := Marshal.AllocHGlobal(SizeOf(integer));
      Marshal.WriteInt32(ValuePtr, Value);
    end;
    dtUInt32: begin
      ValuePtr := Marshal.AllocHGlobal(SizeOf(longword));
      lw := Value;
      Marshal.WriteInt32(ValuePtr, integer(lw));
    end;
    dtInt64: begin
    {$IFDEF VER6P}
      ValuePtr := Marshal.AllocHGlobal(SizeOf(Int64));
      i64 := Value;
      Marshal.WriteInt64(ValuePtr, i64);
    {$ELSE}
      GetMem(ValuePtr, SizeOf(Int64));
      if TVarData(Value).VType = varDecimal then
        Int64(ValuePtr^) := TVarDataD6(Value).VInt64
      else
        Int64(ValuePtr^) := StrToInt64(Value);
    {$ENDIF}
    end;
    dtUInt64: begin
    {$IFDEF VER6P}
      ValuePtr := Marshal.AllocHGlobal(SizeOf(Int64));
      ui64 := Value;
      Marshal.WriteInt64(ValuePtr, Int64(ui64));
    {$ELSE}
      GetMem(ValuePtr, SizeOf(Int64));
      if TVarData(Value).VType = varDecimal then
        Int64(ValuePtr^) := TVarDataD6(Value).VInt64
      else
        Int64(ValuePtr^) := StrToInt64(Value);
    {$ENDIF}
    end;
    dtFloat, dtCurrency: begin
      ValuePtr := Marshal.AllocHGlobal(SizeOf(Int64));
      Marshal.WriteInt64(ValuePtr, BitConverter.DoubleToInt64Bits(Value));
    end;
    dtSingle: begin
      ValuePtr := Marshal.AllocHGlobal(SizeOf(integer));
      Marshal.WriteInt32(ValuePtr, CRBitConverter.SingleToInt32Bits(Value));
    end;
    dtBCD: begin
      ValuePtr := Marshal.AllocHGlobal(SizeOf(Currency));
    {$IFDEF CLR}
      i64 := Round(double(Value) * 10000);
    {$ELSE}
      c := Value;
      i64 := Int64((@c)^);
    {$ENDIF}
      Marshal.WriteInt64(ValuePtr, i64);
    end;
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    dtFmtBCD: begin
      ValuePtr := Marshal.AllocHGlobal(SizeOfTBcd);
      bcd := VarToBcd(Value);
    {$IFDEF CLR}
      Data := TBcd.ToBytes(bcd);
      Marshal.Copy(Data, 0, ValuePtr, SizeOfTBcd);
    {$ELSE}
      PBcd(ValuePtr)^ := bcd;
    {$ENDIF}
    end;
  {$ENDIF}
  {$ENDIF}
    dtString, dtExtString, dtBlob, dtMemo, dtGuid, dtVariant: begin
      sa := AnsiString(Value);
      l := LengthA(sa) + 1;
      ValuePtr := Marshal.AllocHGlobal(l);
      CopyBufferAnsi(sa, ValuePtr, l);
      ValueType := dtString;
    end;
    dtWideString, dtExtWideString, dtWideMemo: begin
      ws := Value;
      l := (Length(ws) + 1) * SizeOf(WideChar);
      ValuePtr := Marshal.AllocHGlobal(l);
      CopyBufferUni(ws, ValuePtr, l);
      ValueType := dtWideString;
    end;
    dtDateTime, dtDate, dtTime: begin
      ValueType := dtFloat;
      ValuePtr := Marshal.AllocHGlobal(SizeOf(Int64));
      Marshal.WriteInt64(ValuePtr, BitConverter.DoubleToInt64Bits(TDateTime(Value)));
    end;
    dtBytes, dtVarBytes, dtExtVarBytes: begin
      ValueType := dtVarBytes;

      if VarIsStr(Value) then begin
        sa := AnsiString(Value);
        l := LengthA(sa);
        ValuePtr := Marshal.AllocHGlobal(l + SizeOf(Word));

        Temp := Marshal.StringToHGlobalAnsi(sa);
        try
          CopyBuffer(Temp, PtrOffset(ValuePtr, SizeOf(Word)), l);
        finally
          Marshal.FreeCoTaskMem(Temp);
        end;
        Marshal.WriteInt16(ValuePtr, l);
      end
      else begin
        Assert(VarType(Value) = varArray + varByte);
      {$IFDEF CLR}
        SetLength(Data, VarArrayHighBound(Value, 1) + 1);
        for l := 0 to High(Data) do
          Data[l] := VarArrayGet(Value, l);
        ValuePtr := Marshal.AllocHGlobal(Length(Data) + sizeof(word));
        Marshal.WriteInt16(ValuePtr, Length(Data));
        Marshal.Copy(Data, 0, PtrOffset(ValuePtr, sizeof(word)), Length(Data));
      {$ELSE}
        GetMem(ValuePtr, TVarData(Value).VArray.Bounds[0].ElementCount + SizeOf(Word));
        Move(TVarData(Value).VArray.Data^, PtrOffset(ValuePtr, SizeOf(Word))^, TVarData(Value).VArray.Bounds[0].ElementCount);
        Word(ValuePtr^) := TVarData(Value).VArray.Bounds[0].ElementCount;
      {$ENDIF}
      end;
    end;
  else
    raise EConvertError.Create(SCannotConvertType);
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalLocateRecord(KeyFields: TDAList; KeyValues: variant;
  Options: TLocateExOptions; SavePos: boolean): boolean;
var
  FieldDesc: TFieldDesc;
  RecBuf: TRecordBuffer;
  Values: array of IntPtr;
  Types: array of Word;
  i, FieldCount: integer;
  IndexedFieldCount: integer;
  Bookmark: PRecBookmark;
  Res: integer;
  CalcKeyFields: boolean;
  FirstRecNo, LastRecNo, RecCount: Integer;
  PartialSearchFirstEntry: Boolean;
  CompareOptions: TCompareOptions;
  CanUseGoldenSection: Boolean;
  FieldBlank: boolean;

  procedure SetKeyFields;
  var
    i, j: integer;
    Index: integer;
    Value: variant;
  begin
    CalcKeyFields := False;
    FieldCount := KeyFields.Count;
    SetLength(Values, FieldCount);
    for i := 0 to FieldCount - 1 do begin
      Values[i] := nil; // Clear Values array to prevent AV in 'finally' section after Exception

      if TFieldDesc(KeyFields[i]).FieldDescKind = fdkCalculated then
        CalcKeyFields := True;
    end;

    IndexedFieldCount := 0;
    //lxPartialKey and lxPartialCompare incompatible with ordered locate
    //lxNearest can be partially supported in the next versions
    if  Data is TMemData then
      if not ((lxPartialKey in Options) or (lxPartialCompare in Options) or (lxNearest in Options)) then begin
        for i := 0 to Length(TMemData(Data).IndexFields) - 1 do begin
          //Check ordered locate posibility for current Index field
          if (TMemData(Data).IndexFields[i].SortType = stCaseInsensitive) xor (lxCaseInsensitive in Options) then
            Break;

          //First IndexFields should be in KeyFields if not then we can't use ordered Locate
          Index := KeyFields.IndexOf(TMemData(Data).IndexFields[i].FieldDesc);
          if (Index > -1) then begin
            if IndexedFieldCount <> Index then begin
              FieldDesc := TFieldDesc(KeyFields[IndexedFieldCount]);
              KeyFields[IndexedFieldCount] := KeyFields[Index];
              KeyFields[Index] := FieldDesc;

              if not VarIsArray(KeyValues) then begin
                Value := KeyValues;
                KeyValues := VarArrayCreate([0, FieldCount - 1], varVariant);
                KeyValues[i] := Value;
                for j := 1 to FieldCount - 1 do
                  KeyValues[j] := Unassigned;
              end;

              Value := KeyValues[IndexedFieldCount];
              KeyValues[IndexedFieldCount] := KeyValues[Index];
              KeyValues[Index] := Value;
            end;
            Inc(IndexedFieldCount);
          end
          else
            break;
        end;
      end;

    SetLength(Types, FieldCount);
    for i := 0 to FieldCount - 1 do begin
      Value := Unassigned;
      if VarIsArray(KeyValues) and ((FieldCount > 1) or (VarArrayHighBound(KeyValues, 1) = 0)) then
        if i <= VarArrayHighBound(KeyValues, 1) then
          Value := KeyValues[i]
        else
          Value := Null
      else
        if i = 0 then
          Value := KeyValues
        else
          Value := Null;
      // string values should be converted to field native types only when they are used
      // in the golden section search (for compatibility)
      CopyFieldValue(Value, Values[i], Types[i], TFieldDesc(KeyFields[i]).MappedDataType, i < IndexedFieldCount);
    end;
  end;

  function GetGoldenSectionDir(ReadRecBuf: boolean): integer;
  var
    i: integer;
    Dir: Integer;
    Options: TCompareOptions;
  begin
    Result := 0;
    i := 0;
    if ReadRecBuf then
      Data.GetRecord(RecBuf);

    if CalcKeyFields then
      GetCalcFields({$IFDEF VER18P}NativeInt{$ENDIF}(RecBuf));

    while (Result = 0) and (i < IndexedFieldCount) do begin
      FieldDesc := TFieldDesc(KeyFields[i]);

      FieldBlank := Data.GetNull(FieldDesc, RecBuf);
      if Data is TMemData then
        Options := Data.GetSortOptions(TMemData(Data).IndexFields[i])
      else
        Options := [];

      if (Data is TMemData) and TMemData(Data).IndexFields[i].DescendingOrder then
        Dir := -1
      else
        Dir := 1;

      if FieldBlank and (Values[i] = nil) then
        Result := 0
      else
      if FieldBlank and not (Values[i] = nil) then
        if coInvertNullOrder in Options then
          Result := -1
        else
          Result := 1
      else
      if not FieldBlank and (Values[i] = nil) then
        if coInvertNullOrder in Options then
          Result := 1
        else
          Result := -1
      else begin
        if (Data is TMemData) and (TMemData(Data).IndexFields[i].SortType = stBinary) then
          Include(CompareOptions, coOrdinalCompare)
        else
          Exclude(CompareOptions, coOrdinalCompare);
        Result := Data.CompareFieldValue(Values[i], Types[i], FieldDesc, RecBuf, CompareOptions);
      end;

      if Result <> 0 then
        Result := Result * Dir;
      Inc(i);
    end;
  end;

  function ExecGoldenSection: boolean;
  var
    First, Last, Current: Integer;
    Dir: Integer;

  begin
    Result := False;

    if FirstRecNo > LastRecNo then Exit; // cr - 23687

    First := FirstRecNo;
    Last  := LastRecNo;

    Data.RecordNo := First;
    Dir := GetGoldenSectionDir(True);
    if Dir <= 0 then begin
      Result := Dir = 0;
      Exit;
    end;

    Data.RecordNo := Last;
    Dir := GetGoldenSectionDir(True);
    if Dir >= 0 then begin
      Result := Dir = 0;
      Exit;
    end;

    repeat
      Current := (Last + First) div 2;
      Data.RecordNo := Current;
      Dir := GetGoldenSectionDir(True);
      if Dir < 0 then
        Last := Current
      else
      if Dir > 0 then
        First := Current
      else begin
        Result := True;
        Break;
      end;
    until  Last - First <= 1;
  end;

begin
  Result := False;
  if KeyFields.Count = 0 then
    Exit;

  CheckBrowseMode;
  CursorPosChanged;
  UpdateCursorPos;

  Assert(Data <> nil);
  FieldCount := 0;

  RecBuf := TRecordBuffer(TempBuffer);
  FreeRefComplexFields(RecBuf);

  CompareOptions := [];
  if lxCaseInsensitive in Options then
    Include(CompareOptions, coCaseInsensitive);
  if lxPartialKey in Options then
    Include(CompareOptions, coPartialKey);
  if lxPartialCompare in Options then
    Include(CompareOptions, coPartialCompare);

  Values := nil;
  Bookmark := Marshal.AllocHGlobal(sizeof(TRecBookmark));
  try
    CanUseGoldenSection := True;
    try
      SetKeyFields;
    except
      CanUseGoldenSection := False;
    end;

    Data.GetBookmark(Bookmark);

    //Set locate dimensions. This dimensions should be used for lxNearest ordered implementation
    FirstRecNo := 1;
    LastRecNo := Data.RecordCount;

    if (lxNext in Options) then begin//Search from current position
      if IndexedFieldCount > 0 then begin
        Data.GetNextRecord(RecBuf);  //Next RecNo in case of Ordered Search
        if Data.Eof then begin
          if not Data.Bof {Empty Data} then
            Data.SetToBookmark(Bookmark);
          exit;
        end;
      end;
      FirstRecNo := Data.RecordNo;
    end;

    if (lxUp in Options) then begin  //Search from current position downto first
      if IndexedFieldCount > 0 then
        Data.GetPriorRecord(RecBuf); //Prior RecNo in case of Ordered Search
      LastRecNo := Data.RecordNo;
    end;

    if (IndexedFieldCount > 0) and CanUseGoldenSection then begin
      if Data is TMemData then begin
        TMemData(Data).PrepareRecNoCache(RecCount);
        if RecCount < LastRecNo then
          LastRecNo := RecCount;
      end;
      Result := ExecGoldenSection;

      //Find the first occurence of located data
      if Result then
        repeat

          if (lxUp in Options) then begin
            if Data.RecordNo >= LastRecNo then //top limit of first occurrence search
              break
            else begin
              Data.GetNextRecord(RecBuf);
              if Data.Eof or (GetGoldenSectionDir(False) <> 0) then begin
                if IndexedFieldCount >= FieldCount then  //we shouldn't restore correct position in case of mixed locate
                  Data.GetPriorRecord(RecBuf);   //Restore correct position
                break;
              end;
            end;
          end
          else
            if Data.RecordNo <= FirstRecNo then //bottom limit of first occurrence search
              break
            else begin
              Data.GetPriorRecord(RecBuf);
              if (GetGoldenSectionDir(False) <> 0) then begin
                if IndexedFieldCount >= FieldCount then  //we shouldn't restore correct position in case of mixed locate
                  Data.GetNextRecord(RecBuf);     //Restore correct position
                break;
              end;
            end;

        until Data.Bof or Data.Eof;
    end;


    if (IndexedFieldCount = 0) or not CanUseGoldenSection or ((IndexedFieldCount < FieldCount) and Result) then begin

      if not((lxNext in Options) or (lxUp in Options)) and not (IndexedFieldCount > 0) then
        Data.SetToBegin;

      Exclude(CompareOptions, coOrdinalCompare);
      PartialSearchFirstEntry := (IndexedFieldCount < FieldCount) and Result;
      while True do begin
        if not PartialSearchFirstEntry then
          if lxUp in Options then
            Data.GetPriorRecord(RecBuf)
          else
            Data.GetNextRecord(RecBuf)
        else
          PartialSearchFirstEntry := False;

        if CalcKeyFields then
          GetCalcFields({$IFDEF VER18P}NativeInt{$ENDIF}(RecBuf));

        if not (Data.EOF or Data.BOF) then begin
          Result := True;
          i := 0;
          while Result and (i < FieldCount) do begin
            FieldDesc := TFieldDesc(KeyFields[i]);

            FieldBlank := Data.GetNull(FieldDesc, RecBuf);

            if (Values[i] = nil) or FieldBlank then
              Result := (Values[i] = nil) and FieldBlank
            else begin
              Res := Data.CompareFieldValue(Values[i], Types[i], FieldDesc, RecBuf, CompareOptions);
              Result := (Res = 0) or (Res < 0) and (lxNearest in Options);
            end;
            Inc(i);
          end;

          if Result then
            break;
        end
        else begin
          Result := lxNearest in Options; // goto last record for lxNearest
          break;
        end;
      end;
    end;

    if (SavePos or not Result)
      and not (Data.Eof and Data.Bof {Empty Data}) then
      Data.SetToBookmark(Bookmark);
  finally
    Marshal.FreeHGlobal(Bookmark);
    for i := 0 to FieldCount - 1 do
      Marshal.FreeHGlobal(Values[i]);
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.LocateRecord(const KeyFields: string; const KeyValues: variant;
  Options: TLocateExOptions; SavePos: boolean): boolean;
var
  Fields: TDAList;
  FieldDesc: TFieldDesc;

  procedure ParseKeyFields;
  var
    St: string;
    i: integer;
  begin
    i := 1;
    while True do begin
      St := ExtractFieldName(KeyFields, i);
    {$IFDEF FPC}
      St := Trim(St);
    {$ENDIF}
      if St <> '' then begin
        FieldDesc := Data.FieldByName(St);
        if FieldDesc <> nil then
          Fields.Add(FieldDesc);
      end
      else
        break;
    end;
  end;

begin
  CheckActive;

  Fields := TDAList.Create;
  try
    ParseKeyFields;
    Result := InternalLocateRecord(Fields, KeyValues, Options, SavePos);
  finally
    Fields.Free;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.LocateRecord(const KeyFields: array of TField; const KeyValues: variant;
  Options: TLocateExOptions; SavePos: boolean): boolean;
var
  Fields: TDAList;
  i: integer;
begin
  CheckActive;

  Fields := TDAList.Create;
  try
    for i := 0 to Length(KeyFields) - 1 do begin
      if KeyFields[i] <> nil then
        Fields.Add(GetFieldDesc(KeyFields[i]));
    end;

    Result := InternalLocateRecord(Fields, KeyValues, Options, SavePos);
  finally
    Fields.Free;
  end;
end;

function LocateExOptions(Options: TLocateOptions): TLocateExOptions;
begin
  Result := [];
  if loCaseInsensitive in Options then
    Result := Result + [lxCaseInsensitive];

  if loPartialKey in Options then
    Result := Result + [lxPartialKey];
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Locate(const KeyFields: array of TField; const KeyValues: variant;
  Options: TLocateOptions): boolean;
begin
  DoBeforeScroll;

  Result := LocateRecord(KeyFields, KeyValues, LocateExOptions(Options), False);

  if Result then begin
    Resync([rmExact, rmCenter]);
    DoAfterScroll;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Locate(const KeyFields: string;
  const KeyValues: variant; Options: TLocateOptions): boolean;
begin
  DoBeforeScroll;

  Result := LocateRecord(KeyFields, KeyValues, LocateExOptions(Options), False);

  if Result then begin
    Resync([rmExact, rmCenter]);
    DoAfterScroll;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.LocateEx(const KeyFields: string;
  const KeyValues: variant; Options: TLocateExOptions): boolean;
begin
  DoBeforeScroll;

  Result := LocateRecord(KeyFields, KeyValues, Options, False);

  if Result then begin
    if LocateExOldBehavior or Data.Eof or Data.Bof then
      Resync([{rmExact, rmCenter}])
    else
      Resync([rmExact, rmCenter]);
    DoAfterScroll;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.LocateEx(const KeyFields: array of TField;
  const KeyValues: variant; Options: TLocateExOptions): boolean;
begin
  DoBeforeScroll;

  Result := LocateRecord(KeyFields, KeyValues, Options, False);

  if Result then begin
    if LocateExOldBehavior or Data.Eof or Data.Bof then
      Resync([{rmExact, rmCenter}])
    else
      Resync([rmExact, rmCenter]);
    DoAfterScroll;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.Lookup(const KeyFields: string; const KeyValues: variant;
  const ResultFields: string): variant;
var
  bm: TBookmark;
begin
  Result := Null;

  if Active and Filtered then begin
    bm := GetBookmark;
    try
      CheckBrowseMode;
      CursorPosChanged;
      UpdateCursorPos;

      Data.FilterUpdated;
      Resync([]);
      try
        GotoBookmark(bm);
      except
        First;
      end;
    finally
      if bm <> nil then
        FreeBookmark(bm);
    end;
  end;

  if LocateRecord(KeyFields, KeyValues, [], True) then begin
    SetTempState(dsCalcFields);
    try
      CalculateFields(TempBuffer);
      Result := FieldValues[ResultFields];
    finally
      RestoreState(dsBrowse);
    end;
  end;
end;

{ CachedUpdates }

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalGetUpdateResult: TUpdateRecAction;
begin
  UpdateCursorPos;

  Assert(Data <> nil);
  Result := Data.GetUpdateResult;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CheckCachedUpdateMode;
begin
  if not CachedUpdates then
    DatabaseError(SNotCachedUpdate);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.UpdateStatus: TUpdateStatus;
var
  RecBuf: TRecordBuffer;
begin
  if CachedUpdates and not IsEmpty then begin
    if State = dsCalcFields then
      RecBuf := IntPtr(CalcBuffer)
    else
      RecBuf := IntPtr(ActiveBuffer);

    Result := PRecInfo(PtrOffset(RecBuf, FRecInfoOfs)).UpdateStatus;
  end
  else
    Result := usUnModified;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.UpdateResult: TUpdateAction;
begin
  UpdateCursorPos;

  Assert(Data <> nil);
  if Data.GetUpdateResult = urNone then
    Result := uaApplied
  else
    Result := TUpdateAction(Data.GetUpdateResult);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ApplyUpdates;
begin
  ApplyUpdates([ukUpdate, ukInsert, ukDelete]);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ApplyUpdates(const UpdateRecKinds: TUpdateRecKinds);
begin
  CheckActive;
  FreeRefBuffers;
  if State <> dsBrowse then
    Post;
  CheckCachedUpdateMode;
  UpdateCursorPos;

  Assert(Data <> nil);
  NewCacheRecBuf := AllocRecordBuffer;
  OldCacheRecBuf := AllocRecordBuffer;
  try
    Data.SetCacheRecBuf(NewCacheRecBuf, OldCacheRecBuf);
    Data.ApplyUpdates(UpdateRecKinds);
  finally
    FreeRecordBuffer(NewCacheRecBuf);
    FreeRecordBuffer(OldCacheRecBuf);
    Resync([]);
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CommitUpdates;
begin
  CheckActive;
  CheckCachedUpdateMode;
  FreeRefBuffers;
  UpdateCursorPos;

  Assert(Data <> nil);
  NewCacheRecBuf := AllocRecordBuffer;
  OldCacheRecBuf := AllocRecordBuffer;
  try
    Data.SetCacheRecBuf(NewCacheRecBuf, OldCacheRecBuf);
    Data.CommitUpdates;
  finally
    FreeRecordBuffer(NewCacheRecBuf);
    FreeRecordBuffer(OldCacheRecBuf);
  end;
  Resync([]);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CancelUpdates;
begin
  CheckActive;
  FreeRefBuffers;
  Cancel;
  CheckCachedUpdateMode;
  UpdateCursorPos;
  Assert(Data <> nil);
  Data.CancelUpdates;
  Resync([]);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.RestoreUpdates;
begin
  CheckActive;
  FreeRefBuffers;
  Cancel;
  CheckCachedUpdateMode;
  UpdateCursorPos;
  Assert(Data <> nil);
  Data.RestoreUpdates;
  Resync([]);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.RevertRecord;
begin
  CheckActive;
  FreeRefComplexFields(IntPtr(ActiveBuffer));
  if State in dsEditModes then
    Cancel;
  CheckCachedUpdateMode;
  UpdateCursorPos;
  Assert(Data <> nil);
  Data.RevertRecord;
  Resync([]);
end;

{
procedure TMemDataSet.DoApplyRecord(UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean);
var
  OldModified: boolean;
  UpdateAction: TUpdateAction;
begin
  OldModified := Modified;  // NewValue change Modified ??? or MemDS
  try
    UpdateAction := uaFail;
    try
      if Assigned(OnUpdateRecord) then begin
        OnUpdateRecord(Self, TUpdateKind(UpdateKind), UpdateAction);
        if UpdateAction in [uaAbort] then
          Abort;
      end;
      //else begin
      if not Assigned(OnUpdateRecord) or (UpdateAction = TUpdateAction(uaDefault)) then begin
        case UpdateKind of
          ukUpdate:
            PerformUpdate;
          ukInsert:
            PerformAppend;
          ukDelete:
            PerformDelete;
        end;

        if BatchUpdate then
          if CanFlushBatch or LastItem then
            // Should be flushed here because of Action parameter that should be returned
            // to TMemData.ApplyUpdates function
            FlushBatch
          else begin
            Action := urSuspended;
            Exit;
          end;

        UpdateAction := uaApplied;
      end;
    except
      on E: Exception do
        if IsClass(E, EDatabaseError) and Assigned(OnUpdateError) then begin
          OnUpdateError(Self, EDatabaseError(E), TUpdateKind(UpdateKind), UpdateAction);
          case UpdateAction of
            uaFail:
              raise;
            uaAbort:
              Abort;
          end;
        end
        else
          raise;
    end;
  finally
    SetModified(OldModified);
  end;
  Action := TUpdateRecAction(UpdateAction);
end; }


{ BLOB Support }

{function TMemDataSet.GetBlobData(Field:TField; Buffer: PAnsiChar):TBlobData;
begin
  Result := PBlobDataArray(Buffer + FBlobCacheOfs)[Field.Offset];
end;

procedure TMemDataSet.SetBlobData(Field:TField; Buffer: PAnsiChar; Value:TBlobData);
begin
  if Buffer = ActiveBuffer then
    PBlobDataArray(Buffer + FBlobCacheOfs)[Field.Offset] := Value;
end;

procedure TMemDataSet.ClearBlobCache(Buffer: PAnsiChar);
var
  i: integer;
begin
  for i := 0 to BlobFieldCount - 1 do
    PBlobDataArray(Buffer + FBlobCacheOfs)[i] := '';
end;}

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CloseBlob(Field: TField);
begin
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
begin
  Result := TBlobStream.Create(Field as TBlobField, Mode);
end;

{ Informational }

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.IsSequenced: boolean;
begin
  Result := True;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InCacheProcessing: boolean;
begin
  Assert(Data <> nil);
  Result := Data.InCacheProcessing;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetRange(const StartValues, EndValues: array of const; StartExlusive: Boolean = False; EndExclusive: Boolean = False);
begin
  CheckBrowseMode;
  SetRangeBuffer(FRangeStartBuffer, StartValues, True);
  PRecInfo(PtrOffset(FRangeStartBuffer, FRecInfoOfs)).KeyExclusive := StartExlusive;
  SetRangeBuffer(FRangeEndBuffer, EndValues, True);
  PRecInfo(PtrOffset(FRangeEndBuffer, FRecInfoOfs)).KeyExclusive := EndExclusive;
  ApplyRange;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ApplyRange;
var
  IsModified: Boolean;
begin
  if (State = dsSetKey) then begin
    IsModified := Modified;
    CheckBrowseMode;
    if IsModified then begin
      Data.FilterRangeFunc := RecordFilterRange;
      Data.FilterUpdated;
      Resync([]);
      First;
      FRanged := True;
    end;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.CancelRange;
begin
  if FRanged then begin
    Data.FilterRangeFunc := nil;
    Data.FilterUpdated;
    Resync([]);
    First;
    FRanged := False;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetRangeStart;
begin
  SetRangeBuffer(FRangeStartBuffer, [], True);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetRangeEnd;
begin
  SetRangeBuffer(FRangeEndBuffer, [], True);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.EditRangeStart;
begin
  SetRangeBuffer(FRangeStartBuffer, [], False);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.EditRangeEnd;
begin
  SetRangeBuffer(FRangeEndBuffer, [], False);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetRecordSize: word;
begin
  Assert(Data <> nil);
  Result := word(Data.RecordSize);
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetRecordCount: integer;
begin
  if Active then
    Result := Data.RecordCount
  else
    Result := 0;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetRecNo: integer;
var
  RecBuf: TRecordBuffer;
begin
  if GetActiveRecBuf(RecBuf) then
    Result := PRecInfo(PtrOffset(RecBuf, FRecInfoOfs)).RecordNumber
  else
    Result := 0;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetRecNo(Value: integer);
begin
  CheckBrowseMode;
  DoBeforeScroll;
  Assert(Data <> nil);
  Data.RecordNo := Value;
  Resync([{rmCenter}]);
  DoAfterScroll;
end;

{ More }

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.InternalHandleException;
begin
{$IFDEF VER6P}
  if Assigned(Classes.ApplicationHandleException) then
    Classes.ApplicationHandleException(ExceptObject)
  else
    ShowException(ExceptObject, ExceptAddr)
{$ELSE}
  ApplicationHandleException(Self);
{$ENDIF}
end;

{$IFDEF CLR}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataEvent(Event: TDataEvent; Info: TObject);
{$ELSE} {$IFDEF FPC}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataEvent(Event: TDataEvent; Info: PtrInt);
{$ELSE} {$IFDEF VER16P}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataEvent(Event: TDataEvent; Info: NativeInt);
{$ELSE}
procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.DataEvent(Event: TDataEvent; Info: Longint);
{$ENDIF} {$ENDIF} {$ENDIF}
  procedure CheckIfParentScrolled;
  var
    ParentPosition, I: Integer;
  begin
    if FParentDataSet = nil then
      Exit;
    ParentPosition := 0;
    with FParentDataSet do
      if not IsEmpty then
        for I := 0 to BookmarkSize - 1 do
          ParentPosition := ParentPosition +
            Marshal.ReadByte(IntPtr(ActiveBuffer), FBookmarkOfs + I);
    if (FLastParentPos = 0) or (ParentPosition <> FLastParentPos) then
    begin
      First;
      FLastParentPos := ParentPosition;
    end else
    begin
      UpdateCursorPos;
      Resync([]);
    end;
  end;

begin
  if Event = deParentScroll then
    CheckIfParentScrolled;
  inherited DataEvent(Event, Info);
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.AssignTo(Dest: TPersistent);
begin
  if Dest is {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF} then begin
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).CachedUpdates := CachedUpdates;
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).LocalConstraints := LocalConstraints;
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).LocalUpdate := LocalUpdate;
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).MasterSource := MasterSource;
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).MasterFields := MasterFields;
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).DetailFields := DetailFields;
    {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}(Dest).DetailDelay := DetailDelay;
  end
  else
    inherited;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetCachedUpdates(Value: boolean);
begin
  if FCachedUpdates <> Value then begin
    CheckInactive;
    FCachedUpdates := Value;
    FUpdateRecordTypes := [rtModified, rtInserted, rtUnmodified];
    if Data <> nil then
      Data.CachedUpdates := FCachedUpdates;
  end;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetLocalUpdate(Value: boolean);
begin
  if FLocalUpdate <> Value then begin
    FLocalUpdate := Value;
    if Data <> nil then
      Data.LocalUpdate := FLocalUpdate;
  end;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetUpdatesPending: boolean;
begin
  if Data <> nil then
    Result := Data.UpdatesPending
  else
    Result := False;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetPrepared: boolean;
begin
  if Data <> nil then
    Result := Data.Prepared
  else
    Result := False;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetPrepared(Value: boolean);
begin
  if Value then
    Prepare
  else
    UnPrepare;
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.ConvertUpdateRecordTypes(Value: TUpdateRecordTypes): TItemTypes;
begin
  Result := [];
  if rtUnmodified in Value then
    Result := Result + [isUnmodified];
  if rtModified in Value then
    Result := Result + [isUpdated];
  if rtInserted in Value then
    Result := Result + [isAppended];
  if rtDeleted in Value then
    Result := Result + [isDeleted];
end;

function {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.GetUpdateRecordTypes: TUpdateRecordTypes;
begin
  CheckCachedUpdateMode;

  Result := FUpdateRecordTypes;
end;

procedure {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}.SetUpdateRecordTypes(Value: TUpdateRecordTypes);
begin
  CheckCachedUpdateMode;

  //CheckBrowseMode;
  if Active then begin
    UpdateCursorPos;
    case State of // CR-S22656
      dsEdit, dsInsert:
        begin
          UpdateRecord;
          if Modified then Post else Cancel;
        end;
      dsSetKey:
        Post;
    end;
  end;

  FUpdateRecordTypes := Value;

  if Data <> nil then begin
    Data.FilterItemTypes := ConvertUpdateRecordTypes(Value);

    if Active then
      Resync([]);
  end;
end;

{ TBlobStream }

constructor TBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);
begin
  inherited Create;

  FMode := Mode;
  FField := Field;
  FDataSet := FField.DataSet as {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF};
  FFieldDesc := FDataSet.GetFieldDesc(FField.FieldNo);
  if not FDataSet.GetActiveRecBuf(FBuffer) then
    Exit;
{  if FDataSet.State = dsFilter then
    DatabaseErrorFmt('SNoFieldAccess', [FField.DisplayName]);}
  if not FField.Modified then begin
    if Mode = bmRead then begin
{      FCached := FDataSet.FCacheBlobs and (FBuffer = FDataSet.ActiveBuffer) and
        (FField.IsNull or (FDataSet.GetBlobData(FField, FBuffer) <> ''));}
    end
    else begin
//      FDataSet.SetBlobData(FField, FBuffer, '');
      if FField.ReadOnly then
        DatabaseErrorFmt(SFieldReadOnly, [FField.DisplayName]);
      if not (FDataSet.State in [dsNewValue, dsEdit, dsInsert]) then
        DatabaseError(SNotEditing);
    end;
  end;
  FOpened := True;
  if Mode = bmWrite then
    Truncate;
end;

destructor TBlobStream.Destroy;
begin
  if FOpened then begin
    if FModified then
      FField.Modified := True;
  end;
  if FModified then
    //try
    {$IFDEF CLR}
      FDataSet.DataEvent(deFieldChange, FField);
    {$ELSE} {$IFDEF FPC}
      FDataSet.DataEvent(deFieldChange, PtrInt(FField));
    {$ELSE} {$IFDEF VER16P}
      FDataSet.DataEvent(deFieldChange, NativeInt(FField));
    {$ELSE}
      FDataSet.DataEvent(deFieldChange, Longint(FField));
    {$ENDIF} {$ENDIF} {$ENDIF}
    {except
      Application.HandleException(Self);
    end;}
end;

{$IFDEF CLR}
function TBlobStream.Read(var Buffer: TBytes; Offset, Count: Longint): Longint;
var
  Handle: IntPtr;
{$ELSE}
function TBlobStream.Read(var Buffer; Count: longint): longint;
{$ENDIF}
begin
  Result := 0;
  if FOpened then begin
    if Count > Size - FPosition then
      Result := Size - FPosition
    else
      Result := Count;
    if Result > 0 then begin
    {$IFDEF CLR}
      Handle := AllocGCHandle(Buffer, True);
      try
        Result := FDataSet.Data.ReadBlob(FFieldDesc, FBuffer, FPosition, Count,
          GetAddrOfPinnedObject(Handle), FDataSet.State = dsOldValue{$IFDEF VER10P}, (FField is TWideMemoField){$ENDIF});
      finally
        FreeGCHandle(Handle);
      end;
    {$ELSE}
      Result := FDataSet.Data.ReadBlob(FFieldDesc, FBuffer, FPosition, Count, @Buffer,
        (FDataSet.State = dsOldValue){$IFDEF VER10P}, (FField is TWideMemoField){$ENDIF});
    {$ENDIF}
      Inc(FPosition, Result);
    end;
  end;
end;

{$IFDEF CLR}
function TBlobStream.Write(const Buffer: TBytes; Offset, Count: Longint): Longint;
var
  Handle: IntPtr;
{$ELSE}
function TBlobStream.Write(const Buffer; Count: Longint): Longint;
{$ENDIF}
begin
  Result := 0;
  if FOpened then begin
  {$IFDEF CLR}
    Handle := AllocGCHandle(Buffer, True);
    try
      FDataSet.Data.WriteBlob(FFieldDesc, FBuffer, FPosition, Count,
        GetAddrOfPinnedObject(Handle){$IFDEF VER10P}, FField is TWideMemoField{$ENDIF});
    finally
      FreeGCHandle(Handle);
    end;

  {$ELSE}
    FDataSet.Data.WriteBlob(FFieldDesc, FBuffer, FPosition, Count, @Buffer
    {$IFDEF VER10P}, FField is TWideMemoField{$ENDIF});
  {$ENDIF}
    Inc(FPosition, Count);
    Result := Count;
    FModified := True;
{    FDataSet.SetBlobData(FField, FBuffer, '');}
  end;
end;

{$IFDEF CLR}
function TBlobStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
{$ELSE}
function TBlobStream.Seek(Offset: Longint; Origin: Word): Longint;
{$ENDIF}
begin
  case Origin of
    soFromBeginning:
      FPosition := Offset;
    soFromCurrent:
      Inc(FPosition, Offset);
    soFromEnd:
      FPosition := GetBlobSize + Offset;
  end;
  Result := FPosition;
end;

procedure TBlobStream.Truncate;
begin
  if FOpened then begin
    FDataSet.Data.TruncateBlob(FFieldDesc, FBuffer, FPosition
      {$IFDEF VER10P}, FField is TWideMemoField{$ENDIF});
    FModified := True;
//    FDataSet.SetBlobData(FField, FBuffer, '');
  end;
end;

function TBlobStream.GetBlobSize: longint;
begin
  Result := 0;
  if FOpened then
    Result := FDataSet.Data.GetBlobSize(FFieldDesc, FBuffer, (FDataSet.State = dsOldValue)
    {$IFDEF VER10P}, FField is TWideMemoField{$ENDIF});
end;

{$IFDEF CLR}
procedure TBlobStream.SetSize(NewSize: Int64);
{$ELSE}
procedure TBlobStream.SetSize(NewSize: Longint);
{$ENDIF}
begin
  if FOpened then
    FDataSet.Data.SetBlobSize(FFieldDesc, FBuffer, NewSize, (FDataSet.State = dsOldValue)
    {$IFDEF VER10P}, (FField is TWideMemoField){$ENDIF});
end;

class function TMemDSUtils.SetBlob(Obj: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}; Field: TField; Blob: TBlob): boolean;
begin
  Result := Obj.SetBlob(Field, Blob);
end;

class function TMemDSUtils.GetBlob(Obj: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF}; FieldDesc: TFieldDesc): TBlob;
begin
  Result := Obj.InternalGetBlob(FieldDesc);
end;

{ TDataSetUpdater }

constructor TDataSetUpdater.Create(AOwner: TDataSetService);
begin
  inherited Create;

  FDataSetService := AOwner;
  Assert(FDataSetService <> nil);
  FDataSet := FDataSetService.FDataset;
end;

function TDataSetUpdater.PerformAppend: boolean;
begin
  Result := True;
end;

function TDataSetUpdater.PerformDelete: boolean;
begin
  Result := True;
end;

function TDataSetUpdater.PerformUpdate: boolean;
begin
  Result := True;
end;

function TDataSetUpdater.CacheChanged: boolean;
begin
  Result := True;
end;

function TDataSetUpdater.CacheApplied: boolean;
begin
  Result := True;
end;

function TDataSetUpdater.CacheCanceled: boolean;
begin
  Result := True;
end;

procedure TDataSetUpdater.DoPerformAppend;
var
  OldModified: boolean;
begin
  OldModified := FDataSet.Modified;
  try
    if not FDataSet.FLocalUpdate then
      if not FDataSet.FInDeferredPost then   // WAR supports defer posting
        PerformAppend
      else
        DoPerformUpdate;
  finally
    FDataSet.SetModified(OldModified);
  end;
end;

procedure TDataSetUpdater.DoPerformDelete;
var
  OldModified: boolean;
begin
  OldModified := FDataSet.Modified;
  try
    if not FDataSet.FLocalUpdate then
      PerformDelete
  finally
    FDataSet.SetModified(OldModified);
  end;
end;

procedure TDataSetUpdater.DoPerformUpdate;
var
  OldModified: boolean;
begin
  OldModified := FDataSet.Modified;
  try
    if not FDataSet.FLocalUpdate then
      PerformUpdate
  finally
    FDataSet.SetModified(OldModified);
  end;
end;

procedure TDataSetUpdater.DoCacheChanged;
begin
  CacheChanged;
end;

procedure TDataSetUpdater.DoCacheApplied;
begin
  CacheApplied;
end;

procedure TDataSetUpdater.DoCacheCanceled;
begin
  CacheCanceled;
end;

procedure TDataSetUpdater.DoApplyRecord(UpdateKind: TUpdateRecKind; var Action: TUpdateRecAction; LastItem: boolean);
var
  OldModified: boolean;
  UpdateAction: TUpdateAction;
begin
  OldModified := FDataSet.Modified;  // NewValue change Modified ??? or MemDS
  try
    UpdateAction := uaFail;
    try
      if Assigned(FDataSet.OnUpdateRecord) then begin
        FDataSet.OnUpdateRecord(FDataSet, TUpdateKind(UpdateKind), UpdateAction);
        case UpdateAction of
          uaFail:
            if not DoNotRaiseExcetionOnUaFail then
              raise EDatabaseError.Create(SCustomUpdateFailed);
          uaAbort:
            Abort;
        end;
      end;
      //else begin
      if not Assigned(FDataSet.OnUpdateRecord) or (UpdateAction = TUpdateAction(uaDefault)) then begin
        case UpdateKind of
          ukUpdate:
            PerformUpdate;
          ukInsert:
            PerformAppend;
          ukDelete:
            PerformDelete;
        end;

        if BatchUpdate then
          if CanFlushBatch or LastItem then
            // Should be flushed here because of Action parameter that should be returned
            // to TMemData.ApplyUpdates function
            FlushBatch
          else begin
            Action := urSuspended;
            Exit;
          end;

        UpdateAction := uaApplied;
      end;
    except
      on E: Exception do
        if IsClass(E, EDatabaseError) and Assigned(FDataSet.OnUpdateError) then begin
          FDataSet.OnUpdateError(FDataSet, EDatabaseError(E), TUpdateKind(UpdateKind), UpdateAction);
          case UpdateAction of
            uaFail:
              raise;
            uaAbort:
              Abort;
          end;
        end
        else
          raise;
    end;
  finally
    FDataSet.SetModified(OldModified);
  end;
  Action := TUpdateRecAction(UpdateAction);
end;

function TDataSetUpdater.BatchUpdate: boolean;
begin
  Result := False;
end;

function TDataSetUpdater.CanFlushBatch: boolean;
begin
  Result := False;
end;

procedure TDataSetUpdater.FlushBatch;
begin

end;

{ TDataSetService }

constructor TDataSetService.Create(AOwner: {$IFNDEF EntityDAC}TMemDataSet{$ELSE}TEDMemDataSet{$ENDIF});
begin
  inherited Create;

  FDataSet := AOwner;
  CreateDataSetUpdater;
end;

destructor TDataSetService.Destroy;
begin
  FreeDataSetUpdater;

  inherited;
end;

procedure TDataSetService.CreateDataSetUpdater;
begin
  SetDataSetUpdater(TDataSetUpdater.Create(Self));
end;

procedure TDataSetService.SetDataSetUpdater(Value: TDataSetUpdater);
begin
  FreeDataSetUpdater;
  FUpdater := Value;
end;

procedure TDataSetService.FreeDataSetUpdater;
begin
  FUpdater.Free;
  FUpdater := nil;
end;

function TDataSetService.SetProp(Prop: integer; const Value: variant): boolean;
begin
  Result := False;
  Assert(False);
end;

function TDataSetService.GetProp(Prop: integer; var Value: variant): boolean;
begin
  Result := False;
  Assert(False);
end;

procedure TDataSetService.SetNumberRange(FieldDef: TFieldDef);
var
  Field: TField;
  FieldDesc: TFieldDesc;
begin
  if FieldDef.DataType in [ftSmallint, ftInteger, ftLargeint, ftFloat] then begin
    Field := FDataSet.FindField(FieldDef.Name);
    if Field <> nil then begin
      FDataSet.CheckFieldCompatibility(Field, FieldDef);
      FieldDesc := FDataSet.Data.FindField(FieldDef.Name);
      if FieldDef.DataType in [ftSmallint, ftInteger] then begin
        Assert(Field is TIntegerField);
        TIntegerField(Field).MaxValue := Round(IntPower(10, FieldDesc.Length)) - 1;
        TIntegerField(Field).MinValue := -TIntegerField(Field).MaxValue;
      end
      else
      if FieldDef.DataType = ftLargeint then begin
        Assert(Field is TLargeintField);
        TLargeintField(Field).MaxValue := Round(IntPower(10, FieldDesc.Length)) - 1;
        TLargeintField(Field).MinValue := -TIntegerField(Field).MaxValue;
      end
      else
        if (FieldDesc.Length > 0) and (FieldDesc.Length <= 15) then begin
          Assert(Field is TFloatField);
          TFloatField(Field).Precision := FieldDesc.Length;
          TFloatField(Field).MaxValue :=
            IntPower(10, FieldDesc.Length - FieldDesc.Scale) -
            IntPower(10, - FieldDesc.Scale);
          TFloatField(Field).MinValue := - TFloatField(Field).MaxValue;
        end;
    end;
  end;
end;

function TDataSetService.GetFieldClass(FieldType: TFieldType; DataType: Word): TFieldClass;
begin
  Result := DefaultFieldClasses[FieldType];
end;

procedure TDataSetService.PreInitCursor;
begin

end;

procedure TDataSetService.SaveToXML(Destination: TStream);
var
  FieldAliases: TStringList;

  function IsValidFieldName(const FldName: string): boolean;
  var
    i: integer;
    ch: char;
  begin
    for i := 1 to Length(FldName) do begin
      ch := FldName[i];
      if not ((ch = '_') or (ch >= 'A') and (ch <= 'Z') or (ch >= 'a') and (ch <= 'z') or
        (i > 1) and (ch >= '0') and (ch <= '9'))
      then begin
        Result := False;
        Exit;
      end;
    end;
    Result := True;
  end;

  function GetFieldAlias(const FldName: string): string;
  var
    i: integer;
    ActualName: string;
  begin
    for i := 0 to FieldAliases.Count - 1 do begin
      ActualName := FieldAliases[i];
      if ActualName = FldName then begin
        Result := 'c' + IntToStr(i + 1);
        exit;
      end;
    end;
    Result := '';
  end;

var
  OldActive: boolean;
  Bookmark: DB.TBookmark;
  DestWriter: StreamWriter;
  XMLWriter: XmlTextWriter;
  XML: WideString;
  FldName, FldAlias: string;
  i: integer;
  FieldDesc: TFieldDesc;
begin
  OldActive := FDataSet.Active;
  Bookmark := nil;
  FieldAliases := nil;
  DestWriter := nil;
  XmlWriter := nil;
  try
    FDataSet.DisableControls;
    FDataSet.Active := True;
  {$IFDEF CLR}
  {$IFDEF VER9P}
  {$DEFINE OWNGETBOOKMARK}
  {$ENDIF}
  {$ENDIF}
  {$IFDEF OWNGETBOOKMARK}
    if FDataSet.BookmarkAvailable then
    begin
      Bookmark := Marshal.AllocHGlobal(FDataSet.BookmarkSize);
      FDataSet.GetBookmarkData(FDataSet.ActiveBuffer, Bookmark);
    end else
      Bookmark := nil;
  {$ELSE}
    Bookmark := FDataSet.GetBookmark;
  {$ENDIF}

    FieldAliases := TStringList.Create;

    DestWriter := StreamWriter.Create(Destination, Encoding(Encoding.UTF8));
    XmlWriter := XmlTextWriter.Create(DestWriter);

    XmlWriter.QuoteChar := '''';
    XmlWriter.Formatting := fmtIndented;
    XmlWriter.Indentation := 2;
    XmlWriter.IndentChar := ' ';

    // Header
    XmlWriter.WriteStartElement('xml');

    XmlWriter.WriteAttributeString('xmlns:s', 'uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882');
    XmlWriter.WriteAttributeString('xmlns:dt', 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882');
    XmlWriter.WriteAttributeString('xmlns:rs', 'urn:schemas-microsoft-com:rowset');
    XmlWriter.WriteAttributeString('xmlns:z', '#RowsetSchema');

    // Fields
    XmlWriter.WriteStartElement('s:Schema');
    XmlWriter.WriteAttributeString('id', 'RowsetSchema');

    XmlWriter.WriteStartElement('s:ElementType');
    XmlWriter.WriteAttributeString('name', 'row');
    XmlWriter.WriteAttributeString('content', 'eltOnly');
    XmlWriter.WriteAttributeString('rs:updatable', 'true');

    for i := 0 to FDataSet.Fields.Count - 1 do
      if not (FDataSet.Fields[i].FieldKind in [fkCalculated, fkLookup]) then begin
        FieldDesc := FDataSet.GetFieldDesc(FDataSet.Fields[i]);
        if FieldDesc <> nil then begin
          FldName := FieldDesc.Name;
          if not IsValidFieldName(FldName) then begin
            FldAlias := 'c' + IntToStr(FieldAliases.Count + 1);
            FieldAliases.Add(FldName);
          end
          else
            FldAlias := '';
          XmlWriter.WriteStartElement('s:AttributeType');
          WriteFieldXMLAttributeType(FDataSet.Fields[i], FieldDesc, FldAlias, XmlWriter);
          XmlWriter.WriteStartElement('s:datatype');
          WriteFieldXMLDataType(FDataSet.Fields[i], FieldDesc, FldAlias, XmlWriter);
          XmlWriter.WriteEndElement; // s:datatype
          XmlWriter.WriteFullEndElement; // s:AttributeType
        end;
      end;

    XmlWriter.WriteStartElement('s:extends');
    XmlWriter.WriteAttributeString('type', 'rs:rowbase');
    XmlWriter.WriteEndElement; // s:extends
    XmlWriter.WriteFullEndElement; // s:ElementType
    XmlWriter.WriteFullEndElement; // s:Schema

    // Data
    XmlWriter.WriteStartElement('rs:data');

    FDataSet.First;
    while not FDataSet.EOF do begin
      XmlWriter.WriteStartElement('z:row');
      for i := 0 to FDataSet.Fields.Count - 1 do
        if not (FDataSet.Fields[i].FieldKind in [fkCalculated, fkLookup]) then begin
          if not FDataSet.Fields[i].IsNull then begin
            XML := GetFieldXMLValue(FDataSet.Fields[i], FDataSet.GetFieldDesc(FDataSet.Fields[i]));
            FldName := FDataSet.Fields[i].FieldName;
            if not IsValidFieldName(FldName) then
              FldName := GetFieldAlias(FldName);
            XmlWriter.WriteAttributeString(FldName, XML);
          end;
        end;
      XmlWriter.WriteEndElement;
      FDataSet.Next;
    end;

    XmlWriter.WriteFullEndElement; // rs:data
    XmlWriter.WriteFullEndElement; // xml

    XmlWriter.Close;
  finally
    FDataSet.Active := OldActive;
    if OldActive and (Bookmark <> nil) then
      FDataSet.GotoBookmark(Bookmark);
    if Bookmark <> nil then
    {$IFDEF OWNGETBOOKMARK}
      Marshal.FreeHGlobal(Bookmark);
    {$ELSE}
      FDataSet.FreeBookmark(Bookmark);
    {$ENDIF}
    FieldAliases.Free;
    XmlWriter.Free;
    DestWriter.Free;

    FDataSet.EnableControls;
  end;
end;

procedure TDataSetService.WriteFieldXMLDataType(Field: TField; FieldDesc: TFieldDesc;
  const FieldAlias: string; XmlWriter: XMLTextWriter);
begin
  case FieldDesc.DataType of
    dtBlob, dtBytes, dtVarBytes, dtExtVarBytes: begin
      XmlWriter.WriteAttributeString('dt:type', 'bin.hex');
      if (FieldDesc.DataType = dtBlob) then
        XmlWriter.WriteAttributeString('dt:maxLength', '2147483647')
      else
        XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Length));
      if Field.IsBlob and not FieldDesc.Fixed then
        XmlWriter.WriteAttributeString('rs:long', 'true');
    end;
    dtBoolean: begin
      XmlWriter.WriteAttributeString('dt:type', 'boolean');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
    end;
    dtString, dtExtString, dtMemo, dtWideString, dtExtWideString, dtWideMemo: begin
      XmlWriter.WriteAttributeString('dt:type', 'string');
      if FieldDesc.DataType in [dtMemo, dtWideMemo] then
        XmlWriter.WriteAttributeString('dt:maxLength', '2147483647')
      else
        XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Length));
      if not (FieldDesc.DataType in [dtWideString, dtExtWideString, dtWideMemo]) then
        XmlWriter.WriteAttributeString('rs:dbtype', 'str');
      if Field.IsBlob and not FieldDesc.Fixed then
        XmlWriter.WriteAttributeString('rs:long', 'true');
    end;
    dtCurrency, dtBCD: begin
      XmlWriter.WriteAttributeString('dt:type', 'number');
      XmlWriter.WriteAttributeString('rs:dbtype', 'currency');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtDateTime: begin
      XmlWriter.WriteAttributeString('dt:type', 'datetime');
      XmlWriter.WriteAttributeString('rs:dbtype', 'variantdate');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:scale', IntToStr(FieldDesc.Scale));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtDate: begin
      XmlWriter.WriteAttributeString('dt:type', 'date');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:scale', IntToStr(FieldDesc.Scale));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtTime: begin
      XmlWriter.WriteAttributeString('dt:type', 'time');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:scale', IntToStr(FieldDesc.Scale));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    dtSQLTimeStamp: begin
      XmlWriter.WriteAttributeString('dt:type', 'datetime');
      XmlWriter.WriteAttributeString('rs:dbtype', 'timestamp');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:scale', IntToStr(FieldDesc.Scale));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
  {$ENDIF}
  {$ENDIF}
    dtFloat, dtSingle: begin
      if FieldDesc.Length <= 7 then begin
        XmlWriter.WriteAttributeString('dt:type', 'r4');
        XmlWriter.WriteAttributeString('dt:maxLength', '4');
      end
      else begin
        XmlWriter.WriteAttributeString('dt:type', 'float');
        XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      end;
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtGuid: begin
      XmlWriter.WriteAttributeString('dt:type', 'uuid');
      XmlWriter.WriteAttributeString('dt:maxLength', '16');
    end;
    dtInt32: begin
      XmlWriter.WriteAttributeString('dt:type', 'int');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', '10');
    end;
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    dtFmtBCD: begin
      XmlWriter.WriteAttributeString('dt:type', 'number');
      XmlWriter.WriteAttributeString('rs:dbtype', 'numeric');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:scale', IntToStr(FieldDesc.Scale));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
  {$ENDIF}
  {$ENDIF}
    dtInt16: begin
      XmlWriter.WriteAttributeString('dt:type', 'i2');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', '5');
    end;
    dtWord: begin
      XmlWriter.WriteAttributeString('dt:type', 'ui2');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtInt8: begin
      XmlWriter.WriteAttributeString('dt:type', 'i1');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtUInt8: begin
      XmlWriter.WriteAttributeString('dt:type', 'ui1');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtLongword: begin
      XmlWriter.WriteAttributeString('dt:type', 'ui4');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', IntToStr(FieldDesc.Length));
    end;
    dtInt64: begin
      XmlWriter.WriteAttributeString('dt:type', 'i8');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', '19');
    end;
    dtUInt64: begin
      XmlWriter.WriteAttributeString('dt:type', 'ui8');
      XmlWriter.WriteAttributeString('dt:maxLength', IntToStr(FieldDesc.Size));
      XmlWriter.WriteAttributeString('rs:precision', '19');
    end;
    dtUnknown, dtVariant:
      XmlWriter.WriteAttributeString('dt:type', 'string')
    else
      DatabaseError(SDataTypeNotSupported, FDataSet);
  end;

  if FieldDesc.Fixed and not (FieldDesc.DataType in [dtUnknown, dtVariant]) then
    XmlWriter.WriteAttributeString('rs:fixedlength', 'true');

  if Field.Required and not Field.ReadOnly then
    XmlWriter.WriteAttributeString('rs:maybenull', 'false');
end;

procedure TDataSetService.WriteFieldXMLAttributeType(Field: TField; FieldDesc: TFieldDesc;
  const FieldAlias: string; XmlWriter: XMLTextWriter);
begin
  if FieldAlias = '' then
    XmlWriter.WriteAttributeString('name', FieldDesc.Name)
  else begin
    XmlWriter.WriteAttributeString('name', FieldAlias);
    XmlWriter.WriteAttributeString('rs:name',  FieldDesc.Name);
  end;

  XmlWriter.WriteAttributeString('rs:number',  IntToStr(FieldDesc.FieldNo));
  if not Field.Required and not Field.ReadOnly then /// Can't use FieldDesc.Required, see "Required and FLocalConstraints" line in TMemDataSet.CreateFieldDefs for details
    XmlWriter.WriteAttributeString('rs:nullable', 'true');
  if not Field.ReadOnly then
    XmlWriter.WriteAttributeString('rs:writeunknown', 'true');
  // XmlWriter.WriteAttributeString('rs:basecatalog', '');

  if FieldDesc.ActualName <> '' then
    XmlWriter.WriteAttributeString('rs:basecolumn', FieldDesc.ActualName);
  if FieldDesc.IsKey then
    XmlWriter.WriteAttributeString('rs:keycolumn', 'true');
  if FDataSet.GetFieldDesc(Field).IsAutoIncrement then
    XmlWriter.WriteAttributeString('rs:autoincrement', 'true');
end;

function TDataSetService.GetFieldXMLValue(Field: TField; FieldDesc: TFieldDesc): WideString;
var
  Buffer: TBytes;
{$IFDEF FPC}
  sBuffer: string;
{$ENDIF}
  Blob: TBlob;
  Piece: PPieceHeader;
{$IFDEF CLR}
  sb: StringBuilder;
  Bytes: TBytes;
{$ELSE}
  sbOffset: integer;
  StrValue: string;
{$ENDIF}

  function EncodeXMLDate(Value: TDateTime): string;
  var
    Year, Month, Day: Word;
  begin
    DecodeDate(Value, Year, Month, Day);
    Result := StringReplace(Format('%4d%s%2d%s%2d', [Year, '-', Month, '-', Day]),
      ' ', '0', [rfReplaceAll]);
  end;

  function EncodeXMLTime(Value: TDateTime): string;
  var
    Hour, Minute, Second, MilliSecond: Word;
  begin
    DecodeTime(Value, Hour, Minute, Second, MilliSecond);
    Result := StringReplace(Format('%2d%s%2d%s%2d', [Hour, ':', Minute, ':', Second]),
      ' ', '0', [rfReplaceAll]);
  end;

  function EncodeXMLDateTime(Value: TDateTime): string;
  begin
    Result := EncodeXMLDate(Value) + 'T' + EncodeXMLTime(Value);
  end;

begin
  Result := '';
  SetLength(Buffer, 0);
  case FieldDesc.DataType of
    dtBoolean:
      Result := BoolToStr(Field.AsBoolean, True);
    dtInt8, dtUInt8, dtInt16, dtUInt16, dtInt32, dtUInt32, dtInt64, dtUInt64:
      Result := Field.AsString;
    dtFloat:
      Result := ChangeDecimalSeparator(FloatToStr(Field.AsFloat), {$IFDEF VER16P}FormatSettings.{$ENDIF}DecimalSeparator, '.');
    dtSingle:
      Result := ChangeDecimalSeparator(FloatToStr(Field.{$IFDEF VER14P}AsSingle{$ELSE}AsFloat{$ENDIF}), {$IFDEF VER16P}FormatSettings.{$ENDIF}DecimalSeparator, '.');
    dtCurrency, dtBcd:
      Result := ChangeDecimalSeparator(CurrToStr(Field.Value), {$IFDEF VER16P}FormatSettings.{$ENDIF}DecimalSeparator, '.');
    dtDateTime:
      Result := EncodeXMLDateTime(Field.AsDateTime);
    dtDate:
      Result := EncodeXMLDate(Field.AsDateTime);
    dtTime:
      Result := EncodeXMLTime(Field.AsDateTime);
  {$IFDEF VER6P}
  {$IFNDEF FPC}
    dtSQLTimeStamp:
      Result := EncodeXMLDateTime(Field.AsDateTime);
    dtFmtBCD: begin
      Result := BcdToStr(Field.AsBCD);
    end;
  {$ENDIF}
  {$ENDIF}
    dtBytes, dtVarBytes, dtExtVarBytes: begin
    {$IFNDEF FPC}
      Buffer := Field.Value;
    {$ELSE}
      sBuffer := Field.Text;
    {$ENDIF}
    {$IFDEF CLR}
      SetLength(Bytes, Length(Buffer) * 2);
      BinToHex(Buffer, 0, Bytes, 0, Length(Buffer));
      Result := Encoding.Default.GetString(Bytes);
    {$ELSE}
    {$IFNDEF FPC}
      SetLength(StrValue, Length(Buffer) * 2);
      BinToHex(@Buffer[0], PChar(StrValue), Length(Buffer));
      Result := StrValue;
    {$ELSE}
      SetLength(StrValue, Length(sBuffer) * 2);
      BinToHex(PChar(sBuffer), PChar(StrValue), Length(Buffer));
      Result := StrValue;
    {$ENDIF}
    {$ENDIF}
    end;
    dtBlob: begin
      Blob := FDataSet.InternalGetBlob(FieldDesc);
      Piece := Blob.FirstPiece;
    {$IFDEF CLR}
      sb := StringBuilder.Create;
      try
        while IntPtr(Piece) <> nil do begin
          SetLength(Buffer, Piece.Used);
          Marshal.Copy(PtrOffset(Piece, sizeof(TPieceHeader)), Buffer, 0, Piece.Used);
          SetLength(Bytes, Length(Buffer) * 2);
          BinToHex(Buffer, 0, Bytes, 0, Length(Buffer));
          sb.Append(Encoding.Default.GetString(Bytes));
          Piece := Piece.Next;
        end;
        Result := sb.ToString;
      finally
        sb.Free;
      end;
    {$ELSE}
      SetLength(StrValue, Blob.Size * 2);
      sbOffset := 0;
      while Piece <> nil do begin
        BinToHex(PtrOffset(Piece, sizeof(TPieceHeader)),
          PChar(PtrOffset(PChar(StrValue), sbOffset)), Piece.Used);
        sbOffset := sbOffset + Piece.Used * 2;
        Piece := Piece.Next;
      end;
      Result := StrValue;
    {$ENDIF}
    end;
    else
      if Field is TWideStringField then
        Result := Field.Value
    {$IFDEF VER10P}
    {$IFNDEF CLR}
      else
      if Field is TWideMemoField then
        Result := Field.AsWideString
    {$ENDIF}
    {$ENDIF}
      else
        Result := Field.AsString;
  end;
end;

end.
