unit Aurelius.Bind.BaseDataset;

{$I Aurelius.inc}

interface

uses
  Generics.Collections, Classes, DB, SysUtils, TypInfo,
  Bcl.Collections;

type
  TAureliusEntityField = class(TVariantField)
  private
    {$IFDEF NEXTGEN}
    FObject: TObject;
    {$ENDIF}
    function GetAsObject: TObject;
    procedure SetAsObject(const Value: TObject);
  protected
    {$IFDEF DELPHIXE3_LVL}
    function GetAsVariant: Variant; override;
    procedure SetVarValue(const Value: Variant); override;
    {$ENDIF}
  public
    property AsObject: TObject read GetAsObject write SetAsObject;
    function AsEntity<T: class>: T;
  end;

  TDatasetCreateObjectEvent = procedure(Dataset: TDataset; var NewObject: TObject) of object;
  TDatasetObjectEvent = procedure(Dataset: TDataset; AObject: TObject) of object;

  {$IFDEF DELPHIXE3_LVL}
  TAureliusValueBuffer = TValueBuffer;
  TAureliusBookmark = TBookmark;
  {$ELSE}
  TAureliusValueBuffer = Pointer;
  TAureliusBookmark = Pointer;
  {$ENDIF}

  {$IFDEF DELPHIXE4_LVL}
  TAureliusRecordBuffer = TRecBuf;
  {$ELSE}
  TAureliusRecordBuffer = TRecordBuffer;
  {$ENDIF}

  TRecordCountMode = (Default, Retrieve);
  ValueType = Variant;

  TObjectSource = class
  public
    function FetchMore(AList: IObjectList): Boolean; virtual; abstract;
    function Fetching: Boolean; virtual; abstract;
    function ObjectClass: TClass; virtual; abstract;
    function CanRetrieveRecordCount: Boolean; virtual;
    function RetrieveRecordCount: Integer; virtual;
  end;

  TBaseAureliusDataset = class(TDataset)
  strict private
    type
      PRecInfo = ^TRecInfo;

      TRecInfo = packed record
        Index: integer;
        Obj: TObject;
        //Obj: Pointer;
        BookmarkFlag: TBookmarkFlag;
      end;

      PADSBookmarkData = ^TADSBookmarkData;
      TADSBookmarkData = record
        {$IFDEF AUTOREFCOUNT}[unsafe]{$ENDIF} Obj2: TObject;
      end;

      PValueList = ^TValueList;
      TValueList = array[0..0] of ValueType;
      TRowFilterState = (Undefined, Accepted, Rejected);
  strict private
    FSourceList: IObjectList;
    FSource: TObjectSource;
    FFilteredRows: TArray<TRowFilterState>;
    FFilteredCount: integer;
    FFilteredProcessed: integer;

    FCurrent: integer;
    FIsOpen: boolean;
    FRecBufSize: integer;
    FRecInfoOffset: integer;
    FRecordSize: integer;
    FOldValueBuffer: TAureliusRecordBuffer;
    FFilterBuffer: TAureliusRecordBuffer;
    FModifiedFields: TList<TField>;
    FObjectClass: TClass;
    FRecordCountRetrieved: Boolean;
    FCachedRecordCount: Integer;
  protected
    FInternalList: TObjectList<TObject>;
  strict private
    FInsertObject: TObject;
    FOnObjectInsert: TDatasetObjectEvent;
    FOnCreateObject: TDatasetCreateObjectEvent;
    FOnObjectRemove: TDatasetObjectEvent;
    FOnObjectUpdate: TDatasetObjectEvent;
    FOnObjectRefresh: TDatasetObjectEvent;
    FCreateObjectOnPost: boolean;
    FDesignClass: string;
    FDesignPackage: string;
    FCreateSelfField: Boolean;
    FIncludeUnmappedObjects: Boolean;
    FReserved: Integer;
    FSetToRecBmk: TBookmark;
    FErrorOnPost: Boolean;
    FRefreshObjectOnCancel: Boolean;
    FSubpropsDepth: Integer;
    FSyncSubprops: Boolean;
    FSyncingSubprops: Boolean;
    FDefaultsFromObject: Boolean;
    FRecordCountMode: TRecordCountMode;
    function ListCount: integer;
    function GetBufferValueList(Buffer: TAureliusRecordBuffer): PValueList;
    function CreateObject: TObject;
    function IsSelfField(Field: TField): Boolean;
    function IsEntityField(Field: TField): Boolean;
    function GetBufferRecInfo(Buffer: TAureliusRecordBuffer): PRecInfo;
    procedure UpdateListFromParent(Field: TDatasetField);
    function InternalGetRecord(Buffer: TAureliusRecordBuffer; GetMode: TGetMode; DoCheck: boolean): TGetResult;
    procedure FetchAllRecords;
    procedure GetTheFieldList(List: TList<TField>; const FieldNames: string);
    function GetFieldVariant(Field: TField; var Data: ValueType): boolean;
    function GetVariantValueForField(Field: TField; Buffer: TAureliusRecordBuffer): ValueType;
    procedure RestartFilter;
    procedure ReadDesignClass(Reader: TReader);
    procedure WriteDesignClass(Writer: TWriter);
    procedure ReadDesignPackage(Reader: TReader);
    procedure WriteDesignPackage(Writer: TWriter);
    function FieldListCheckSum: Integer;
    function ObjectFromBookmark(ABookmark: TBookmark): TObject;
  private
    {$IFDEF DELPHIXE4_LVL}
    const NullBuffer: TAureliusRecordBuffer = 0;
    {$ELSE}
    const NullBuffer: TAureliusRecordBuffer = nil;
    {$ENDIF}
  private
    function GetOldValueBuffer: TAureliusRecordBuffer;
    function GetActiveRecBuf: TAureliusRecordBuffer;
    procedure SetObjectClass(const Value: TClass);
    function GetInternalList: IReadOnlyObjectList;
    function GetBlobData(Field: TField): TBytes;
    function GetModifiedFields: TEnumerable<TField>;
  protected
    class function ObjectToVariant(Obj: TObject): Variant;
    class function VariantToObject(V: Variant): TObject;
    function LocateRecord(const KeyFields: string; const KeyValues: Variant;
      Options: TLocateOptions; SyncCursor: Boolean; ResultFields: string): Boolean; virtual;
  protected
    procedure CreateFields; override;
    function CreateNestedDataSet(DataSetField: TDataSetField): TDataSet; override;
    {$IFDEF DELPHIXE2_LVL}
    procedure DataEvent(Event: TDataEvent; Info: NativeInt); override;
    {$ELSE}
    procedure DataEvent(Event: TDataEvent; Info: Longint); override;
    {$ENDIF}
    function GetCanModify: boolean; override;
    function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
    function GetRecordCount: Integer; override;
    function GetRecNo: Integer; override;
    procedure SetDataSetField(const Value: TDataSetField); override;
    procedure SetRecNo(Value: integer); override;
    procedure DoAfterOpen; override;
    procedure SetFiltered(Value: Boolean); override;
    procedure DoOnNewRecord; override;
    procedure InternalEdit; override;
    procedure InternalCancel; override;
    procedure InternalInsert; override;
  protected
    procedure DefineProperties(Filer: TFiler); override;
    {$IFDEF NEXTGEN}
    function AllocRecBuf: TRecBuf; override;
    procedure FreeRecBuf(var Buffer: TRecBuf); override;
    {$ELSE}
    function AllocRecordBuffer: TRecordBuffer; override;
    procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
    {$ENDIF}

    procedure GetBookmarkData(Buffer: TAureliusRecordBuffer; Data: TAureliusBookmark); overload; override;
    function GetBookmarkFlag(Buffer: TAureliusRecordBuffer): TBookmarkFlag; override;
    function GetRecordSize: Word; override;
//    procedure InternalAddRecord(Buffer: TRecordBuffer; Append: Boolean); overload; virtual;
//    procedure InternalAddRecord(Buffer: Pointer; Append: boolean); override;
    procedure InternalDelete; override;
    procedure InternalFirst; override;
    procedure InternalGotoBookmark(Bookmark: TAureliusBookmark); override;
    procedure InternalInitRecord(Buffer: TAureliusRecordBuffer); override;
    {$IFDEF DELPHIXE4_LVL}
    {$IFNDEF NEXTGEN}
    procedure InternalInitRecord(Buffer: TRecordBuffer); override;
    {$ENDIF}
    {$ENDIF}
    procedure InternalLast; override;
    procedure InternalPost; override;
    procedure InternalSetToRecord(Buffer: TAureliusRecordBuffer); override;
    procedure SetBookmarkFlag(Buffer: TAureliusRecordBuffer; Value: TBookmarkFlag); override;
    procedure SetBookmarkData(Buffer: TAureliusRecordBuffer; Data: TAureliusBookmark); overload; override;
    procedure SetFieldData(Field: TField; Buffer: TAureliusValueBuffer); overload; override;
    procedure SetFieldData(Field: TField; Buffer: TAureliusValueBuffer; NativeFormat: boolean); override;
  protected
    function GetRecord(Buffer: TAureliusRecordBuffer; GetMode: TGetMode; DoCheck: boolean): TGetResult; override;
    procedure InternalClose; override;
    procedure InternalHandleException; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalOpen; override;
    function IsCursorOpen: boolean; override;
  protected
    procedure ClearInternalList; virtual;
    function GetPropValue(APropName: string; Obj: TObject): ValueType; virtual;
    procedure SetPropValue(APropName: string; Obj: TObject; Value: ValueType); virtual;
    procedure InitializeFieldDefinitions; virtual;
    function RetrieveRecordCount: Integer; virtual;
    function InternalCreateObject(AClass: TClass): TObject; virtual;
    procedure UpdateObjectClassFromParent(Parent: TBaseAureliusDataset; Field: TField); virtual;
    function FetchMoreRecords(AList: IObjectList): boolean; virtual;
    function FetchingRecords: boolean; virtual;
    procedure InternalSetSourceList(SourceList: TObject);
    procedure SetSourceToInternalList;
    function DoCreateObject: TObject; virtual;
    procedure DoObjectRemove(Obj: TObject); virtual;
    procedure DoObjectInsert(Obj: TObject); virtual;
    procedure DoObjectUpdate(Obj: TObject); virtual;
    procedure DoObjectRefresh(Obj: TObject); virtual;
    procedure InternalObjectRemove(Obj: TObject); virtual;
    procedure InternalObjectInsert(Obj: TObject); virtual;
    procedure InternalObjectUpdate(Obj: TObject); virtual;
    procedure InternalObjectRefresh(Obj: TObject); virtual;
    property ObjectView default True;
  public
    function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer; override;
    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
    {$IFDEF DELPHIXE4_LVL}
    function GetFieldData(Field: TField; var Buffer: TAureliusValueBuffer): boolean; override;
    {$ELSE}
    function GetFieldData(Field: TField; Buffer: TAureliusValueBuffer): boolean; override;
    {$ENDIF}
    function BookmarkValid(Bookmark: TBookmark): Boolean; override;
    function IsSequenced: Boolean; override;
    function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): boolean; override;
    function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant; override;
  public
    property InternalList: IReadOnlyObjectList read GetInternalList;
    property DesignClass: string read FDesignClass write FDesignClass; // for design-time only
    property DesignPackage: string read FDesignPackage write FDesignPackage; // for design-time only
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Current<T: class>: T;
    procedure SetSourceList(SourceList: TObject);
    procedure SetSource(ASource: TObjectSource);
    procedure SetSourceObject(SourceObject: TObject);
    function EntityFieldByName(const FieldName: string): TAureliusEntityField;
    function IsModified(const FieldName: string): Boolean;
    property CreateObjectOnPost: boolean read FCreateObjectOnPost write FCreateObjectOnPost default False;
    property ModifiedFields: TEnumerable<TField> read GetModifiedFields;
    property ObjectClass: TClass read FObjectClass write SetObjectClass;
    property OnCreateObject: TDatasetCreateObjectEvent read FOnCreateObject write FOnCreateObject;
    property OnObjectInsert: TDatasetObjectEvent read FOnObjectInsert write FOnObjectInsert;
    property OnObjectUpdate: TDatasetObjectEvent read FOnObjectUpdate write FOnObjectUpdate;
    property OnObjectRemove: TDatasetObjectEvent read FOnObjectRemove write FOnObjectRemove;
    property OnObjectRefresh: TDatasetObjectEvent read FOnObjectRefresh write FOnObjectRefresh;
    property CreateSelfField: Boolean read FCreateSelfField write FCreateSelfField default True;
    property IncludeUnmappedObjects: Boolean read FIncludeUnmappedObjects write FIncludeUnmappedObjects default False;
    property RefreshObjectOnCancel: Boolean read FRefreshObjectOnCancel write FRefreshObjectOnCancel default False;
    property SubpropsDepth: Integer read FSubpropsDepth write FSubpropsDepth default 0;
    property SyncSubprops: Boolean read FSyncSubprops write FSyncSubprops default False;
    property DefaultsFromObject: Boolean read FDefaultsFromObject write FDefaultsFromObject default False;
    property RecordCountMode: TRecordCountMode read FRecordCountMode write FRecordCountMode default TRecordCountMode.Default;
  end;

  TSomethingEvent = reference to procedure(Sender: TObject);

  TObjBlobStream = class(TMemoryStream)
  private
    FField: TBlobField;
    FDataSet: TBaseAureliusDataset;
    FBuffer: TAureliusRecordBuffer;
    FFieldNo: integer;
    FModified: boolean;
  protected
    procedure ReadBlobData;
  public
    constructor Create(Field: TBlobField; Mode: TBlobStreamMode);
    destructor Destroy; override;
    function Write(const Buffer; Count: Longint): Longint; override;
    procedure Truncate;
  end;

const
  SelfFieldName = 'Self';
  EnumNameSufix = 'EnumName';

procedure SplitProp(const AText: string; var AProp, ASubProp: string);
function IsEnumeration(ATypeInfo: PTypeInfo): boolean;

implementation

uses
  DBConsts, Variants, FmtBCD, Rtti,
  {$IFDEF DELPHIXE4_LVL}
  {$IFNDEF NEXTGEN}
  AnsiStrings,
  {$ENDIF}
  {$ENDIF}
  Bcl.Rtti.Utils,
  Aurelius.Bind.Exceptions,
  Aurelius.Global.Utils;

procedure SplitProp(const AText: string; var AProp, ASubProp: string);
var
  p: integer;
begin
  p := Pos('.', AText);
  if p = 0 then
  begin
    AProp := AText;
    ASubProp := '';
  end else
  begin
    AProp := Copy(AText, 1, p - 1);
    ASubProp := Copy(AText, p + 1, MaxInt);
  end;
end;

function IsEnumeration(ATypeInfo: PTypeInfo): boolean;
begin
  Result:= (ATypeInfo <> nil) and (ATypeInfo.Kind = tkEnumeration) and (ATypeInfo <> TypeInfo(boolean));
end;

{$IFNDEF DELPHIXE2_LVL}
type
  IntPtr = integer;
{$ENDIF}

{ TBaseAureliusDataset }

{$IFDEF NEXTGEN}
function TBaseAureliusDataset.AllocRecBuf: TRecBuf;
var
  RecInfo: PRecInfo;
begin
  Result := TRecBuf(AllocMem(FRecBufSize));
  Initialize(GetBufferValueList(Result)^, Fields.Count);
  RecInfo := GetBufferRecInfo(Result);
  Initialize(RecInfo^);
end;
{$ELSE}
function TBaseAureliusDataset.AllocRecordBuffer: TRecordBuffer;
begin
  Result := AllocMem(FRecBufSize);
  {$IFDEF DELPHIXE4_LVL}
  Initialize(GetBufferValueList(NativeInt(Result))^, Fields.Count);
//  Initialize(GetBufferRecInfo(NativeInt(Result))^);
  {$ELSE}
  Initialize(GetBufferValueList(Result)^, Fields.Count);
//  Initialize(GetBufferRecInfo(Result)^);
  {$ENDIF}
end;
{$ENDIF}

{$IFDEF NEXTGEN}
procedure TBaseAureliusDataset.FreeRecBuf(var Buffer: TRecBuf);
begin
  Finalize(GetBufferValueList(Buffer)^, Fields.Count);
  Finalize(GetBufferRecInfo(Buffer)^);
  FreeMem(Pointer(Buffer), FRecBufSize);
end;
{$ELSE}
procedure TBaseAureliusDataset.FreeRecordBuffer(var Buffer: TRecordBuffer);
begin
  {$IFDEF DELPHIXE4_LVL}
  Finalize(GetBufferValueList(NativeInt(Buffer))^, Fields.Count);
//  Finalize(GetBufferRecInfo(NativeInt(Buffer))^);
  {$ELSE}
  Finalize(GetBufferValueList(Buffer)^, Fields.Count);
//  Finalize(GetBufferRecInfo(Buffer)^);
  {$ENDIF}
  FreeMem(Buffer, FRecBufSize);
end;
{$ENDIF}

function TBaseAureliusDataset.BookmarkValid(Bookmark: TBookmark): Boolean;
begin
  Result := (Bookmark <> nil) and
    (ObjectFromBookmark(Bookmark) <> nil) and
    (FSourceList.IndexOf(ObjectFromBookmark(Bookmark)) <> -1);
end;

procedure TBaseAureliusDataset.ClearInternalList;
begin
  if not Assigned(FInternalList) then
    Exit;
  FInternalList.Clear;
end;

function TBaseAureliusDataset.CompareBookmarks(Bookmark1,
  Bookmark2: TBookmark): Integer;
var
  I1, I2: integer;
begin
  if (Bookmark1 <> nil) and (Bookmark2 <> nil) then
  begin
    I1 := FSourceList.IndexOf(ObjectFromBookmark(Bookmark1));
    I2 := FSourceList.IndexOf(ObjectFromBookmark(Bookmark2));
    Result := I2 - I1;
  end
  else
  begin
    if Bookmark1 <> nil then
      Result := -1
    else
    if Bookmark2 <> nil then
      Result := 1
    else
      Result := 0;
  end;
end;

constructor TBaseAureliusDataset.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FInternalList := TObjectList<TObject>.Create(false);
  FModifiedFields := TList<TField>.Create;
  FCreateSelfField := True;
  BookmarkSize := SizeOf(TADSBookmarkData);
  SetLength(FSetToRecBmk, BookmarkSize);
  ObjectView := True;
end;

function TBaseAureliusDataset.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
begin
  Result := TObjBlobStream.Create(Field as TBlobField, Mode);
end;

procedure TBaseAureliusDataset.CreateFields;
begin
  inherited;
end;

function TBaseAureliusDataset.CreateNestedDataSet(DataSetField: TDataSetField): TDataSet;
begin
  Result := inherited CreateNestedDataset(DataSetField);
end;

function TBaseAureliusDataset.CreateObject: TObject;
begin
  Result := DoCreateObject;
  if Result = nil then
    Result := InternalCreateObject(FObjectClass);
end;

function TBaseAureliusDataset.Current<T>: T;
var
  RecBuf: TAureliusRecordBuffer;
begin
  if State = dsInsert then
    Exit(T(FInsertObject));
  RecBuf := GetActiveRecBuf;
  if RecBuf = NullBuffer then
    Exit(nil);
  Result := T(GetBufferRecInfo(RecBuf)^.Obj);
end;

{$IFDEF DELPHIXE2_LVL}
procedure TBaseAureliusDataset.DataEvent(Event: TDataEvent; Info: NativeInt);
{$ELSE}
procedure TBaseAureliusDataset.DataEvent(Event: TDataEvent; Info: Longint);
{$ENDIF}
begin
  case Event of
    deParentScroll:
      if DataSetField <> nil then
      begin
        UpdateListFromParent(DataSetField);
        Resync([]);
      end;
    deUpdateState:
      if State = dsBrowse then
        FreeAndNil(FInsertObject);
    deLayoutChange:
      if Active and (FReserved <> 0) and
        (FieldListCheckSum <> FReserved) then
        FReserved := 0;

  end;
  inherited;
end;

procedure TBaseAureliusDataset.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('DesignClass', ReadDesignClass, WriteDesignClass, FDesignClass <> '');
  Filer.DefineProperty('DesignPackage', ReadDesignPackage, WriteDesignPackage, FDesignPackage <> '');
end;

destructor TBaseAureliusDataset.Destroy;
begin
  FModifiedFields.Free;
  ClearInternalList;
  FreeAndNil(FInternalList);
  FreeAndNil(FInsertObject);
  SetLength(FSetToRecBmk, 0);
  FSetToRecBmk := nil;
  FreeAndNil(FSource);
  inherited;
end;

procedure TBaseAureliusDataset.DoAfterOpen;
var
  I: Integer;
begin
  // Notify nested datasets to be refreshed
  if Assigned(NestedDataSets) then
  begin
    for I := 0 to NestedDataSets.Count - 1 do
      if TObject(NestedDatasets[I]) is TBaseAureliusDataset then
        with TBaseAureliusDataset(NestedDataSets[I]) do
          if Active then
          begin
            UpdateListFromParent(DatasetField);
            Resync([]);
          end;
  end;
  inherited;
end;

function TBaseAureliusDataset.DoCreateObject: TObject;
begin
  Result := nil;
  if Assigned(FOnCreateObject) then
    FOnCreateObject(Self, Result);
end;

procedure TBaseAureliusDataset.DoObjectInsert(Obj: TObject);
begin
  if Assigned(FOnObjectInsert) then
    FOnObjectInsert(Self, Obj)
  else
    InternalObjectInsert(Obj);
end;

procedure TBaseAureliusDataset.DoObjectRemove(Obj: TObject);
begin
  if Assigned(FOnObjectRemove) then
    FOnObjectRemove(Self, Obj)
  else
   InternalObjectRemove(Obj);
end;

procedure TBaseAureliusDataset.DoObjectUpdate(Obj: TObject);
begin
  if Assigned(FOnObjectUpdate) then
    FOnObjectUpdate(Self, Obj)
  else
    InternalObjectUpdate(Obj);
end;

procedure TBaseAureliusDataset.DoObjectRefresh(Obj: TObject);
begin
  if Assigned(FOnObjectRefresh) then
    FOnObjectRefresh(Self, Obj)
  else
    InternalObjectRefresh(Obj);
end;

procedure TBaseAureliusDataset.DoOnNewRecord;
begin
  FModifiedFields.Clear;
  inherited;
end;

function TBaseAureliusDataset.EntityFieldByName(const FieldName: string): TAureliusEntityField;
var
  Field: TField;
begin
  Field := FindField(FieldName);
  if Field = nil then DatabaseErrorFmt(SFieldNotFound, [FieldName], Self);
  if not (Field is TAureliusEntityField) then
    DatabaseErrorFmt(SFieldTypeMismatch, [FieldName, 'TEntityField', Field.ClassName], Self);
  Result := TAureliusEntityField(Field);
end;

procedure TBaseAureliusDataset.FetchAllRecords;
begin
  while FetchMoreRecords(FSourceList) do ;
end;

function TBaseAureliusDataset.FetchingRecords: boolean;
begin
  Result := ((FSource <> nil) and FSource.Fetching);
end;

function TBaseAureliusDataset.FetchMoreRecords(AList: IObjectList): boolean;
begin
  Result := false;
  if (FSource <> nil) and FSource.Fetching then
  begin
    Result := FSource.FetchMore(AList);
    if not Result then
      FreeAndNil(FSource);
  end;
end;

function TBaseAureliusDataset.FieldListCheckSum: Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to Fields.Count - 1 do
    Result := Result + (IntPtr(Fields[I]) shr (I mod 16));
end;

function TBaseAureliusDataset.GetActiveRecBuf: TAureliusRecordBuffer;
begin
  case State of
    dsBlockRead, dsBrowse:
      if IsEmpty then
        Result := NullBuffer
      else
        Result := ActiveBuffer;
    dsCalcFields, dsInternalCalc:
      Result := CalcBuffer;
    dsFilter:
      Result := FFilterBuffer;
    dsOldValue:
      Result := GetOldValueBuffer;
    dsEdit, dsInsert, dsNewValue:
      if IsEmpty then
        Result := NullBuffer
      else
        Result := ActiveBuffer;
  else
    Result := NullBuffer;
  end;
end;

function TBaseAureliusDataset.GetBlobData(Field: TField): TBytes;
var
  Data: ValueType;
  AnsiEncoding: TEncoding;
begin
  if GetFieldVariant(Field, Data) then
  begin
    // if field is special cases memo or widememo, and variant is string
    // then force the encoding for the expected encoding.
    // Memo needs ANSI encoding, WideMemo needs Unicode encoding.
    // If variant is bytes, consider the bytes are unicode string (usually TBlob)
    // and then convert to property ansi/unicode string.
    if (Field.DataType in [ftMemo, ftWideMemo]) then
    begin
      {$IFDEF DELPHIXE2_LVL}
      AnsiEncoding := TEncoding.ANSI;
      {$ELSE}
      AnsiEncoding := TEncoding.Default;
      {$ENDIF}

      if VarIsStr(Data) then
        case Field.DataType of
          ftMemo:
            Result := AnsiEncoding.GetBytes(VarToStr(Data));
          ftWideMemo:
            Result := TEncoding.Unicode.GetBytes(VarToStr(Data));
        end
      else
      begin
        Result := TUtils.VariantToBytes(Data);
        if Field.DataType = ftMemo then
          Result := AnsiEncoding.GetBytes(TEncoding.Unicode.GetString(Result));
      end;
    end
    else
      Result := TUtils.VariantToBytes(Data);
  end
  else
    SetLength(Result, 0);
end;

procedure TBaseAureliusDataset.GetBookmarkData(Buffer: TAureliusRecordBuffer; Data: TAureliusBookmark);
var
  Bmk: PADSBookmarkData;
begin
  Bmk := PADSBookmarkData(Data);
//  {$IFDEF DELPHIXE4_LVL}
//  Initialize(Bmk^);
//  {$ENDIF}
  Bmk.Obj2 := GetBufferRecInfo(Buffer)^.Obj;
end;

function TBaseAureliusDataset.GetBookmarkFlag(Buffer: TAureliusRecordBuffer): TBookmarkFlag;
begin
  Result := GetBufferRecInfo(Buffer)^.BookmarkFlag;
end;

function TBaseAureliusDataset.GetBufferRecInfo(Buffer: TAureliusRecordBuffer): PRecInfo;
begin
  Result := PRecInfo(Buffer + FRecInfoOffset);
end;

function TBaseAureliusDataset.GetBufferValueList(Buffer: TAureliusRecordBuffer): PValueList;
begin
  Result := PValueList(Buffer);
end;

function TBaseAureliusDataset.GetCanModify: boolean;
begin
  Result := True;
end;

function TBaseAureliusDataset.GetFieldClass(FieldType: TFieldType): TFieldClass;
begin
  case FieldType of
    ftVariant: Result := TAureliusEntityField;
  else
    result := inherited GetFieldClass(FieldType);
  end;
end;

{$IFDEF DELPHIXE4_LVL}
function TBaseAureliusDataset.GetFieldData(Field: TField; var Buffer: TAureliusValueBuffer): boolean;
{$ELSE}
function TBaseAureliusDataset.GetFieldData(Field: TField; Buffer: TAureliusValueBuffer): boolean;
{$ENDIF}
var
  Data: ValueType;

  procedure VariantToFieldBuffer;
  var
    Str: string;
    SInt: Smallint;
    I: integer;
    I64: LargeInt;
    B: boolean;
    F: double;
    BCD: TBCD;
    D: TDateTime;
    {$IFDEF DELPHIXE3_LVL}
    TempValue: TValueBuffer;
    {$ENDIF}
    {$IFDEF NEXTGEN}
    M: TMarshaller;
    Len: Integer;
    {$ENDIF}
  begin
    case Field.DataType of
      ftString, ftFixedChar:
        begin
        {$IFNDEF NEXTGEN}
          {$IFDEF DELPHIXE3_LVL}
          FillChar(Buffer[0], Field.DataSize, 0);
          {$ELSE}
          FillChar(Buffer^, Field.DataSize, 0);
          {$ENDIF}
          Str := VarToStr(Data);
          {$IFDEF DELPHIXE4_LVL}
          AnsiStrings.StrLCopy(PAnsiChar(Buffer), PAnsiChar(AnsiString(Str)), Field.Size);
          {$ELSE}
          StrLCopy(PAnsiChar(Buffer), PAnsiChar(AnsiString(Str)), Field.Size);
          {$ENDIF}
        {$ELSE}
          FillChar(Buffer[0], Field.DataSize, 0);
          Str := VarToStr(Data);
          Len := Integer(Length(TMarshal.AsAnsi(Str)));
          if Len > Field.DataSize then
            Len := Field.DataSize;
          TMarshal.Copy(M.AsAnsi(Str), Buffer, 0, Len);
        {$ENDIF}
        end;
      ftWideString, ftFixedWideChar:
        begin
          {$IFDEF DELPHIXE3_LVL}
          FillChar(Buffer[0], Field.DataSize, 0);
          {$ELSE}
          FillChar(Buffer^, Field.DataSize, 0);
          {$ENDIF}
          Str := VarToStr(Data);
          StrLCopy(PChar(Buffer), PChar(Str), Field.Size);
        end;
      ftSmallint:
        begin
          SInt := Data;
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<SmallInt>(SInt, Buffer);
            {$ELSE}
            TBitConverter.FromSmallInt(SInt, Buffer);
            {$ENDIF}
          {$ELSE}
          Smallint(Buffer^) := SInt;
          {$ENDIF}
        end;
      ftInteger:
        begin
          I := Data;
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<Integer>(I, Buffer);
            {$ELSE}
            TBitConverter.FromInteger(I, Buffer);
            {$ENDIF}
          {$ELSE}
          integer(Buffer^) := I;
          {$ENDIF}
        end;
      ftLargeint:
        begin
          I64 := Data;
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<LargeInt>(I64, Buffer);
            {$ELSE}
            TBitConverter.FromLargeInt(I64, Buffer);
            {$ENDIF}
          {$ELSE}
          LargeInt(Buffer^) := I64;
          {$ENDIF}
        end;
      ftBoolean:
        begin
          B := Data;
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<WordBool>(B, Buffer);
            {$ELSE}
            TBitConverter.FromWordBool(B, Buffer);
            {$ENDIF}
          {$ELSE}
          WordBool(Buffer^) := B;
          {$ENDIF}
        end;
      ftFloat, ftCurrency:
        begin
          F := Data;
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<Double>(F, Buffer);
            {$ELSE}
            TBitConverter.FromDouble(F, Buffer);
            {$ENDIF}
          {$ELSE}
          double(Buffer^) := F;
          {$ENDIF}
        end;
      ftFmtBCD:
        begin
          BCD := VarToBcd(Data);
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_lVL}
            TDBBitConverter.UnsafeFrom<TBcd>(BCD, Buffer);
            {$ELSE}
            TBitConverter.FromBcd(BCD, Buffer);
            {$ENDIF}
          {$ELSE}
          TBCD(Buffer^) := BCD;
          {$ENDIF}
        end;
      ftDate, ftTime, ftDateTime:
        begin
          D := VarToDateTime(Data);
          {$IFDEF DELPHIXE3_LVL}
          if True then
          begin
            SetLength(TempValue, SizeOf(double));
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<Double>(D, TempValue);
            {$ELSE}
            TBitConverter.FromDouble(D, TempValue);
            {$ENDIF}
            DataConvert(Field, TempValue, Buffer, True);
          end
          else
          begin
            {$IFDEF DELPHIXE8_LVL}
            TDBBitConverter.UnsafeFrom<Double>(D, Buffer);
            {$ELSE}
            TBitConverter.FromDouble(D, Buffer);
            {$ENDIF}
          end;
          {$ELSE}
          //if NativeFormat then
          if True then
            DataConvert(Field, @D, Buffer, True)
          else
            TDateTime(Buffer^) := D;
          {$ENDIF}
        end;
      ftBlob, ftMemo, ftWideMemo, ftGraphic:
        begin
           // Not needed, blob stream uses GetBlobData directly
//          TempBytes := TUtils.VariantToBytes(Data);
//          System.Move(TempBytes[0], Buffer^, Length(TempBytes));
//          Variant(Buffer^) := Data;
        end;
      ftVariant:
        begin
          {$IFDEF DELPHIXE3_LVL}
          Variant(PVariant(@Buffer[0])^) := Data;
          {$ELSE}
          Variant(Buffer^) := Data;
          {$ENDIF}
        end;
      ftGuid:
        begin
        {$IFNDEF NEXTGEN}
          // Same as string
          {$IFDEF DELPHIXE3_LVL}
          FillChar(Buffer[0], Field.DataSize, 0);
          {$ELSE}
          FillChar(Buffer^, Field.DataSize, 0);
          {$ENDIF}
          Str := VarToStr(Data);
          {$IFDEF DELPHIXE4_LVL}
          AnsiStrings.StrLCopy(PAnsiChar(Buffer), PAnsiChar(AnsiString(Str)), Field.Size);
          {$ELSE}
          StrLCopy(PAnsiChar(Buffer), PAnsiChar(AnsiString(Str)), Field.Size);
          {$ENDIF}
        {$ELSE}
          // Same as string
          FillChar(Buffer[0], Field.DataSize * SizeOf(Char), 0);
          Str := VarToStr(Data);
          TMarshal.Copy(M.AsAnsi(Str), Buffer, 0, Field.DataSize);
        {$ENDIF}
        end
    else

      // ftWord,ftBytes,ftVarBytes,ftAutoInc,ftGraphic,ftFmtMemo,ftParadoxOle,ftDBaseOle,ftTypedBinary,
      // ftCursor,ftADT,ftArray,ftReference,ftDataSet,ftOraBlob,ftOraClob,ftVariant,ftInterface,ftIDispatch,
      // ftGuid,ftTimeStamp,ftFMTBcd,ftOraTimeStamp,ftOraInterval,ftLongWord,ftShortint,ftByte,ftExtended,
      // ftConnection,ftParams,ftStream,ftTimeStampOffset,ftObject,ftSingle,
    end;
  end;

  procedure ValueToFieldBuffer;
  begin
    VariantToFieldBuffer;
  end;

begin
  Result := GetFieldVariant(Field, Data);
  {$IFDEF DELPHIXE3_LVL}
  if Length(Buffer) = 0 then Exit;
  {$ENDIF}
  if Result and (Buffer <> nil) then
    ValueToFieldBuffer;
end;

function TBaseAureliusDataset.GetFieldVariant(Field: TField;
  var Data: ValueType): boolean;
var
  RecBuf: TAureliusRecordBuffer;
begin
  if FReserved = 0 then
  begin
    FReserved := FieldListCheckSum;
    UpdateCursorPos;
    Resync([]);
  end;

  RecBuf := GetActiveRecBuf;
  if RecBuf = NullBuffer then
    Exit(false);
  Data := GetBufferValueList(RecBuf)[Field.Index];
  if VarIsEmpty(Data) then
  begin
    Data := GetVariantValueForField(Field, RecBuf);
    GetBufferValueList(RecBuf)[Field.Index] := Data;
  end;
  Result := not VarIsNull(Data);
end;


function TBaseAureliusDataset.GetInternalList: IReadOnlyObjectList;
begin
  Result := Bcl.Collections.AsObjectList(FInternalList);
end;

function TBaseAureliusDataset.GetModifiedFields: TEnumerable<TField>;
begin
  Result := FModifiedFields;
end;

function TBaseAureliusDataset.GetOldValueBuffer: TAureliusRecordBuffer;
begin
  if IsEmpty then
    Result := NullBuffer
  else
  begin
    if (FOldValueBuffer <> NullBuffer) and (FModifiedFields.Count > 0) then
      Result := FOldValueBuffer
    else
      Result := ActiveBuffer;
  end;
end;

function TBaseAureliusDataset.GetPropValue(APropName: string; Obj: TObject): ValueType;
begin
end;

function TBaseAureliusDataset.GetRecNo: Integer;
var
  RecBuf: TAureliusRecordBuffer;
begin
  CheckActive;
  Result := -1;
  if (State in [dsInternalCalc, dsInsert]) then
    Exit;
  RecBuf := GetActiveRecBuf;
  if (RecBuf <> NullBuffer) and (GetBufferRecInfo(RecBuf)^.BookmarkFlag = bfCurrent) then
    Result := GetBufferRecInfo(RecBuf)^.Index + 1;
end;

function TBaseAureliusDataset.InternalGetRecord(Buffer: TAureliusRecordBuffer; GetMode: TGetMode; DoCheck: boolean): TGetResult;
begin
  try
    Result := grOK;
    case GetMode of
      gmNext:
        Inc(FCurrent);
      gmPrior:
        Dec(FCurrent);
    end;
    if FCurrent < 0 then
    begin
      Result := grBOF;
      FCurrent := -1;
    end
    else if FCurrent >= ListCount then
    begin
      repeat
        if not FetchMoreRecords(FSourceList) then
        begin
          Result := grEOF;
          if ListCount = 0 then
            FCurrent := -1
          else
            FCurrent := ListCount;
          break;
        end;
      until FCurrent < ListCount;
    end;

    if Result = grOK then
    begin
      with GetBufferRecInfo(Buffer)^ do
      begin
        Index := FCurrent;
        Obj := FSourceList.Item(FCurrent);
        BookmarkFlag := bfCurrent;
      end;
      Finalize(GetBufferValueList(Buffer)^, Fields.Count);
      GetCalcFields(Buffer);
    end;
  except
    Result := grError;
    if DoCheck then
      raise ;
  end;
end;

function TBaseAureliusDataset.GetRecord(Buffer: TAureliusRecordBuffer; GetMode: TGetMode; DoCheck: boolean): TGetResult;
var
  SaveState: TDataSetState;
  AcceptRecord: Boolean;
begin
  if Filtered and Assigned(OnFilterRecord) then
  begin
    FFilterBuffer := Buffer;
    SaveState := SetTempState(dsFilter);
    try
      AcceptRecord := True;
      repeat
        Result := InternalGetRecord(Buffer, GetMode, DoCheck);
        if Result = grOK then
        begin
          if FCurrent >= Length(FFilteredRows) then
            SetLength(FFilteredRows, FCurrent + 1);
          if FFilteredRows[FCurrent] = TRowFilterState.Undefined then
          begin
            OnFilterRecord(Self, AcceptRecord);
            if AcceptRecord then
              FFilteredRows[FCurrent] := TRowFilterState.Accepted
            else
              FFilteredRows[FCurrent] := TRowFilterState.Rejected;
            if not AcceptRecord then
              Inc(FFilteredCount);
            Inc(FFilteredProcessed);
          end else
            AcceptRecord := FFilteredRows[FCurrent] = TRowFilterState.Accepted;

          if not AcceptRecord and (GetMode = gmCurrent) then
            Result := grError;
        end;
      until AcceptRecord or (Result <> grOK);
    except
      InternalHandleException;
      Result := grError;
    end;
    RestoreState(SaveState);
  end
  else
    Result := InternalGetRecord(Buffer, GetMode, DoCheck)
end;

function TBaseAureliusDataset.GetRecordCount: integer;
begin
  Result := 0;
  if not (State in [dsInactive, dsOpening]) then
  begin
    if not Filtered then
    begin
      if FetchingRecords then
      begin
        if (RecordCountMode = TRecordCountMode.Retrieve) then
          Result := RetrieveRecordCount
        else
          Result := -1;
      end
      else
        Result := ListCount;
    end
    else
    begin
      if FetchingRecords or (FFilteredProcessed < ListCount) then
        Result := -1
      else
      begin
        Result := ListCount - FFilteredCount;
      end;
    end;
  end;
end;

function TBaseAureliusDataset.GetRecordSize: Word;
begin
  Result := FRecordSize;
end;

{$IFDEF DELPHIXE3_LVL}
procedure TBaseAureliusDataset.GetTheFieldList(List: TList<TField>;
  const FieldNames: string);
begin
  GetFieldList(List, FieldNames);
end;
{$ELSE}
procedure TBaseAureliusDataset.GetTheFieldList(List: TList<TField>;
  const FieldNames: string);
var
  TheFields: TList;
  I: integer;
begin
  TheFields := TList.Create;
  try
    GetFieldList(TheFields, FieldNames);
    List.Clear;
    for I := 0 to TheFields.Count - 1 do
      List.Add(TField(TheFields[I]));
  finally
    TheFields.Free;
  end;
end;
{$ENDIF}

function TBaseAureliusDataset.GetVariantValueForField(Field: TField;
  Buffer: TAureliusRecordBuffer): ValueType;
var
  Obj: TObject;
begin
  Obj := GetBufferRecInfo(Buffer)^.Obj;
  Result := Null;
  if Obj = nil then Exit;

  if IsSelfField(Field) then
    Result := ObjectToVariant(Obj)
  else
  begin
    if Field.FieldKind = fkData then
      Result := GetPropValue(Field.FieldName, Obj);
  end;
end;

procedure TBaseAureliusDataset.InitializeFieldDefinitions;
begin
end;

//procedure TBaseAureliusDataset.InternalAddRecord(Buffer: Pointer; Append: boolean);
//begin
//end;

procedure TBaseAureliusDataset.InternalCancel;
var
  Obj: TObject;
begin
  inherited;
  if FErrorOnPost and FRefreshObjectOnCancel and (State = dsEdit) then
  begin
    Obj := GetBufferRecInfo(GetActiveRecBuf)^.Obj;
    if Obj <> nil then
      DoObjectRefresh(Obj);
  end;
end;

procedure TBaseAureliusDataset.InternalClose;
begin
  FIsOpen := false;
  BindFields(false);
  {$IFDEF DELPHIXE6_LVL}
  DestroyFields;
  {$ELSE}
  if DefaultFields then
    DestroyFields;
  {$ENDIF}

  // Free internal buffers

  FCurrent := -1;
  ClearInternalList;
  SetLength(FFilteredRows, 0);
  FFilteredCount := 0;
  FFilteredProcessed := 0;
  FreeAndNil(FSource);
end;

function TBaseAureliusDataset.InternalCreateObject(AClass: TClass): TObject;
begin
  Result := nil;
end;

procedure TBaseAureliusDataset.InternalDelete;
var
  RecBuf: TAureliusRecordBuffer;
  Obj: TObject;
  Index: integer;
begin
  RecBuf := GetActiveRecBuf;
  Obj := GetBufferRecInfo(RecBuf)^.Obj;
  Index := FSourceList.IndexOf(Obj);

  if Index > -1 then
  begin
    DoObjectRemove(Obj);
    FSourceList.Delete(Index);
    if FCurrent >= ListCount then
      FCurrent := ListCount - 1;
  end;
end;

procedure TBaseAureliusDataset.InternalEdit;
begin
  inherited;
  FModifiedFields.Clear;
  FErrorOnPost := False;
end;

procedure TBaseAureliusDataset.InternalFirst;
begin
  FCurrent := -1;
end;

procedure TBaseAureliusDataset.InternalGotoBookmark(Bookmark: TAureliusBookmark);
var
  Index: integer;
begin
  Index := FSourceList.IndexOf(ObjectFromBookmark(Bookmark));
  if Index <> -1 then
    FCurrent := Index
  else
    DatabaseError('Bookmark not found');
end;

procedure TBaseAureliusDataset.InternalHandleException;
begin
  if Assigned(Classes.ApplicationHandleException) then
    ApplicationHandleException(Self);
end;

procedure TBaseAureliusDataset.InternalInitFieldDefs;
var
  FieldDef: TFieldDef;
begin
  FieldDefs.Clear;
  if not (csDesigning in ComponentState) and (FieldCount > 0) then
  begin
    InitFieldDefsFromFields;
    Exit;
  end;

  if CreateSelfField then // Add Self field def? True by default
  begin
    FieldDef := FieldDefs.AddFieldDef;
    FieldDef.Name := SelfFieldName;
    FieldDef.DataType := ftVariant;
    FieldDef.Attributes := FieldDef.Attributes + [TFieldAttribute.faReadonly];
  end;

  InitializeFieldDefinitions;
end;

procedure TBaseAureliusDataset.InternalInitRecord(Buffer: TAureliusRecordBuffer);
var
  I: integer;
  Data: Variant;
begin
  // Create the underlying object
  if not CreateObjectOnPost then
  begin
    FreeAndNil(FInsertObject);
    FInsertObject := CreateObject;
    GetBufferRecInfo(Buffer)^.Obj := FInsertObject;
  end;

  for I := 0 to Fields.Count - 1 do
  begin
    if FDefaultsFromObject then
      Data := GetVariantValueForField(Fields[I], Buffer)
    else
      Data := Null;
    GetBufferValueList(Buffer)[I] := Data;
  end;
end;

procedure TBaseAureliusDataset.InternalInsert;
begin
  inherited;
  FErrorOnPost := False;
end;

{$IFDEF DELPHIXE4_LVL}
{$IFNDEF NEXTGEN}
procedure TBaseAureliusDataset.InternalInitRecord(Buffer: TRecordBuffer);
begin
  InternalInitRecord(TAureliusRecordBuffer(Buffer));
end;
{$ENDIF}
{$ENDIF}

procedure TBaseAureliusDataset.InternalLast;
begin
  FetchAllRecords;
  FCurrent := ListCount;
end;

procedure TBaseAureliusDataset.InternalObjectInsert(Obj: TObject);
begin
end;

procedure TBaseAureliusDataset.InternalObjectRemove(Obj: TObject);
begin
end;

procedure TBaseAureliusDataset.InternalObjectUpdate(Obj: TObject);
begin
end;

procedure TBaseAureliusDataset.InternalObjectRefresh(Obj: TObject);
begin
end;

procedure TBaseAureliusDataset.InternalOpen;
begin
  if DataSetField <> nil then
    UpdateListFromParent(DataSetField);
  if FSourceList = nil then
    raise ESourceListNotAssigned.Create;
  if FObjectClass = nil then
    raise EObjectClassNotSpecified.Create;
  FCurrent := -1;
  SetLength(FFilteredRows, 0);
  FFilteredCount := 0;
  FFilteredProcessed := 0;
  FRecordCountRetrieved := false;
  FIsOpen := True;
  InternalInitFieldDefs;

  {$IFDEF DELPHIXE6_LVL}
  CreateFields;
  {$ELSE}
  if DefaultFields then
    CreateFields;
  {$ENDIF}
  FReserved := FieldListCheckSum;
  BindFields(True);

  // Initialize buffer pointers
  FRecordSize := Fields.Count * SizeOf(ValueType);
  FRecBufSize := FRecordSize + SizeOf(TRecInfo);
  FRecInfoOffset := FRecordSize;
end;

procedure TBaseAureliusDataset.InternalPost;
var
  RecBuf: TAureliusRecordBuffer;

  procedure UpdateObjectFields(Obj: TObject);
  var
    Field: TField;
    Value: ValueType;
  begin
    for Field in FModifiedFields do
    begin
      if Field.FieldKind = fkData then
      begin
        Value := GetBufferValueList(RecBuf)[Field.Index];
        SetPropValue(Field.FieldName, Obj, Value);
      end;
    end;
  end;

var
  Obj: TObject;
  IndexToInsert: integer;
begin
  inherited;
  UpdateCursorPos;
  RecBuf := GetActiveRecBuf;
  if State = dsEdit then
  begin
    Obj := GetBufferRecInfo(RecBuf)^.Obj;
    UpdateObjectFields(Obj);
    try
      DoObjectUpdate(Obj);
    except
      FErrorOnPost := True;
      raise;
    end;
  end
  else
  begin
    case GetBufferRecInfo(RecBuf)^.BookmarkFlag of
      bfBOF:
        IndexToInsert := 0;
      bfEOF:
        IndexToInsert := -1;
    else
      IndexToInsert := FSourceList.IndexOf(GetBufferRecInfo(RecBuf)^.Obj);
    end;
    if FInsertObject = nil then
      FInsertObject := CreateObject;
    Obj := FInsertObject;
    UpdateObjectFields(Obj);
    if IndexToInsert = -1 then
      FetchAllRecords;

    try
      DoObjectInsert(Obj);
    except
      FErrorOnPost := True;
      raise;
    end;
    if IndexToInsert = -1 then
      FSourceList.Add(Obj)
    else
      FSourceList.Insert(IndexToInsert, Obj);
    FInsertObject := nil;
  end;
  FErrorOnPost := False;
end;

procedure TBaseAureliusDataset.InternalSetSourceList(SourceList: TObject);
begin
  if SourceList = nil then
  begin
    FSourceList := nil;
  end
  else
  begin
    if not Bcl.Collections.IsObjectList(SourceList.ClassType) then
      raise EInvalidListObject.Create(SourceList);
    FSourceList := Bcl.Collections.AsObjectList(SourceList);
    ObjectClass := TRttiUtils.GetInstance.GetSurroundedClass(SourceList.ClassType);
  end;
  RestartFilter;
end;

procedure TBaseAureliusDataset.InternalSetToRecord(Buffer: TAureliusRecordBuffer);
begin
  GetBookmarkData(Buffer, FSetToRecBmk);
  InternalGotoBookmark(FSetToRecBmk);
end;

function TBaseAureliusDataset.IsCursorOpen: boolean;
begin
  Result := FIsOpen;
end;

function TBaseAureliusDataset.IsEntityField(Field: TField): Boolean;
begin
  Result := Field is TAureliusEntityField;
end;

function TBaseAureliusDataset.IsModified(const FieldName: string): Boolean;
begin
  Result := FModifiedFields.IndexOf(FindField(FieldName)) >= 0;
end;

function TBaseAureliusDataset.IsSelfField(Field: TField): boolean;
begin
  Result := Field.FieldName = SelfFieldName;
end;

function TBaseAureliusDataset.IsSequenced: Boolean;
begin
  Result := not FetchingRecords or ((FSource <> nil) and FSource.CanRetrieveRecordCount);
end;

function TBaseAureliusDataset.ListCount: integer;
begin
  Result := FSourceList.Count;
end;

function TBaseAureliusDataset.Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): boolean;
begin
  DoBeforeScroll;
  Result := LocateRecord(KeyFields, KeyValues, Options, True, '');
  if Result then
  begin
    Resync([rmExact, rmCenter]);
    DoAfterScroll;
  end;
end;

function TBaseAureliusDataset.LocateRecord(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions;
  SyncCursor: Boolean; ResultFields: string): Boolean;
var
  FieldList: TList<TField>;

  function MatchField(Field: TField; Value: Variant; Options: TLocateOptions): Boolean;
  var
    FieldValue: string;
  begin
    case Field.DataType of
      ftString, ftFixedChar, ftWideString, ftFixedWideChar:
        begin
          FieldValue := VarToStr(Field.Value);
          if loPartialKey in Options then
            FieldValue := Copy(FieldValue, 1, Length(Value));
          if loCaseInsensitive in Options then
            Result := SameText(VarToStr(Value), FieldValue)
          else
            Result := SameStr(VarToStr(Value), FieldValue);
        end;
    else
      Result := (Field.Value = Value);
    end;
  end;

  function MatchRecord: boolean;
  var
    I: integer;
  begin
    Result := False;
    for I := 0 to FieldList.Count - 1 do
    begin
      if (FieldList.Count = 1) and not VarIsArray(KeyValues) then
        Result := MatchField(FieldList[I], KeyValues, Options)
      else
        Result := MatchField(FieldList[I], KeyValues[I], Options);
      if not Result then
        Break;
    end;
  end;

var
  OldIndex: Integer;
  Buffer: TAureliusRecordBuffer;
  ResultFieldList: TList<TField>;
  I: integer;
  {$IFDEF DELPHIXE4_LVL}
  NullValueBuffer: TValueBuffer;
  {$ENDIF}
begin
  CheckBrowseMode;
  CursorPosChanged;
  Result := False;
  FieldList := TList<TField>.Create;
  try
    GetTheFieldList(FieldList, KeyFields);
    OldIndex := FCurrent;
    SetTempState(dsFilter);
    Buffer := TempBuffer;
    FFilterBuffer := Buffer;
    try
      InternalFirst;
      while GetRecord(Buffer, gmNext, True) = grOK do
        if MatchRecord then
        begin
          Result := True;
          break;
        end;


      if Result and not SyncCursor then
      begin
        // For lookups, read needed field values into the temp buffer
        ResultFieldList := TList<TField>.Create;
        try
          GetTheFieldList(ResultFieldList, ResultFields);
          // For optimization, update only the values for desired result fields
          for I := 0 to ResultFieldList.Count - 1 do
          begin
            {$IFDEF DELPHIXE4_LVL}
              SetLength(NullValueBuffer, 0);
              GetFieldData(ResultFieldList[I], NullValueBuffer);
            {$ELSE}
              {$IFDEF DELPHIXE3_LVL}
              GetFieldData(ResultFieldList[I], TValueBuffer(nil));
              {$ELSE}
              GetFieldData(ResultFieldList[I], nil);
              {$ENDIF}
            {$ENDIF}
          end;
//          CalculateFields(Buffer);
        finally
          ResultFieldList.Free;
        end;
      end;

      // Restore old cursor position
      if not (Result and SyncCursor) then
        FCurrent := OldIndex;
    finally
      RestoreState(dsBrowse);
    end;
  finally
    FieldList.Free;
  end;
end;

function TBaseAureliusDataset.Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant;
begin
  VarClear(Result);
  if LocateRecord(KeyFields, KeyValues, [], False, ResultFields) then
  begin
    SetTempState(dsFilter);
    try
      Result := FieldValues[ResultFields];
    finally
      RestoreState(dsBrowse);
    end;
  end;
end;

function TBaseAureliusDataset.ObjectFromBookmark(
  ABookmark: TBookmark): TObject;
begin
  Result := PADSBookmarkData(@ABookmark[0])^.Obj2;
end;

class function TBaseAureliusDataset.ObjectToVariant(Obj: TObject): Variant;
begin
  {$IFDEF DELPHIXE2_LVL}
  Result := IntPtr(Obj);
  {$ELSE}
  Result := integer(Obj);
  {$ENDIF}
end;

procedure TBaseAureliusDataset.ReadDesignClass(Reader: TReader);
begin
  FDesignClass := Reader.ReadString;
end;

procedure TBaseAureliusDataset.ReadDesignPackage(Reader: TReader);
begin
  FDesignPackage := Reader.ReadString;
end;

procedure TBaseAureliusDataset.RestartFilter;
begin
  SetLength(FFilteredRows, 0);
  FFilteredCount := 0;
  FFilteredProcessed := 0;
end;

function TBaseAureliusDataset.RetrieveRecordCount: Integer;
begin
  if (FSource <> nil) and FSource.CanRetrieveRecordCount then
  begin
    { If we're fetching records using criteria and RecordCountMode allow us to do so,
      we will perform an extra select to retrieve the total number of records }
    if not FRecordCountRetrieved then
    begin
      FCachedRecordCount := FSource.RetrieveRecordCount;
      FRecordCountRetrieved := True;
    end;
    Result := FCachedRecordCount;
  end
  else
    Result := -1;
end;

procedure TBaseAureliusDataset.SetBookmarkData(Buffer: TAureliusRecordBuffer; Data: TAureliusBookmark);
begin
  GetBufferRecInfo(Buffer)^.Obj := ObjectFromBookmark(Data);
end;

procedure TBaseAureliusDataset.SetBookmarkFlag(Buffer: TAureliusRecordBuffer; Value: TBookmarkFlag);
begin
  GetBufferRecInfo(Buffer)^.BookmarkFlag := Value;
end;

procedure TBaseAureliusDataset.SetDataSetField(const Value: TDataSetField);
begin
  if Assigned(Value) then
    UpdateListFromParent(Value);
  inherited SetDataSetField(Value);
end;

procedure TBaseAureliusDataset.SetFieldData(Field: TField; Buffer: TAureliusValueBuffer);
begin
  SetFieldData(Field, Buffer, True);
end;

procedure TBaseAureliusDataset.SetFieldData(Field: TField; Buffer: TAureliusValueBuffer; NativeFormat: boolean);

  function FieldBufferToVariant(Buffer: TAureliusValueBuffer): Variant;
  var
    SInt: Smallint;
    I: integer;
    I64: LargeInt;
    B: boolean;
    F: double;
    BCD: TBCD;
    {$IFDEF DELPHIXE3_LVL}
    TempValue: TValueBuffer;
    {$ELSE}
    D: TDateTime;
    {$ENDIF}
    {$IFDEF NEXTGEN}
    NullIndex: integer;
    Str: string;
    {$ENDIF}
  begin
    case Field.DataType of
      ftString, ftFixedChar:
        begin
          {$IFNDEF NEXTGEN}
          Result := AnsiString(PAnsiChar(Buffer));
          {$ELSE}
          Str := TEncoding.ANSI.GetString(Buffer);
          NullIndex := Str.IndexOf(#0);
          if NullIndex >= 0 then
            Result := Str.Remove(NullIndex)
          else
            Result := Str;
          {$ENDIF}
        end;
      ftWideString, ftFixedWideChar:
        begin
          {$IFNDEF NEXTGEN}
          Result := WideString(PWideChar(Buffer));
          {$ELSE}
          Result := string(PChar(Buffer));
          {$ENDIF}
        end;
      ftSmallint:
        begin
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            SInt := TDBBitConverter.UnsafeInTo<SmallInt>(Buffer);
            {$ELSE}
            SInt := TBitConverter.ToSmallInt(Buffer);
            {$ENDIF}
          {$ELSE}
          SInt := Smallint(Buffer^);
          {$ENDIF}
          Result := SInt;
        end;
      ftInteger:
        begin
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            I := TDBBitConverter.UnsafeInto<Integer>(Buffer);
            {$ELSE}
            I := TBitConverter.ToInteger(Buffer);
            {$ENDIF}
          {$ELSE}
          I := integer(Buffer^);
          {$ENDIF}
          Result := I;
        end;
      ftLargeint:
        begin
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            I64 := TDBBitConverter.UnsafeInto<Int64>(Buffer);
            {$ELSE}
            I64 := TBitConverter.ToLargeInt(Buffer);
            {$ENDIF}
          {$ELSE}
          I64 := LargeInt(Buffer^);
          {$ENDIF}
          Result := I64;
        end;
      ftBoolean:
        begin
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            B := TDBBitConverter.UnsafeInto<WordBool>(Buffer);
            {$ELSE}
            B := TBitConverter.ToWordBool(Buffer);
            {$ENDIF}
          {$ELSE}
          B := WordBool(Buffer^);
          {$ENDIF}
          Result := B;
        end;
      ftFloat, ftCurrency:
        begin
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            F := TDBBitConverter.UnsafeInto<Double>(Buffer);
            {$ELSE}
            F := TBitConverter.ToDouble(Buffer);
            {$ENDIF}
          {$ELSE}
          F := double(Buffer^);
          {$ENDIF}
          Result := F;
        end;
      ftFmtBCD:
        begin
          {$IFDEF DELPHIXE3_LVL}
            {$IFDEF DELPHIXE8_LVL}
            BCD := TDBBitConverter.UnsafeInto<TBcd>(Buffer);
            {$ELSE}
            BCD := TBitConverter.ToBcd(Buffer);
            {$ENDIF}
          {$ELSE}
          BCD := TBCD(Buffer^);
          {$ENDIF}
          VarFMTBcdCreate(Result, BCD);
        end;
      ftDate, ftTime, ftDateTime:
        begin
          {$IFDEF DELPHIXE3_LVL}
          if NativeFormat then
          begin
            SetLength(TempValue, SizeOf(TVarData(Result).VDate));
            DataConvert(Field, Buffer, TempValue, False);
            {$IFDEF DELPHIXE8_LVL}
            TVarData(Result).VDate := TDBBitConverter.UnsafeInto<Double>(TempValue);
            {$ELSE}
            TVarData(Result).VDate := TBitConverter.ToDouble(TempValue);
            {$ENDIF}
          end else
          begin
            {$IFDEF DELPHIXE8_LVL}
            Result := TDateTime(TDBBitConverter.UnsafeInto<Double>(Buffer));
            {$ELSE}
            Result := TDateTime(TBitConverter.ToDouble(Buffer));
            {$ENDIF}
          end;
          {$ELSE}
          if NativeFormat then
          begin
            DataConvert(Field, Buffer, @D, False);
            Result := D;
          end
          else
            Result := TDateTime(Buffer^);
          {$ENDIF}
        end;
      ftBlob, ftGraphic:
        begin
          Result := TUtils.BytesToVariant(TBytes(Buffer));
        end;
      ftMemo:
        begin
          {$IFNDEF NEXTGEN}
          Result := AnsiString(StringOf(TBytes(Buffer)));
          {$ELSE}
          Str := TEncoding.ANSI.GetString(Buffer);
          NullIndex := Str.IndexOf(#0);
          if NullIndex >= 0 then
            Result := Str.Remove(NullIndex)
          else
            Result := Str;
          {$ENDIF}
        end;
      ftWideMemo:
        begin
          Result := WideStringOf(TBytes(Buffer));
        end;
      ftVariant:
        begin
          {$IFDEF DELPHIXE3_LVL}
          Result := Variant(PVariant(@Buffer[0])^);
          {$ELSE}
          Result := Variant(Buffer^);
          {$ENDIF}
        end;
      ftGuid:
        begin
          {$IFNDEF NEXTGEN}
          Result := AnsiString(PAnsiChar(Buffer));
          {$ELSE}
          Str := TEncoding.ANSI.GetString(Buffer);
          NullIndex := Str.IndexOf(#0);
          if NullIndex >= 0 then
            Result := Str.Remove(NullIndex)
          else
            Result := Str;
          {$ENDIF}
        end;
    end;
  end;

  function FieldBufferToValue(Buffer: TAureliusValueBuffer): Variant;
  begin
    Result := FieldBufferToVariant(Buffer);
  end;

var
  RecBuf: TAureliusRecordBuffer;
  Data: ValueType;
  Prefix: string;
  LenPrefix: Integer;
  I: Integer;
  SubField: TField;
begin
  if not(State in dsWriteModes) then
    DatabaseError(SNotEditing, Self);
  if IsSelfField(Field) then
    Exit;

  RecBuf := GetActiveRecBuf;
  if RecBuf = NullBuffer then
    Exit;

  if Field.FieldNo > 0 then
  begin
    if Field.ReadOnly and not(State in [dsSetKey, dsFilter]) then
      DatabaseErrorFmt(SFieldReadOnly, [Field.DisplayName]);
    Field.Validate(Buffer);
    if FModifiedFields.IndexOf(Field) = -1 then
      FModifiedFields.Add(Field);
  end;

  if Buffer = nil then
    Data := Null
  else
    Data := FieldBufferToValue(Buffer);
  GetBufferValueList(RecBuf)[Field.Index] := Data;
  if not(State in [dsCalcFields, dsInternalCalc, dsFilter, dsNewValue]) then
    DataEvent(deFieldChange, IntPtr(Field));

  if SyncSubprops and IsEntityField(Field) and not FSyncingSubprops then
  begin
    FSyncingSubprops := True;
    try
      Prefix := Field.FieldName + '.';
      LenPrefix := Length(Prefix);

      // Iterate through all fields that are subproperties of this field,
      // but only update fields that are not sub-subproperties
      for I := 0 to FieldCount - 1 do
      begin
        SubField := Fields[I];
        if SameText(Prefix, Copy(SubField.FieldName, 1, LenPrefix)) and (SubField.FieldKind = fkData) then
        begin
          GetBufferValueList(RecBuf)[SubField.Index] := GetPropValue(Copy(SubField.FieldName, LenPrefix + 1, MaxInt), TAureliusEntityField(Field).AsObject);
          DataEvent(deFieldChange, IntPtr(SubField));
          FModifiedFields.Remove(SubField);
        end;
      end;
    finally
      FSyncingSubprops := False;
    end;
  end;
end;

procedure TBaseAureliusDataset.SetFiltered(Value: Boolean);
begin
  if Value <> Filtered then
  begin
    inherited;
    RestartFilter;
    if Active then
    begin
      if Filtered then
        First
      else
        Refresh;
    end;
  end;
end;

procedure TBaseAureliusDataset.SetObjectClass(const Value: TClass);
begin
  if FObjectClass <> Value then
  begin
    FObjectClass := Value;
    DataEvent(dePropertyChange, 0);
  end;
end;

procedure TBaseAureliusDataset.SetPropValue(APropName: string; Obj: TObject; Value: ValueType);
begin
end;

procedure TBaseAureliusDataset.SetRecNo(Value: integer);
begin
  CheckBrowseMode;
  if Value < 1 then
    Value := 1;
  if Value > ListCount then
  begin
    while FetchingRecords and (Value > ListCount) do
      FetchMoreRecords(FSourceList);
    if Value > ListCount then
      Value := ListCount;
  end;
  if RecNo <> Value then
  begin
    DoBeforeScroll;
    FCurrent := Value - 1;
    Resync([rmCenter]);
    DoAfterScroll;
  end;
end;

procedure TBaseAureliusDataset.SetSource(ASource: TObjectSource);
begin
  CheckInactive;
  FSource := ASource;
  ClearInternalList;
  SetSourceToInternalList;
  ObjectClass := FSource.ObjectClass;
end;

procedure TBaseAureliusDataset.SetSourceList(SourceList: TObject);
begin
  CheckInactive;
  InternalSetSourceList(SourceList);
end;

procedure TBaseAureliusDataset.SetSourceObject(SourceObject: TObject);
begin
  CheckInactive;
  ClearInternalList;
  FInternalList.Add(SourceObject);
  InternalSetSourceList(FInternalList);
  ObjectClass := SourceObject.ClassType;
end;

procedure TBaseAureliusDataset.SetSourceToInternalList;
begin
  InternalSetSourceList(FInternalList);
end;

procedure TBaseAureliusDataset.UpdateListFromParent(Field: TDatasetField);
var
  List: TObject;
  Parent: TBaseAureliusDataset;
  MasterObject: TObject;
begin
  if Field = nil then Exit;
  Parent := Field.DataSet as TBaseAureliusDataset;
  MasterObject := Parent.Current<TObject>;
  if MasterObject <> nil then
    List := VariantToObject(GetPropValue(Field.FieldName, MasterObject))
  else
    List := nil;

  if List <> nil then
  begin
    InternalSetSourceList(List);
  end
  else
  begin
    InternalSetSourceList(FInternalList);
    UpdateObjectClassFromParent(Parent, Field);
  end;
end;

procedure TBaseAureliusDataset.UpdateObjectClassFromParent(
  Parent: TBaseAureliusDataset; Field: TField);
begin
end;

class function TBaseAureliusDataset.VariantToObject(V: Variant): TObject;
begin
  if VarIsNull(V) then Exit(nil);
  {$IFDEF DELPHIXE2_LVL}
  Result := TObject(IntPtr(V));
  {$ELSE}
  Result := TObject(integer(V));
  {$ENDIF}
end;

procedure TBaseAureliusDataset.WriteDesignClass(Writer: TWriter);
begin
  Writer.WriteString(FDesignClass);
end;

procedure TBaseAureliusDataset.WriteDesignPackage(Writer: TWriter);
begin
  Writer.WriteString(FDesignPackage);
end;

{ TObjBlobStream }

constructor TObjBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);
begin
  FField := Field;
  FFieldNo := FField.FieldNo;
  FDataSet := FField.DataSet as TBaseAureliusDataset;
  if Mode <> bmRead then
  begin
    if FField.ReadOnly then
      DatabaseErrorFmt(SFieldReadOnly, [FField.DisplayName], FDataSet);
    if not(FDataSet.State in [dsEdit, dsInsert, dsNewValue]) then
      DatabaseError(SNotEditing, FDataSet);
  end;
  FBuffer := FDataSet.GetActiveRecBuf;
  if FBuffer = TBaseAureliusDataset.NullBuffer then
    Exit;
  if Mode = bmWrite then
    Truncate
  else
    ReadBlobData;
end;

destructor TObjBlobStream.Destroy;
var
  LocalBytes: TBytes;
begin
  if FModified then
    try
      SetLength(LocalBytes, Size);
      Move(Self.Memory^, LocalBytes[0], Size);
      FDataset.SetFieldData(FField, LocalBytes);

//      FDataset.SetFieldData(FField, @LocalBytes[0]);

//      case FField.DataType of
//        ftMemo: data.db
//          VariantData := AnsiString(StringOf(LocalBytes));
//        ftWideMemo:
//          VariantData := WideStringOf(LocalBytes);
//      else
//        VariantData := TUtils.BytesToVariant(LocalBytes);
//      end;
//      FDataSet.SetFieldData(FField, @VariantData);

      FField.Modified := True;
      FDataSet.DataEvent(deFieldChange, IntPtr(FField));
    except
      if Assigned(Classes.ApplicationHandleException) then
        ApplicationHandleException(Self);
    end;
  inherited Destroy;
end;

procedure TObjBlobStream.ReadBlobData;
var
  LocalBytes: TBytes;
begin
  LocalBytes := FDataSet.GetBlobData(FField);
  Clear;
  Write(LocalBytes[0], Length(LocalBytes));
  Position := 0;
  FModified := false;
end;

procedure TObjBlobStream.Truncate;
begin
  Clear;
  FModified := True;
end;

function TObjBlobStream.Write(const Buffer; Count: Longint): Longint;
begin
  Result := inherited Write(Buffer, Count);
  FModified := True;
end;

{ TEntityField }

function TAureliusEntityField.AsEntity<T>: T;
begin
  Result := T(AsObject);
end;

function TAureliusEntityField.GetAsObject: TObject;
begin
  Result := TBaseAureliusDataset.VariantToObject(AsVariant);
end;

{$IFDEF DELPHIXE3_LVL}
function TAureliusEntityField.GetAsVariant: Variant;
var
  TempBuff: TValueBuffer;
begin
  SetLength(TempBuff, SizeOf(Variant));
  if GetData(TempBuff) then
    // TBitConverter.ToVariant is wrong! (buggy)
    //Result := TBitConverter.ToVariant(TempBuff)
    System.Move(TempBuff[0], Result, SizeOf(Variant))
  else
    Result := Null;
end;
{$ENDIF}

{$IFDEF DELPHIXE3_LVL}
procedure TAureliusEntityField.SetVarValue(const Value: Variant);
begin
  SetData(BytesOf(@Value, SizeOf(Variant)));
end;
{$ENDIF}

procedure TAureliusEntityField.SetAsObject(const Value: TObject);
begin
  {$IFDEF NEXTGEN}
  FObject := Value;
  {$ENDIF}
  AsVariant := TBaseAureliusDataset.ObjectToVariant(Value);
end;

{ TObjectSource }

function TObjectSource.CanRetrieveRecordCount: Boolean;
begin
  Result := False;
end;

function TObjectSource.RetrieveRecordCount: Integer;
begin
  Result := -1;
end;

end.
