{ *********************************************************************** }
{                                                                         }
{ Delphi Visual Component Library                                         }
{                                                                         }
{ Copyright (c) 1999-2004 Borland Software Corporation                    }
{                                                                         }
{ *********************************************************************** }

unit Borland.Vcl.ADONETDb;

interface

{$R-,Q-}

uses
  Windows, Variants, SysUtils, TypInfo, Contnrs, Classes, Db, FMTBcd, SqlTimSt,
  System.Data, System.Data.Common, System.ComponentModel, System.Text;

type

{ field types }

  TADONETField = class(TField)
  private
    FValidateValue: TObject;
    FValidating: Boolean;
    procedure ValidateValue(Value: TObject);
  protected
    class procedure CheckTypeSize(Value: Integer); override;
    function GetIsNull: Boolean; override;
    function GetAsVariant: Variant; override;
  public
    procedure Clear; override;
    function GetData(var Value: TObject): Boolean; reintroduce;
    procedure SetData(Value: TObject); reintroduce;
  end;

  TADONETBooleanField = class(TADONETField)
  private
    FDisplayValues: string;
    FTextValues: array[Boolean] of string;
    procedure LoadTextValues;
    procedure SetDisplayValues(const Value: string);
  protected
    function GetDefaultWidth: Integer; override;
    function GetAsBoolean: Boolean; override;
    function GetAsByteArray: TBytes; override;
    function GetAsString: string; override;
    function GetDataSize: Integer; override;
    procedure SetAsBoolean(Value: Boolean); override;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    property Value: Boolean read GetAsBoolean write SetAsBoolean;
  published
    property DisplayValues: string read FDisplayValues write SetDisplayValues;
  end;

  TADONETStringField = class(TADONETField)
  protected
    class procedure CheckTypeSize(Value: Integer); override;
    function GetAsBCD: TBcd; override;
    function GetAsBoolean: Boolean; override;
    function GetAsByteArray: TBytes; override;
    function GetAsDateTime: TDateTime; override;
    function GetAsFloat: Double; override;
    function GetAsInteger: Longint; override;
    function GetAsSqlTimeStamp: TSQLTimeStamp; override;
    function GetAsString: string; override;
    function GetDataSize: Integer; override;
    function GetDefaultWidth: Integer; override;
    procedure GetText(var Text: string; DisplayText: Boolean); override;
    function GetValue(var Value: string): Boolean;
    procedure SetAsBCD(const Value: TBcd); override;
    procedure SetAsBoolean(Value: Boolean); override;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsDateTime(Value: TDateTime); override;
    procedure SetAsFloat(Value: Double); override;
    procedure SetAsInteger(Value: Longint); override;
    procedure SetAsSQLTimeStamp(const Value: TSQLTimeStamp); override;
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetFieldType(Value: TFieldType); override;
    property Value: string read GetAsString write SetAsString;
  published
    property EditMask;
    property Size default 20;
  end;

  TADONETNumericField = class(TADONETField)
  private
    FDisplayFormat: string;
    FEditFormat: string;
  protected
    procedure RangeError(Value, Min, Max: Extended);
    procedure SetDisplayFormat(const Value: string);
    procedure SetEditFormat(const Value: string);
  public
    constructor Create(AOwner: TComponent); override;
  published
    property Alignment default taRightJustify;
    property DisplayFormat: string read FDisplayFormat write SetDisplayFormat;
    property EditFormat: string read FEditFormat write SetEditFormat;
  end;

  TADONETIntegerField = class(TADONETNumericField)
  private
    FMinRange: Largeint;
    FMaxRange: Largeint;
    FMinValue: Largeint;
    FMaxValue: Largeint;
    procedure CheckRange(Value, Min, Max: LargeInt);
    procedure SetMaxValue(Value: LargeInt);
    procedure SetMinValue(Value: LargeInt);
  protected
    function GetAsByteArray: TBytes; override;
    function GetAsFloat: Double; override;
    function GetAsInteger: Longint; override;
    function GetAsLargeint: Largeint;
    function GetAsString: string; override;
    function GetDataSize: Integer; override;
    function GetDefaultWidth: Integer; override;
    procedure GetText(var Text: string; DisplayText: Boolean); override;
    function GetValue(var Value: LargeInt): Boolean;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsFloat(Value: Double); override;
    procedure SetAsInteger(Value: Longint); override;
    procedure SetAsLargeint(Value: Largeint);
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetFieldType(Value: TFieldType); override;
    property AsLargeInt: LargeInt read GetAsLargeint write SetAsLargeint;
    property Value: Largeint read GetAsLargeint write SetAsLargeint;
  published
    property MaxValue: LargeInt read FMaxValue write SetMaxValue default 0;
    property MinValue: LargeInt read FMinValue write SetMinValue default 0;
  end;

  TADONETFloatField = class(TADONETNumericField)
  private
    FCurrency: Boolean;
    FCheckRange: Boolean;
    FPrecision: Integer;
    FMinValue: Decimal;
    FMaxValue: Decimal;
    procedure SetCurrency(Value: Boolean);
    procedure SetMaxValue(Value: Decimal);
    procedure SetMinValue(Value: Decimal);
    procedure SetPrecision(Value: Integer);
    procedure UpdateCheckRange;
  protected
    function GetAsBCD: TBcd; override;
    function GetAsByteArray: TBytes; override;
    function GetAsCurrency: Currency; override;
    function GetAsDecimal: Decimal;
    function GetAsFloat: Double; override;
    function GetAsInteger: Longint; override;
    function GetAsString: string; override;
    function GetDataSize: Integer; override;
    function GetDefaultWidth: Integer; override;
    procedure GetText(var Text: string; DisplayText: Boolean); override;
    procedure SetAsBCD(const Value: TBcd); override;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsCurrency(Value: Currency); override;
    procedure SetAsDecimal(Value: Decimal);
    procedure SetAsFloat(Value: Double); override;
    procedure SetAsInteger(Value: Longint); override;
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetFieldType(Value: TFieldType); override;
    function IsValidChar(InputChar: Char): Boolean; override;
    property Value: Decimal read GetAsDecimal write SetAsDecimal;
  published
    property currency: Boolean read FCurrency write SetCurrency default False;
    property MaxValue: Decimal read FMaxValue write SetMaxValue;
    property MinValue: Decimal read FMinValue write SetMinValue;
    property Precision: Integer read FPrecision write SetPrecision default 15;
  end;

  TADONETDateTimeField = class(TADONETField)
  private
    FDisplayFormat: string;
    function GetValue(var Value: System.DateTime): Boolean;
    procedure SetDisplayFormat(const Value: string);
  protected
    function GetAsByteArray: TBytes; override;
    function GetAsDateTime: TDateTime; override;
    function GetAsFloat: Double; override;
    function GetAsSqlTimeStamp: TSQLTimeStamp; override;
    function GetAsString: string; override;
    function GetAsVariant: Variant; override;
    function GetDataSize: Integer; override;
    function GetDefaultWidth: Integer; override;
    procedure GetText(var Text: string; DisplayText: Boolean); override;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsDateTime(Value: TDateTime); override;
    procedure SetAsFloat(Value: Double); override;
    procedure SetAsSQLTimeStamp(const Value: TSQLTimeStamp); override;
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetFieldType(Value: TFieldType); override;
    property Value: TDateTime read GetAsDateTime write SetAsDateTime;
  published
    property DisplayFormat: string read FDisplayFormat write SetDisplayFormat;
    property EditMask;
  end;

  TADONETBinaryField = class(TADONETField)
  protected
    function GetAsByteArray: TBytes; override;
    function GetAsString: string; override;
    procedure GetText(var Text: string; DisplayText: Boolean); override;
    function GetAsVariant: Variant; override;
    function GetDataSize: Integer; override;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsString(const Value: string); override;
    procedure SetText(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetFieldType(Value: TFieldType); override;
  end;

  TADONETGuidField = class(TADONETField)
  protected
    function GetAsByteArray: TBytes; override;
    function GetAsGuid: TGuid;
    function GetAsString: string; override;
    function GetDataSize: Integer; override;
    procedure SetAsByteArray(const Value: TBytes); override;
    procedure SetAsGuid(const Value: TGuid);
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
    property Value: TGuid read GetAsGuid write SetAsGuid;
    property AsGuid: TGuid read GetAsGuid write SetAsGuid;
  end;

  TADONETObjectField = class(TADONETField)
  protected
    function GetAsBCD: TBcd; override;
    function GetAsBoolean: Boolean; override;
    function GetAsCurrency: Currency; override;
    function GetAsDateTime: TDateTime; override;
    function GetAsFloat: Double; override;
    function GetAsInteger: Longint; override;
    function GetAsSqlTimeStamp: TSQLTimeStamp; override;
    function GetAsString: string; override;
    procedure SetAsBCD(const Value: TBcd); override;
    procedure SetAsBoolean(Value: Boolean); override;
    procedure SetAsCurrency(Value: Currency); override;
    procedure SetAsDateTime(Value: TDateTime); override;
    procedure SetAsFloat(Value: Double); override;
    procedure SetAsInteger(Value: Longint); override;
    procedure SetAsSQLTimeStamp(const Value: TSQLTimeStamp); override;
    procedure SetAsString(const Value: string); override;
    procedure SetVarValue(const Value: Variant); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

  { TCustomADONETConnector }

  TCalcValue = packed record
    Field: TField;
    Value: Variant;
  end;

  TDataRowInfo = packed record
    InUse: Boolean;
    BookmarkFlag: TBookmarkFlag;
    RecordNumber: Integer; // index of record in current view
    DataRow: System.Data.DataRow; // data row object in DataTable
    FieldBuffer: System.Data.DataRow; // buffer of unposted values
    CalcBuffer: array of TCalcValue; // buffer of calculated values
  end;

  TBeforeUpdateAction = (buaAbort, buaRevert, buaAccept, buaApply);
  TADONETUpdateErrorEvent = procedure(DataSet: TDataSet; Row: DataRow;
    UpdateKind: TUpdateKind; var UpdateAction: TBeforeUpdateAction;
    var ClearError: Boolean) of object;

  ObjArray = array of TObject;

  TCustomADONETConnector = class(TDataSet)
  private
    FDataTable: System.Data.DataTable;
    FRecordCache: array of TDataRowInfo;
    FCursorPos: Integer;
    FFilterBuffer: TRecordBuffer;
    FErrorBuffer: TRecordBuffer;
    FDetailFilter: string;
    FIndexFieldNames: string;
    FLogChanges: Boolean;
    FSort: string;
    FIndexFields: TObjectList;
    FDesignerData: string;
    FMasterDataLink: TMasterDataLink;
    FParentDataSet: TCustomADONETConnector;
    FStoreDefs: Boolean;
    FBeforeApplyUpdates: TDataSetNotifyEvent;
    FAfterApplyUpdates: TDataSetNotifyEvent;
    FBeforeUpdateError: TADONETUpdateErrorEvent;
    procedure AddFieldToList(SB: StringBuilder; Field: string);
    function BufferToIndex(Buf: TRecordBuffer): Integer;
    function FieldListForColumns(Cols: array of DataColumn): string;
    function FieldNameForColumnName(Name: string; FldNo: Integer): string;
    function GenerateIndexForSort(SortString: string): TIndexDef;
    function GenerateIndexForUniqueConstraint: TIndexDef;
    function GenerateSortForFields(Fields: TList): string;
    function GetDetailFilterStr: string;
    function GetFilterStr(Field: TField; Value: Variant; Partial: Boolean = False): string;
    function GetIndexField(Index: Integer): TField;
    function GetIndexFieldCount: Integer;
    function GetIndexFieldNames: string;
    function GetKeyValues(Row: DataRow): ObjArray;
    function GetMasterFields: string;
    function IndexOfRow(Row: DataRow; StartAt: Integer): Integer;
    function IndexOfViewRow(Row: DataRow; StartAt: Integer): Integer;
    function IndexToBuffer(I: Integer): TRecordBuffer;
    procedure ReadDesignerData(Reader: TReader);
    procedure SetIndexField(Index: Integer; const Value: TField);
    procedure SetIndexFieldNames(const Value: string);
    procedure SetLogChanges(const Value: Boolean);
    procedure SetMasterFields(const Value: string);
    procedure SetRecordBufferToRow(Index: Integer; Row: DataRow);
    procedure ViewChanged(Sender: TObject; Args: ListChangedEventArgs);
    procedure WriteDesignerData(Writer: TWriter);
  protected
    { IProviderSupport }
    function PSGetDefaultOrder: TIndexDef; override;
    function PSGetKeyFields: string; override;
    function PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean; override;
  protected
    function AllocRecordBuffer: TRecordBuffer; override;
    procedure CheckFieldCompatibility(Field: TField; FieldDef: TFieldDef); override;
    procedure ClearCalcFields(Buffer: TRecordBuffer); override;
    procedure DefChanged(Sender: TObject); override;
    procedure DefineProperties(Filer: TFiler); override;
    procedure DoOnNewRecord; override;
    function FindRecord(Restart, GoForward: Boolean): Boolean; override;
    procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
    function GetActiveRecBuf(var RecBuf: TRecordBuffer): Boolean;
    procedure GetBookmarkData(Buffer: TRecordBuffer; var Bookmark: TBookmark); override;
    function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
    function GetCanModify: Boolean; override;
    function GetDataSource: TDataSource; override;
    function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
    function GetRecNo: Integer; override;
    function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
    function GetRecordCount: Integer; override;
    function GetStateFieldValue(State: TDataSetState; Field: TField): Variant; override;
    procedure InternalAddRecord(Buffer: TRecordBuffer; Append: Boolean); override;
    procedure InternalCancel; override;
    procedure InternalClose; override;
    procedure InternalDelete; override;
    procedure InternalEdit; override;
    procedure InternalFirst; override;
    function InternalGetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
    procedure InternalGotoBookmark(const Bookmark: TBookmark); override;
    procedure InternalInitFieldDefs; override;
    procedure InternalInitRecord(Buffer: TRecordBuffer); override;
    procedure InternalLast; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalRefresh; override;
    procedure InternalSetToRecord(Buffer: TRecordBuffer); override;
    function IsCursorOpen: Boolean; override;
    function LocateRecord(const KeyFields: string; const KeyValues: Variant;
      Options: TLocateOptions; var FoundRow: DataRow): Boolean;
    procedure MasterChanged(Sender: TObject); virtual;
    procedure OpenCursor(InfoQuery: Boolean); override;
    procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
    procedure SetBookmarkData(Buffer: TRecordBuffer; const Bookmark: TBookmark); override;
    procedure SetDataSource(const Value: TDataSource); virtual;
    function SetDetailFilter(ApplyOnChangeOnly: Boolean): Boolean;
    procedure SetFieldData(Field: TField; Buffer: TValueBuffer); override; deprecated;
    procedure SetFieldData(Field: TField; Buffer: TValueBuffer; NativeFormat: Boolean); override; deprecated;
    procedure SetFieldData(Field: TField; Data: TObject); overload;
    procedure SetFiltered(Value: Boolean); override;
    procedure SetFilterOptions(Value: TFilterOptions); override;
    procedure SetFilterText(const Value: string); override;
    procedure SetRecNo(Value: Integer); override;
    property MasterDataLink: TMasterDataLink read FMasterDataLink;
    {! BeforeApplyUpdates gives you an opportunity to start a transaction
     or validate the datatable and set errors on rows before applying updates }
    property BeforeApplyUpdates: TDataSetNotifyEvent read FBeforeApplyUpdates write FBeforeApplyUpdates;
    {! If you started a transaction in BeforeApplyUpdates, here is where you
     can commit it. }
    property AfterApplyUpdates: TDataSetNotifyEvent read FAfterApplyUpdates write FAfterApplyUpdates;
    {! BeforeUpdateError occurs for each row in the datatable that is marked
    as having an error. It occurs after BeforeApplyUpdates but before applying the
    updates. To catch errors that occur when actually trying to apply updates to
    the server, use the RowUpdated event of the DbDataAdapter descendant that you
    use to apply the updates. }
    property BeforeUpdateError: TADONETUpdateErrorEvent read FBeforeUpdateError write FBeforeUpdateError;
    property FieldDefs stored FStoreDefs;
    property IndexFieldNames: string read GetIndexFieldNames write SetIndexFieldNames;
    property MasterFields: string read GetMasterFields write SetMasterFields;
    property StoreDefs: Boolean read FStoreDefs write FStoreDefs default False;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure FreeBookmark(var Bookmark: TBookmark); override;
    function GetBookmark: TBookmark; override;
    function BookmarkValid(const Bookmark: TBookmark): Boolean; override;
    procedure ApplyUpdates(Adapter: DbDataAdapter);
    procedure CancelUpdates;
    procedure Clone(Source: TCustomADONETConnector; IncludeData: Boolean = False);
    function CompareBookmarks(const Bookmark1, Bookmark2: TBookmark): Integer; override;
    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
    procedure GetDetailLinkFields(MasterFields, DetailFields: TObjectList); override;
    function GetFieldData(Field: TField; Buffer: TValueBuffer): Boolean; override; deprecated;
    function GetFieldData(Field: TField; Buffer: TValueBuffer; NativeFormat: Boolean): Boolean; override; deprecated;
    function GetFieldData(Field: TField; var Data: TObject): Boolean; overload;
    function IsSequenced: Boolean; override;
    {!Note, must load entire dataset, not just the data table}
    procedure LoadFromFile(const FileName: string;
      ReadMode: System.Data.XMLReadMode = System.Data.XMLReadMode.Auto);
    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;
    procedure MergeChangeLog;
    {!Note: SaveToFile saves entire System.Data.DataSet, not just this table}
    procedure SaveToFile(const FileName: string;
      WriteMode: System.Data.XMLWriteMode = System.Data.XMLWriteMode.WriteSchema);
    function UpdateStatus: TUpdateStatus; override;
    function get_ChangeCount: Integer;
    procedure set_DataTable(const Value: System.Data.DataTable);
    function get_Sort: string;
    procedure set_Sort(Value: string);
    property Sort: string read get_Sort write set_Sort;
    property ChangeCount: Integer read get_ChangeCount;
    property DesignerData: string read FDesignerData write FDesignerData;
    property IndexFieldCount: Integer read GetIndexFieldCount;
    property IndexFields[Index: Integer]: TField read GetIndexField write SetIndexField;
    property LogChanges: Boolean read FLogChanges write SetLogChanges default True;
    property DataTable: System.Data.DataTable read FDataTable write set_DataTable;
  end;

  TADONETConnector = class(TCustomADONETConnector)
  published
    property Active default False;
    property AutoCalcFields;
    property Filter;
    property Filtered;
    property FieldDefs;
    property IndexFieldNames;
    property MasterFields;
    property MasterSource: TDataSource read GetDataSource write SetDataSource;
    property BeforeOpen;
    property AfterOpen;
    property BeforeClose;
    property AfterClose;
    property BeforeInsert;
    property AfterInsert;
    property BeforeEdit;
    property AfterEdit;
    property BeforePost;
    property AfterPost;
    property BeforeCancel;
    property AfterCancel;
    property BeforeDelete;
    property AfterDelete;
    property BeforeScroll;
    property AfterScroll;
    property BeforeRefresh;
    property AfterRefresh;
    property OnCalcFields;
    property OnDeleteError;
    property OnEditError;
    property OnFilterRecord;
    property OnNewRecord;
    property OnPostError;
    property BeforeApplyUpdates;
    property AfterApplyUpdates;
    property BeforeUpdateError;
  end;

resourcestring
  SDataTable = 'DataTable';
  SNeedDataSetToSave = 'ADO.Net dataset can only be saved with a System.Data.DataSet';
  SNeedDataSetToLoad = 'ADO.Net dataset can only be loaded with a System.Data.DataSet';
  SInvalidSourceDataSet = 'Invalid source dataset';
  SNoBlobStreams = 'Blob field types are not supported on ADO.NET datasets, use ftVariant';
  SInvalidRecordBuffer = 'Invalid record buffer';
  SRowNotInTable = 'The row can not be found in the table.';
  SCantUseCalcField = 'Invalid operation for calculated field value.';
  SUsupportedFieldType = 'Unsupported field type (%s) in field %s';
  SNoFilterOptions = 'FilterOptions are not supported';
  SNoDataTable = 'No DataTable defined for the ADO.NET dataset';
  SNoRefresh = 'Refresh not supported, use the Fill method of a Db adapter on the DataTable.';
  SNoNesting = 'Nested detail sets not supported';
  SCannotCreateDataSet = 'No fields defined.  Cannot create dataset';

implementation

uses
  DBConsts, MaskUtils, WinUtils, StrUtils, 
  System.Runtime.InteropServices, System.Data.SqlTypes;

type
  CharArray = array of Char;

{ Utility Functions }

function SystemTypeToFieldType(const SystemType: System.Type): TFieldType;
begin
  case System.Type.GetTypeCode(SystemType) of
    System.TypeCode.Boolean: Result := ftBoolean;
    System.TypeCode.Char: Result := ftSmallInt;
    System.TypeCode.DateTime: Result := ftDateTime;
    System.TypeCode.Decimal: Result := ftBCD;
    System.TypeCode.Double,
    System.TypeCode.Single: Result := ftFloat;
    System.TypeCode.Int16,
    System.TypeCode.Byte,
    System.TypeCode.SByte: Result := ftSmallInt;
    System.TypeCode.UInt16: Result := ftWord;
    System.TypeCode.Int32: Result := ftInteger;
    System.TypeCode.UInt32: Result := ftBCD;
    System.TypeCode.Int64,
    System.TypeCode.UInt64: Result := ftLargeint;
    System.TypeCode.String: Result := ftWideString;
    System.TypeCode.Object: Result := ftVariant;
  else
    Result := ftUnknown;
  end;

  if Result = ftVariant then
  begin
    if SystemType.Equals(TypeOf(TBytes)) or SystemType.Equals(TypeOf(SqlBinary)) then
      Result := ftBytes
    else if SystemType.Equals(TypeOf(TGuid)) or SystemType.Equals(TypeOf(SqlGuid)) then
      Result := ftGuid
    else if SystemType.Equals(TypeOf(SqlBoolean)) then
      Result := ftBoolean
    else if SystemType.Equals(TypeOf(SqlDateTime)) then
      Result := ftDateTime
    else if SystemType.Equals(TypeOf(SqlDecimal)) then
      Result := ftBcd
    else if SystemType.Equals(TypeOf(SqlDouble)) or
      SystemType.Equals(TypeOf(SqlSingle)) then
      Result := ftFloat
    else if SystemType.Equals(TypeOf(SqlInt16)) or SystemType.Equals(TypeOf(SqlByte)) then
      Result := ftSmallInt
    else if SystemType.Equals(TypeOf(SqlInt32)) then
      Result := ftInteger
    else if SystemType.Equals(TypeOf(SqlInt64)) then
      Result := ftLargeint
    else if SystemType.Equals(TypeOf(SqlMoney)) then
      Result := ftCurrency
    else if SystemType.Equals(TypeOf(SqlString)) then
      Result := ftWideString
    else if SystemType.Equals(TypeOf(TSQLTimeStamp)) then
      Result := ftTimeStamp
    else if SystemType.Equals(TypeOf(TBcd)) then
      Result := ftFMTBcd
    else if SystemType.Equals(TypeOf(AnsiString)) then
      Result := ftString;
  end;
end;

function FieldTypeToSystemType(const FieldType: TFieldType): System.Type;
begin
  case FieldType of
    ftUnknown: Result := TypeOf(System.DBNull);
    ftString, ftWideString: Result := typeof(string);
    ftSmallint: Result := typeof(SmallInt);
    ftInteger, ftAutoInc: Result := typeof(Integer);
    ftWord: Result := typeof(Word);
    ftBoolean: Result := typeof(Boolean);
    ftFloat: Result := typeof(Double);
    ftCurrency, ftBCD: Result := typeof(System.Decimal);
    ftDate: Result := typeof(System.DateTime);
    ftTime: Result := Typeof(System.DateTime);
    ftDateTime: Result := TypeOf(System.DateTime);
    ftBytes: Result := TypeOf(TBytes);
    ftVarBytes: Result := TypeOf(TBytes);
    ftMemo: Result := TypeOf(string);
    ftBlob, ftVariant, ftGraphic..ftTypedBinary: Result := TypeOf(TObject);
    ftFixedChar: Result := TypeOf(CharArray);
    ftLargeint: Result := TypeOf(Int64);
    ftGuid: Result := TypeOf(TGuid);
    ftTimeStamp: Result := typeof(System.DateTime);
    ftFMTBCD: Result := typeof(System.Decimal);
  else
    Result := TypeOf(TObject);
  end;
end;

function ExtractFieldName(const Fields: string; var Pos: Integer): string;
var
  I: Integer;
begin
  I := Pos;
  while (I <= Length(Fields)) and (Fields[I] <> ';') do Inc(I);
  Result := Copy(Fields, Pos, I - Pos);
  if (I <= Length(Fields)) and (Fields[I] = ';') then Inc(I);
  Pos := I;
end;

const
  InvalidCharArray: array[0..22] of Char = (#9, #10, #13, '~', '(', ')', '#',
    '\', '/', '=', '>', '<', '+', '-', '*', '%', '&', '|', '^', '''', '"', '[',
     ']');

function ValidFieldNameForExpression(const FieldName: string): string;
var
  SB: StringBuilder;
  BracketPos, BracketCount: Integer;
begin
  if FindDelimiter(InvalidCharArray, FieldName) > 1 then
  begin
    BracketPos := Pos(']', FieldName) - 1;
    if BracketPos >= 0 then
    begin
      BracketCount := 0;
      SB := StringBuilder.Create(FieldName);
      while BracketPos >= 0 do
      begin
        SB.Insert(BracketPos + BracketCount, '\');
        Inc(BracketCount);
        BracketPos := PosEx(']', FieldName, BracketPos + 2) - 1;
      end;
      SB.Insert(0, '[');
      SB.Append(']');
      Result := SB.TOString;
    end else
      Result := Format('[%s]', [FieldName]);
  end else
    Result := FieldName;
end;

procedure ApplicationHandleException(Sender: TObject);
begin
  if Assigned(Classes.ApplicationHandleException) then
    Classes.ApplicationHandleException(Sender);
end;

  { TADONETField }

procedure TADONETField.ValidateValue(Value: TObject);
var
  OldValValue: TObject;
begin
  if Assigned(OnValidate) then
  begin
    OldValValue := FValidateValue;
    { Use the already assigned FValidateValue if set }
    if FValidateValue = nil then
      FValidateValue := Value;
    FValidating := True;
    try
      OnValidate(Self);
    finally
      FValidating := False;
      FValidateValue := OldValValue;
    end;
  end;
end;

class procedure TADONETField.CheckTypeSize(Value: Integer);
begin
  {By default, we ignore the data size so this lets everything through}
end;

function TADONETField.GetIsNull: Boolean;
var
  O: TObject;
begin
  Result := not GetData(O);
end;

function TADONETField.GetData(var Value: TObject): Boolean;
begin
  if DataSet = nil then DatabaseErrorFmt(SDataSetMissing, [DisplayName]);
  if FValidating then
  begin
    Value := FValidateValue;
    Result := (Value <> nil) and not (Value is System.DBNull);
  end else
    Result := TCustomADONETConnector(DataSet).GetFieldData(Self, Value);
end;

function TADONETField.GetAsVariant: Variant;
var
  O: TObject;
begin
  if GetData(O) then
    Result := Variant(O)
  else
    Result := Null;
end;

procedure TADONETField.SetData(Value: TObject);
begin
  if DataSet = nil then DatabaseErrorFmt(SDataSetMissing, [DisplayName]);
  FValidateValue := Value;
  try
    TCustomADONETConnector(DataSet).SetFieldData(Self, Value);
  finally
    FValidateValue := nil;
  end;
end;

procedure TADONETField.Clear;
begin
  SetData(nil);
end;

  { TADONETBooleanField }

constructor TADONETBooleanField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftBoolean);
  LoadTextValues;
end;

function TADONETBooleanField.GetDataSize: Integer;
begin
  Result := SizeOf(WordBool);
end;

function TADONETBooleanField.GetAsBoolean: Boolean;
var
  O: TObject;
begin
  if GetData(O) then
    Result := System.Convert.ToBoolean(O)
  else
    Result := False;
end;

function TADONETBooleanField.GetAsByteArray: TBytes;
begin
  Result := BitConverter.GetBytes(GetAsBoolean);
end;

function TADONETBooleanField.GetAsString: string;
var
  O: TObject;
  V: Variant;
begin
  if GetData(O) then
  begin
    if TypeOf(O).Equals(TypeOf(Boolean)) then
      Result := FTextValues[Boolean(O)]
    else
    begin
      V := Variant(O);
      Result := FTextValues[Boolean(V)];
    end
  end else Result := '';
end;

function TADONETBooleanField.GetDefaultWidth: Integer;
begin
  if Length(FTextValues[False]) > Length(FTextValues[True]) then
    Result := Length(FTextValues[False]) else
    Result := Length(FTextValues[True]);
end;

procedure TADONETBooleanField.LoadTextValues;
begin
  FTextValues[False] := STextFalse;
  FTextValues[True] := STextTrue;
end;

procedure TADONETBooleanField.SetAsBoolean(Value: Boolean);
begin
  SetData(TObject(Value));
end;

procedure TADONETBooleanField.SetAsByteArray(const Value: TBytes);
var
  I: Integer;
begin
  i := 0; // remove compiler warning about uninitialized var
  case Length(Value) of
    1: I := Value[0];
    2: I := BitConverter.ToInt16(Value, 0);
    4: I := BitConverter.ToInt32(Value, 0);
  else
    DatabaseErrorFmt(SInvalidVarByteArray, [DisplayName]);
  end;
  SetData(TObject(I <> 0));
end;

procedure TADONETBooleanField.SetAsString(const Value: string);
var
  L: Integer;
begin
  L := Length(Value);
  if L = 0 then
  begin
    if Length(FTextValues[False]) = 0 then SetAsBoolean(False) else
      if Length(FTextValues[True]) = 0 then SetAsBoolean(True) else
        Clear;
  end else
  begin
    if SameText(Value, Copy(FTextValues[False], 1, L)) then
      SetAsBoolean(False)
    else
      if SameText(Value, Copy(FTextValues[True], 1, L)) then
        SetAsBoolean(True)
      else
        DatabaseErrorFmt(SInvalidBoolValue, [Value, DisplayName]);
  end;
end;

procedure TADONETBooleanField.SetDisplayValues(const Value: string);
var
  P: Integer;
begin
  if FDisplayValues <> Value then
  begin
    FDisplayValues := Value;
    if Value = '' then LoadTextValues else
    begin
      P := Pos(';', Value);
      if P = 0 then P := 256;
      FTextValues[False] := Copy(Value, P + 1, 255);
      FTextValues[True] := Copy(Value, 1, P - 1);
    end;
    PropertyChanged(True);
  end;
end;

procedure TADONETBooleanField.SetVarValue(const Value: Variant);
begin
  SetAsBoolean(Value);
end;

  { TADONETStringField }

constructor TADONETStringField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftWideString);
  if Size = 0 then Size := 20;
end;

procedure TADONETStringField.SetFieldType(Value: TFieldType);
begin
  if Value in [ftString, ftWideString, ftMemo, ftFixedChar] then
    SetDataType(Value);
end;

function TADONETStringField.GetDataSize: Integer;
begin
  if Size > 0 then
    Result := Size + 1
  else
    Result := 256;
  if DataType = ftWideString then
    Result := Result * 2;
end;

class procedure TADONETStringField.CheckTypeSize(Value: Integer);
begin
  if Value < 0 then
    DatabaseError(SInvalidFieldSize);
end;

function TADONETStringField.GetAsBCD: TBcd;
begin
  Result := TBcd.Parse(GetAsString);
end;

function TADONETStringField.GetAsBoolean: Boolean;
var
  S: string;
begin
  S := GetAsString;
  Result := (Length(S) > 0) and (S[1] in ['T', 't', 'Y', 'y']);
end;

function TADONETStringField.GetAsByteArray: TBytes;
begin
  if DataType = ftWideString then
    Result := WideBytesOf(GetAsString)
  else
    Result := BytesOf(GetAsString);
end;

function TADONETStringField.GetAsDateTime: TDateTime;
begin
  Result := StrToDateTime(GetAsString);
end;

function TADONETStringField.GetAsFloat: Double;
begin
  Result := StrToFloat(GetAsString);
end;

function TADONETStringField.GetAsInteger: Longint;
begin
  Result := StrToInt(GetAsString);
end;

function TADONETStringField.GetAsSQLTimeStamp: TSQLTimeStamp;
begin
  Result := TSQLTimeStamp.Parse(GetAsString);
end;

function TADONETStringField.GetAsString: string;
begin
  if not GetValue(Result) then Result := '';
end;

function TADONETStringField.GetDefaultWidth: Integer;
begin
  Result := Size;
end;

procedure TADONETStringField.GetText(var Text: string; DisplayText: Boolean);
begin
  if DisplayText and (EditMaskPtr <> '') then
    Text := FormatMaskText(EditMaskPtr, GetAsString) else
    Text := GetAsString;
end;

function TADONETStringField.GetValue(var Value: string): Boolean;
var
  O: TObject;
begin
 Result := GetData(O);
 if Result then
   Value := VarToStr(Variant(O));
end;

procedure TADONETStringField.SetAsBCD(const Value: TBcd);
begin
  SetAsString(Value.ToString);
end;

procedure TADONETStringField.SetAsBoolean(Value: Boolean);
const
  Values: array[Boolean] of string[1] = ('F', 'T');
begin
  SetAsString(Values[Value]);
end;

procedure TADONETStringField.SetAsByteArray(const Value: TBytes);
begin
  if DataType = ftWideString then
    SetAsString(WideStringOf(Value))
  else
    SetAsString(StringOf(Value));
end;

procedure TADONETStringField.SetAsDateTime(Value: TDateTime);
begin
  SetAsString(DateTimeToStr(Value));
end;

procedure TADONETStringField.SetAsFloat(Value: Double);
begin
  SetAsString(FloatToStr(Value));
end;

procedure TADONETStringField.SetAsInteger(Value: Longint);
begin
  SetAsString(IntToStr(Value));
end;

procedure TADONETStringField.SetAsSQLTimeStamp(const Value: TSQLTimeStamp);
var
  SSqlTs: String;
begin
  SSQLTs := Value.ToString(''); 
  SetAsString(SSqlTs);
end;

procedure TADONETStringField.SetAsString(const Value: string);
var
  S: string;
begin
  if Length(Value) <= Size then
    SetData(TObject(Value))
  else
  begin
    S := Copy(Value, 1, Size);
    SetData(TObject(S));
  end;
end;

procedure TADONETStringField.SetVarValue(const Value: Variant);
begin
  SetAsString(Value);
end;

{ TADONETNumericField }

constructor TADONETNumericField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Alignment := taRightJustify;
end;

procedure TADONETNumericField.RangeError(Value, Min, Max: Extended);
begin
  DatabaseErrorFmt(SFieldRangeError, [Value, DisplayName, Min, Max]);
end;

procedure TADONETNumericField.SetDisplayFormat(const Value: string);
begin
  if FDisplayFormat <> Value then
  begin
    FDisplayFormat := Value;
    PropertyChanged(False);
  end;
end;

procedure TADONETNumericField.SetEditFormat(const Value: string);
begin
  if FEditFormat <> Value then
  begin
    FEditFormat := Value;
    PropertyChanged(False);
  end;
end;

{ TADONETIntegerField }

constructor TADONETIntegerField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftInteger);
  FMinRange := Low(Longint);
  FMaxRange := High(Longint);
  ValidChars := ['+', '-', '0'..'9'];
end;

procedure TADONETIntegerField.SetFieldType(Value: TFieldType);
begin
  case Value of
    ftInteger, ftAutoInc:
      begin
        SetDataType(Value);
        FMinRange := Low(Longint);
        FMaxRange := High(Longint);
      end;
    ftLargeint:
      begin
        SetDataType(ftLargeint);
        FMinRange := Low(Int64);
        FMaxRange := High(Int64);
      end;
    ftSmallint:
      begin
        SetDataType(ftSmallint);
        FMinRange := Low(Smallint);
        FMaxRange := High(Smallint);
      end;
    ftWord:
      begin
        SetDataType(ftWord);
        FMinRange := Low(Word);
        FMaxRange := High(Word);
      end;
  end;
end;

function TADONETIntegerField.GetDataSize: Integer;
begin
  case DataType of
    ftSmallint, ftWord: Result := Sizeof(Smallint);
    ftAutoInc, ftInteger: Result := SizeOf(Integer);
    ftLargeInt: Result := SizeOf(Int64);
    else Result := Size;
  end;
end;

procedure TADONETIntegerField.CheckRange(Value, Min, Max: Largeint);
begin
  if (Value < Min) or (Value > Max) then RangeError(Value, Min, Max);
end;

function TADONETIntegerField.GetAsByteArray: TBytes;
begin
  case DataType of
    ftLargeint: Result := BitConverter.GetBytes(GetAsLargeInt);
    ftSmallint: Result := BitConverter.GetBytes(SmallInt(GetAsInteger));
    ftWord: Result := BitConverter.GetBytes(Word(GetAsInteger));
  else
    Result := BitConverter.GetBytes(GetAsInteger);
  end;
end;

function TADONETIntegerField.GetAsFloat: Double;
begin
  Result := GetAsLargeInt;
end;

function TADONETIntegerField.GetAsInteger: Longint;
var
  L: LargeInt;
begin
  if GetValue(L) then Result := Longint(L) else Result := 0;
end;

function TADONETIntegerField.GetAsLargeint: Largeint;
begin
  if not GetValue(Result) then Result := 0;
end;

function TADONETIntegerField.GetAsString: string;
var
  L: Largeint;
begin
  if GetValue(L) then Result := IntToStr(L) else Result := '';
end;

function TADONETIntegerField.GetDefaultWidth: Integer;
begin
  if DataType = ftLargeInt then
    Result := 15
  else
    Result := inherited GetDefaultWidth;
end;

procedure TADONETIntegerField.GetText(var Text: string; DisplayText: Boolean);
var
  L: Largeint;
  FmtStr: string;
begin
  if GetValue(L) then
  begin
    if DisplayText or (FEditFormat = '') then
      FmtStr := FDisplayFormat else
      FmtStr := FEditFormat;
    if FmtStr = '' then
      Text := IntToStr(L)
    else Text := FormatFloat(FmtStr, L);
  end else
    Text := '';
end;

function TADONETIntegerField.GetValue(var Value: Largeint): Boolean;
var
  Temp: TObject;
  V: Variant;
begin
  Result := GetData(Temp);
  if Result then
  begin
    V := Variant(Temp); // use a variant to handle the conversion
    Value := V;
  end;
end;

procedure TADONETIntegerField.SetAsByteArray(const Value: TBytes);
var
  I: Largeint;
begin
  I := 0; // remove compiler warning about uninitialized var
  case Length(Value) of
    1: I := Value[0];
    2: I := BitConverter.ToInt16(Value, 0);
    4: I := BitConverter.ToInt32(Value, 0);
    8: I := BitConverter.ToInt64(Value, 0);
  else
    DatabaseErrorFmt(SInvalidVarByteArray, [DisplayName]);
  end;
  SetData(TObject(I));
end;

procedure TADONETIntegerField.SetAsFloat(Value: Double);
begin
  SetAsLargeint(Round(Value));
end;

procedure TADONETIntegerField.SetAsInteger(Value: Longint);
begin
  SetAsLargeInt(Value);
end;

procedure TADONETIntegerField.SetAsLargeint(Value: Largeint);
begin
  if (FMinValue <> 0) or (FMaxValue <> 0) then
    CheckRange(Value, FMinValue, FMaxValue);
  SetData(TObject(Value));
end;

procedure TADONETIntegerField.SetAsString(const Value: string);
begin
  if Value = '' then Clear else
  begin
    try
      SetAsLargeint(System.Convert.ToInt32(Value))
    except
      DatabaseErrorFmt(SInvalidIntegerValue, [Value, DisplayName]);
    end;
  end;
end;

procedure TADONETIntegerField.SetMaxValue(Value: Largeint);
begin
  CheckRange(Value, FMinRange, FMaxRange);
  FMaxValue := Value;
end;

procedure TADONETIntegerField.SetMinValue(Value: Largeint);
begin
  CheckRange(Value, FMinRange, FMaxRange);
  FMinValue := Value;
end;

procedure TADONETIntegerField.SetVarValue(const Value: Variant);
begin
  SetAsLargeint(Value);
end;

  { TADONETFloatField }

constructor TADONETFloatField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftFloat);
  Size := 4;
  ValidChars := ['+', '-', '0'..'9'];
end;

procedure TADONETFloatField.SetFieldType(Value: TFieldType);
begin
  if Value in [ftFloat, ftBcd, ftFMTBCD, ftCurrency] then
    SetDataType(Value);
end;

function TADONETFloatField.GetDataSize: Integer;
begin
  if DataType in [ftFMTBCD, ftBCD] then
  begin
    if Precision > 0 then
      Result := 2 + Precision div 2
    else
      Result := SizeOfTBCD
  end
  else
    Result := SizeOf(Double);
end;

function TADONETFloatField.IsValidChar(InputChar: Char): Boolean;
begin
  Result := inherited IsValidChar(InputChar);
  if not Result then
    Result := Pos(InputChar, DecimalSeparator) > 0;
end;

function TADONETFloatField.GetAsBCD: TBcd;
var
  O: TObject;
begin
  if not GetData(O) then
    Result := TBCd.Empty
  else if O is TBcd then
    Result := TBcd(O)
  else if O is System.Double then
    Result := Double(O)
  else if O is Borland.Delphi.System.Currency then
    Result := Borland.Delphi.System.Currency(O)
  else
    Result := Variant(O);
end;

function TADONETFloatField.GetAsByteArray: TBytes;
begin
  if (DataType = ftFloat) or (DataType = ftCurrency) then
    Result := BitConverter.GetBytes(GetAsFloat)
  else
    Result := TBcd.ToBytes(GetAsBCD);
end;

function TADONETFloatField.GetAsCurrency: Currency;
var
  O: TObject;
begin
  if not GetData(O) then
    Result := 0
  else if O is Borland.Delphi.System.Currency then
    Result := Borland.Delphi.System.Currency(O)
  else if O is TBcd then
    Result := TBcd(O)
  else if O is System.Double then
    Result := Double(O)
  else
    Result := GetAsDecimal;
end;

function TADONETFloatField.GetAsDecimal: Decimal;
var
  O: TObject;
begin
  if not GetData(O) then
    Result := System.Decimal.Zero
  else if O is Decimal then
    Result := Decimal(O)
  else if O is Borland.Delphi.System.Currency then
    Result := Borland.Delphi.System.Currency(O)
  else if O is TBCD then
    Result := System.Decimal.Parse(TBcd(O).ToString)
  else
    Result := System.Convert.ToDecimal(O);
end;

function TADONETFloatField.GetAsFloat: Double;
var
  O: TObject;
begin
  if not GetData(O) then
    Result := 0
  else if O is System.Double then
    Result := Double(O)
  else if O is Borland.Delphi.System.Currency then
    Result := Double(Borland.Delphi.System.Currency(O))
  else if O is TBcd then
    Result := TBcd(O)
  else
    Result := System.Convert.ToDouble(O);
end;

function TADONETFloatField.GetAsInteger: Longint;
var
  O: TObject;
begin
  if not GetData(O) then
    Result := 0
  else if O is Borland.Delphi.System.Currency then
    Result := Longint(Round(Borland.Delphi.System.Currency(O)))
  else if O is TBcd then
    Result := Longint(Round(AsCurrency))
  else
    Result := System.Convert.ToInt32(O);
end;

function TADONETFloatField.GetAsString: string;
var
  O: TObject;
begin
  if not GetData(O) then
    Result := ''
  else if O is TBcd then
    Result := TBcd(O).ToString
  else
    Result := VarToStr(Variant(O))
end;

function TADONETFloatField.GetDefaultWidth: Integer;
begin
  if FPrecision > 0 then
    Result := FPrecision + 1
  else
    Result := inherited GetDefaultWidth;
end;

procedure TADONETFloatField.GetText(var Text: string; DisplayText: Boolean);
var
  Format: TFloatFormat;
  Digits: Integer;
  FmtStr: string;
  O: TObject;
begin
  if GetData(O) then
  begin
    if DisplayText or (EditFormat = '') then
      FmtStr := DisplayFormat else
      FmtStr := EditFormat;
    try
      if FmtStr = '' then
      begin
        if FCurrency then
        begin
          if DisplayText then Format := ffCurrency else Format := ffFixed;
          Digits := CurrencyDecimals;
        end
        else begin
          Format := ffGeneral;
          Digits := 0;
        end;
        Text := CurrToStrF(GetAsCurrency, Format, Digits);
      end else
        Text := FormatCurr(FmtStr, GetAsCurrency);
    except
      Text := O.ToString;
    end;
  end else
    Text := '';
end;

procedure TADONETFloatField.SetAsBCD(const Value: TBcd);
var
  D: Decimal;
begin
  if (DataType in [ftFMTBCD, ftBCD]) and
       (((Precision > 0) and (Value.SignificantDigits > Precision)) or
        (Value.Scale > Size)) then
    DatabaseError(sBcdOverflow, self);
  if FCheckRange then
  begin
    D := System.Convert.ToDecimal(Value.ToString);
    if (D < FMinValue) or (D > FMaxValue) then
      DatabaseErrorFmt(SFieldRangeError, [D, DisplayName, FMinValue, FMaxValue]);
    SetData(D);
  end else
    SetData(TObject(Value));
end;

procedure TADONETFloatField.SetAsByteArray(const Value: TBytes);
begin
  case Length(Value) of
    SizeOfTBCD: SetAsBcd(TBcd.FromBytes(Value));
    8: SetAsFloat(BitConverter.ToDouble(Value, 0));
  else
    DatabaseErrorFmt(SInvalidVarByteArray, [DisplayName]);
  end;
end;

procedure TADONETFloatField.SetAsCurrency(Value: Currency);
begin
  SetAsDecimal(Value);
end;

procedure TADONETFloatField.SetAsDecimal(Value: Decimal);
begin
  if FCheckRange and ((Value < FMinValue) or (Value > FMaxValue)) then
    DatabaseErrorFmt(SFieldRangeError, [Value, DisplayName, FMinValue, FMaxValue]);
  if DataType in [ftFMTBCD, ftBCD] then
    SetData(System.Decimal.Round(Value, Size)) // enforce size
  else
    SetData(Value);
end;

procedure TADONETFloatField.SetAsFloat(Value: Double);
begin
  SetAsDecimal(Decimal.Create(Value))
end;

procedure TADONETFloatField.SetAsInteger(Value: Longint);
begin
  SetAsDecimal(Decimal.Create(Value));
end;

procedure TADONETFloatField.SetAsString(const Value: string);
begin
  if Value = '' then
    Clear
  else
    SetAsDecimal(System.Decimal.Parse(Value));
end;

procedure TADONETFloatField.SetCurrency(Value: Boolean);
begin
  if FCurrency <> Value then
  begin
    FCurrency := Value;
    PropertyChanged(False);
  end;
end;

procedure TADONETFloatField.SetMaxValue(Value: Decimal);
begin
  FMaxValue := Value;
  UpdateCheckRange;
end;

procedure TADONETFloatField.SetMinValue(Value: Decimal);
begin
  FMinValue := Value;
  UpdateCheckRange;
end;

procedure TADONETFloatField.SetPrecision(Value: Integer);
begin
  if Value < 1 then Value := 1;
  if Value > MaxFMTBcdFractionSize then
    Value := MaxFMTBcdFractionSize;
  if FPrecision <> Value then
  begin
    FPrecision := Value;
    PropertyChanged(False);
  end;
end;

procedure TADONETFloatField.SetVarValue(const Value: Variant);
begin
// SetAsDecimal(Value);
  SetAsString(Value);
end;

procedure TADONETFloatField.UpdateCheckRange;
begin
  FCheckRange := (FMinValue <> System.Decimal.Zero) or (FMaxValue <> System.Decimal.Zero);
end;

{ TADONETDateTimeField }

constructor TADONETDateTimeField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftDateTime);
end;

procedure TADONETDateTimeField.SetFieldType(Value: TFieldType);
begin
  if Value in [ftDateTime, ftDate, ftTime, ftTimeStamp] then
    SetDataType(Value);
end;

function TADONETDateTimeField.GetDataSize: Integer;
begin
  if DataType = ftTimeStamp then
    Result := SizeOf(TSQLTimeStamp)
  else
    Result := SizeOf(Double);
end;

function TADONETDateTimeField.GetAsByteArray: TBytes;
begin
  if DataType = ftTimeStamp then
    Result := TSqlTimeStamp.ToBytes(GetAsSQLTimeStamp)
  else
    Result := BitConverter.GetBytes(GetAsFloat);
end;

function TADONETDateTimeField.GetAsDateTime: TDateTime;
var
  DT: System.DateTime;
begin
  if GetValue(DT) then
    Result := DT
  else
    Result := 0;
end;

function TADONETDateTimeField.GetAsFloat: Double;
begin
  Result := GetAsDateTime;
end;

function TADONETDateTimeField.GetAsSQLTimeStamp: TSQLTimeStamp;
var
  DT: System.DateTime;
begin
  if GetValue(DT) then
    Result := DT
  else
    Result := TSQLTimeStamp.Empty;
end;

function TADONETDateTimeField.GetAsString: string;
begin
  GetText(Result, False);
end;

function TADONETDateTimeField.GetAsVariant: Variant;
 var
  D: System.DateTime;
begin
  if GetValue(D) then
    Result := D
  else
    Result := Null;
end;

function TADONETDateTimeField.GetDefaultWidth: Integer;
begin
  if DataType in [ftDateTime, ftTimeStamp] then
    Result := 20
  else
    Result := 10;
end;

procedure TADONETDateTimeField.GetText(var Text: string; DisplayText: Boolean);
var
  F: string;
  DT: System.DateTime;
begin
  if GetValue(DT) then
  begin
    if DisplayText and (FDisplayFormat <> '') then
      F := FDisplayFormat
    else
      case DataType of
        ftDate: F := ShortDateFormat;
        ftTime: F := LongTimeFormat;
      end;
    if F = '' then
      Text := DT.ToString
    else
      Text := DT.ToString(ConvertDelphiDateTimeFormat(F));
  end else
    Text := '';
end;

function TADONETDateTimeField.GetValue(var Value: System.DateTime): Boolean;
var
  O: TObject;
begin
  Result := GetData(O);
  if Result then
  begin
    if O is TDateTime then
      Value := System.DateTime(TDateTime(O))
    else
      if O is TSQLTimeStamp then
        Value := System.DateTime(TSQLTimeStamp(O))
      else
        Value := System.Convert.ToDateTime(O);
  end;
end;

procedure TADONETDateTimeField.SetAsByteArray(const Value: TBytes);
begin
  if (DataType = ftTimeStamp) and (Length(Value) = SizeOf(TSQLTimeStamp)) then
    SetAsSQLTimeStamp(TSQLTimeStamp.FromBytes(Value))
  else if (DataType <> ftTimeStamp) and (Length(Value) = SizeOf(Double)) then
    SetAsFloat(BitConverter.ToDouble(Value, 0))
  else
    DatabaseErrorFmt(SInvalidVarByteArray, [DisplayName]);
end;

procedure TADONETDateTimeField.SetAsDateTime(Value: TDateTime);
begin
  SetAsFloat(Value);
end;

procedure TADONETDateTimeField.SetAsFloat(Value: Double);
begin
  SetData(System.DateTime.FromOADate(Value));
end;

procedure TADONETDateTimeField.SetAsSQLTimeStamp(const Value: TSQLTimeStamp);
begin
  with Value do
    SetData(System.DateTime(Value));
end;

procedure TADONETDateTimeField.SetAsString(const Value: string);
begin
  if Value = '' then Clear else
  begin
    case DataType of
      ftDate: SetAsDateTime(StrToDate(Value));
      ftTime: SetAsDateTime(StrToTime(Value));
    else
      SetData(TObject(Value));  // let the datatable handle the conversion
    end;
  end;
end;

procedure TADONETDateTimeField.SetDisplayFormat(const Value: string);
begin
  if FDisplayFormat <> Value then
  begin
    FDisplayFormat := Value;
    PropertyChanged(False);
  end;
end;

procedure TADONETDateTimeField.SetVarValue(const Value: Variant);
begin
  if Value is TDateTime then
    SetAsDateTime(Value)
  else
    SetAsString(Value);
end;

  { TADONETBinaryField }

constructor TADONETBinaryField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftBytes);
end;

procedure TADONETBinaryField.SetFieldType(Value: TFieldType);
begin
  if Value in [ftBytes, ftVarBytes] then
    SetDataType(Value);
end;

function TADONETBinaryField.GetDataSize: Integer;
begin
  if Size > 0 then
    Result := Size
  else
    Result := 256;
  if DataType = ftVarBytes then
    Result := Result + SizeOf(Word);
end;

function TADONETBinaryField.GetAsByteArray: TBytes;
var
  O: TObject;
begin
  if not GetData(O) then
    SetLength(Result, 0)
  else if TypeOf(O).Equals(TypeOf(TBytes)) then
    Result := TBytes(O)
  else
    Result := StructureToBytes(O);
end;

function TADONETBinaryField.GetAsString: string;
var
  Data: TBytes;
begin
  Data := GetAsByteArray;
  if Length(Data) = 0 then
    Result := ''
  else
    Result := StringOf(Data);
end;

function TADONETBinaryField.GetAsVariant: Variant;
begin
  Result := Variant(GetAsByteArray);
end;

procedure TADONETBinaryField.GetText(var Text: string; DisplayText: Boolean);
begin
  Text := inherited GetAsString;
end;

procedure TADONETBinaryField.SetAsByteArray(const Value: TBytes);
begin
  SetData(TObject(Value));
end;

procedure TADONETBinaryField.SetAsString(const Value: string);
var
  Data: TBytes;
begin
  if Value = '' then Clear else
  begin
    Data := BytesOf(Value);
    SetAsByteArray(Data);
  end;
end;

procedure TADONETBinaryField.SetText(const Value: string);
begin
  raise AccessError('Text');    // do not localize
end;

procedure TADONETBinaryField.SetVarValue(const Value: Variant);
begin
  if TypeOf(Value).Equals(TypeOf(TBytes)) then
    SetAsByteArray(TBytes(TObject(Value)))
  else
    DatabaseErrorFmt(SInvalidVarByteArray, [DisplayName]);
end;

  { TADONETGuidField }

constructor TADONETGuidField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftGuid);
end;

function TADONETGuidField.GetDataSize: Integer;
begin
  Result := 39;
end;

function TADONETGuidField.GetAsByteArray: TBytes;
begin
  Result := GetAsGuid.ToByteArray;
end;

function TADONETGuidField.GetAsGuid: TGuid;
var
  O: TObject;
begin
  if GetData(O) then
  begin
    if O is TGuid then
      Result := TGuid(O)
    else if TypeOf(O).Equals(TypeOf(TBytes)) then
      Result := System.Guid.Create(TBytes(O))
    else
      Result := StringToGuid(O.ToString);
  end else
    Result := System.Guid.Empty;
end;

function TADONETGuidField.GetAsString: string;
var
  G: TGuid;
begin
  G := GetAsGuid;
  if G = System.Guid.Empty then
    Result := ''
  else
    Result := GuidToString(G);
end;

procedure TADONETGuidField.SetAsByteArray(const Value: TBytes);
begin
  SetData(System.Guid.Create(Value));
end;

procedure TADONETGuidField.SetAsGuid(const Value: TGuid);
begin
  SetData(TObject(Value));
end;

procedure TADONETGuidField.SetAsString(const Value: string);
begin
  SetAsGuid(StringToGuid(Value));
end;

procedure TADONETGuidField.SetVarValue(const Value: Variant);
begin
  if TypeOf(Value).Equals(TypeOf(TBytes)) then
    SetAsByteArray(TBytes(TObject(Value)))
  else
    SetData(TObject(Value));
end;

  { TADONETObjectField }

constructor TADONETObjectField.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetDataType(ftVariant);
end;

function TADONETObjectField.GetAsBCD: TBcd;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsBoolean: Boolean;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsCurrency: Currency;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsDateTime: TDateTime;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsFloat: Double;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsInteger: Longint;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsSQLTimeStamp: TSQLTimeStamp;
begin
  Result := GetAsVariant;
end;

function TADONETObjectField.GetAsString: string;
begin
  Result := VarToStr(GetAsVariant);
end;

procedure TADONETObjectField.SetAsBCD(const Value: TBcd);
begin
  SetAsString(Value.ToString);
end;

procedure TADONETObjectField.SetAsBoolean(Value: Boolean);
begin
  SetData(TObject(Value));
end;

procedure TADONETObjectField.SetAsCurrency(Value: Currency);
begin
  SetData(TObject(Decimal(Value)));
end;

procedure TADONETObjectField.SetAsDateTime(Value: TDateTime);
begin
  SetData(System.DateTime.FromOADate(Value));
end;

procedure TADONETObjectField.SetAsFloat(Value: Double);
begin
  SetData(TObject(Value));
end;

procedure TADONETObjectField.SetAsInteger(Value: Longint);
begin
  SetData(TObject(Value));
end;

procedure TADONETObjectField.SetAsSQLTimeStamp(const Value: TSQLTimeStamp);
var
  SSqlTs: String;
begin
  SSQLTs := Value.ToString('');
  SetAsString(SSqlTs);
end;

procedure TADONETObjectField.SetAsString(const Value: string);
begin
  SetData(TObject(Value))
end;

procedure TADONETObjectField.SetVarValue(const Value: Variant);
begin
  SetData(TObject(Value));
end;

  { TCustomADONETConnector }

constructor TCustomADONETConnector.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FIndexFields := TObjectList.Create;
  BookmarkSize := SizeOf(Integer);
  FCursorPos := -1;
  FLogChanges := True;
  FMasterDataLink := TMasterDataLink.Create(Self);
  MasterDataLink.OnMasterChange := MasterChanged;
end;

destructor TCustomADONETConnector.Destroy;
begin
  Destroying;
  Close;
  FreeAndNil(FIndexFields);
  FreeAndNil(FMasterDataLink);
  inherited Destroy;
end;

procedure GenerateColumns(ADataTable: System.Data.DataTable; FieldData: TFields);
var
  I: Integer;
  Col: System.Data.DataColumn;
begin
  for I := 0 to FieldData.Count - 1 do
  begin
    with FieldData[I] do
    begin
      Col := System.Data.DataColumn.Create(FieldName, FieldTypeToSystemType(DataType));
      ADataTable.Columns.Add(Col);
      Col.ReadOnly := ReadOnly;
      Col.AllowDBNull := not Required;
      Col.Caption := DisplayLabel;
      Col.ColumnName := FieldName;
      Col.MaxLength := Size;
      if DataType = ftAutoInc then
      begin
        Col.AutoIncrement := True;
        Col.AutoIncrementSeed := 1;
        Col.AutoIncrementStep := 1;
      end else
        Col.AutoIncrement := False;
      if Assigned(DefaultExpression) then
        Col.DefaultValue := TObject(DefaultExpression);
    end;
  end;
end;


procedure TCustomADONETConnector.OpenCursor(InfoQuery: Boolean);

  procedure InitializeDataTable;
  var
    DS: System.Data.DataSet;
  begin
    if Fields.Count = 0 then
      CreateFields;
    if Fields.Count = 0 then
      DatabaseError(SCannotCreateDataSet);
    FDataTable := System.Data.DataTable.Create(Format('%s_%s', [Name, SDataTable]));
    DS := System.Data.DataSet.Create;
    DS.Tables.Add(FDataTable);
    GenerateColumns(FDataTable, Fields);
  end;

begin
  if not Assigned(DataTable) then
    if DataSetField <> nil then
      DatabaseError(SNoNesting, Self)
    else
      InitializeDataTable;
  inherited OpenCursor(InfoQuery);
end;

procedure TCustomADONETConnector.InternalOpen;
begin
  FieldDefs.Updated := False;
  FieldDefs.Update;
  if DefaultFields then CreateFields;
  BindFields(True);
  FCursorPos := -1;
  DataTable.DefaultView.BeginInit;
  DataTable.DefaultView.add_ListChanged(ViewChanged);
  if MasterDataLink.Active then
    SetDetailFilter(False)
  else if Filtered then
    DataTable.DefaultView.RowFilter := Filter
  else
    DataTable.DefaultView.RowFilter := '';
  DataTable.DefaultView.Sort := Sort;
  DataTable.DefaultView.EndInit;
end;

procedure TCustomADONETConnector.InternalClose;
begin
  BindFields(False);
  if DefaultFields then DestroyFields;
  FIndexFields.Clear;
  SetLength(FRecordCache, 0);
  if Assigned(DataTable) then
    DataTable.DefaultView.remove_ListChanged(ViewChanged);
  FDetailFilter := '';
end;

procedure TCustomADONETConnector.InternalRefresh;
begin
  DatabaseError(SNoRefresh, Self);
end;

procedure TCustomADONETConnector.CheckFieldCompatibility(Field: TField;
  FieldDef: TFieldDef);
var
  Compatible: Boolean;
begin
  case Field.DataType of
    ftFloat, ftCurrency, ftBCD, ftFMTBCD: { Numeric and Doubles are interchangeable }
      Compatible := FieldDef.DataType in [ftFloat, ftCurrency, ftBCD, ftFMTBCD];
    ftString, ftWideString, ftMemo, ftFixedChar: { As are string types }
      Compatible := FieldDef.DataType in [ftString, ftWideString];
    ftTimeStamp, ftDate, ftTime, ftDateTime:
      Compatible := FieldDef.DataType in [ftDate, ftTime, ftDateTime, ftTimeStamp];
    ftBytes, ftVarBytes:
      Compatible := FieldDef.DataType in [ftBytes, ftVarBytes];
    ftSmallint, ftWord, ftInteger, ftLargeint, ftAutoInc:
      Compatible := FieldDef.DataType in [ftSmallint, ftWord, ftInteger, ftLargeint, ftAutoInc];
  else
    Compatible := False;
  end;
  if not Compatible then inherited;
end;

function TCustomADONETConnector.IsCursorOpen: Boolean;
begin
  Result := Assigned(DataTable);
end;

procedure TCustomADONETConnector.DefChanged(Sender: TObject);
begin
  FStoreDefs := True;
end;

function TCustomADONETConnector.GetFieldClass(FieldType: TFieldType): TFieldClass;
begin
  case FieldType of
    ftBoolean: Result := TADONETBooleanField;
    ftDateTime, ftTimeStamp, ftDate, ftTime: Result := TADONETDateTimeField;
    ftFixedChar, ftString, ftWideString, ftMemo: Result := TADONETStringField;
    ftFloat, ftBCD, ftCurrency, ftFMTBCD: Result := TADONETFloatField;
    ftWord, ftSmallint, ftInteger, ftLargeInt, ftAutoInc: Result := TADONETIntegerField;
    ftBytes, ftVarBytes: Result := TADONETBinaryField;
    ftGuid: Result := TADONETGuidField;
  else
    Result := TADONETObjectField;
  end;
end;

procedure TCustomADONETConnector.InternalInitFieldDefs;
  procedure AddFieldDef(Field: System.Data.DataColumn; FieldDefs: TFieldDefs);
  var
    FieldType: TFieldType;
    FieldDef: TFieldDef;
    I: Integer;
    FName: string;
    FSize: Integer;
    FPrecision: Integer;
  begin
    FieldType := SystemTypeToFieldType(Field.DataType);
    if FieldType <> ftUnknown then
    begin
      FSize := 0;
      FPrecision := 0;
      FieldDef := FieldDefs.AddFieldDef;
      with FieldDef do
      begin
        FieldNo := Field.Ordinal + 1;  // FieldNo is 1-offset
        I := 0;
        FName := Field.ColumnName;
        while (FName = '') or (FieldDefs.IndexOf(FName) >= 0) do
        begin
          Inc(I);
          if Field.ColumnName = '' then
            FName := Format('COLUMN%d', [I]) else { Do not localize }
            FName := Format('%s_%d', [Field.ColumnName, I]);
        end;
        Name := FName;
        case FieldType of
          ftString, ftWideString:
            if Field.MaxLength > 0 then
              FSize := Field.MaxLength
            else
              FSize := 255;
          ftBytes, ftVarBytes:
            FSize := 255; // What do we do here?
          ftFixedChar:
            FSize := 40; 
          ftBCD, ftFMTBCD:
            if System.Type.GetTypeCode(Field.DataType) = System.TypeCode.UInt64 then
              FPrecision := 19
            else
            begin
              FPrecision := 32;
              FSize := 4;
            end;
          ftInteger:
            if Field.AutoIncrement then
              FieldType := ftAutoInc;
          ftGuid:
            FSize := 38;
        end;
        if Field.ReadOnly then
          Attributes := Attributes + [faReadOnly];
        if not Field.AllowDBNull then
        begin
          Required := True;
          Attributes := Attributes + [faRequired];
        end;
        DataType := FieldType;
        Size := FSize;
        Precision := FPrecision;
        if (DataType = ftDataSet) and (Fields.Count = 0) then
          ObjectView := True;
      end;
    end;
  end;

var
  I: Integer;
begin
  if not Assigned(DataTable) then
    DatabaseError(SNoDataTable);
  FieldDefs.Clear;
  for I := 0 to DataTable.Columns.Count - 1 do
    AddFieldDef(DataTable.Columns[I], FieldDefs);
end;

procedure TCustomADONETConnector.LoadFromFile(const FileName: string;
  ReadMode: System.Data.XMLReadMode = System.Data.XMLReadMode.Auto);
var
  Name: String;
  DS: System.Data.DataSet;
begin
  if (DataTable = nil) or (DataTable.DataSet = nil) then
    DatabaseError(SNeedDataSetToLoad, self);
  DS := DataTable.DataSet;
  Name := DataTable.TableName;
  Close;
  DS.ReadXML(FileName, ReadMode);
  DataTable := DS.Tables[Name];
  Open;
end;

procedure TCustomADONETConnector.SaveToFile(const FileName: string;
  WriteMode: System.Data.XMLWriteMode = System.Data.XMLWriteMode.WriteSchema);
begin
  CheckBrowseMode;
  if (DataTable = nil) or (DataTable.DataSet = nil) then
    DatabaseError(SNeedDataSetToSave, self);
  DataTable.DataSet.WriteXML(FileName, WriteMode);
end;

procedure TCustomADONETConnector.Clone(Source: TCustomADONETConnector; IncludeData: Boolean = False);
begin
  if Source.DataTable = nil then
    DatabaseError(SInvalidSourceDataSet);
  Close;
  if IncludeData then
    DataTable := Source.DataTable.Copy
  else
    DataTable := Source.DataTable.Clone;
  try
    Open;
  except
    DataTable := nil;
    raise;
  end;
end;

procedure TCustomADONETConnector.ReadDesignerData(Reader: TReader);
begin
  FDesignerData := Reader.ReadString;
end;

procedure TCustomADONETConnector.WriteDesignerData(Writer: TWriter);
begin
  Writer.WriteString(FDesignerData);
end;

procedure TCustomADONETConnector.DefineProperties(Filer: TFiler);

  function DesignerDataStored: Boolean;
  begin
    if Filer.Ancestor <> nil then
      Result := TCustomADONETConnector(Filer.Ancestor).DesignerData <> DesignerData else
      Result := DesignerData <> '';
  end;

begin
  inherited;
  Filer.DefineProperty('DesignerData', ReadDesignerData, WriteDesignerData,
    DesignerDataStored);
end;

{ Master / Detail }

procedure TCustomADONETConnector.MasterChanged(Sender: TObject);
begin
  if not Active then Exit;
    CheckBrowseMode;
    if SetDetailFilter(True) then First;
end;

function TCustomADONETConnector.GetDetailFilterStr: string;
var
  I: Integer;
  LinkField: TField;
  FilterStr: StringBuilder;
begin
  if (not MasterDataLink.Active) or (MasterDataLink.Fields.Count = 0) then
  begin
    if Filtered then
      Result := Filter
    else
      Result := '';
    Exit;
  end;
  FilterStr := StringBuilder.Create;
  if Filtered and (Length(Filter) > 0) then
  begin
    FilterStr.AppendFormat('({0})', TObject(Filter));
    FilterStr.Append(' AND ');
  end;
  for I := 0 to MasterDataLink.Fields.Count - 1 do
  begin
    if IndexFieldCount > I then
      LinkField := TField(IndexFields[I]) else
      LinkField := TField(MasterDataLink.Fields[I]);
    FilterStr.Append(GetFilterStr(LinkField, TField(MasterDataLink.Fields[I]).Value));
    if I <> MasterDataLink.Fields.Count - 1 then
      FilterStr.Append(' AND ');
  end;
  Result := FilterStr.ToString;
end;

function TCustomADONETConnector.SetDetailFilter(ApplyOnChangeOnly: Boolean): Boolean;
var
  FilterStr: string;
begin
  FilterStr := GetDetailFilterStr;
  if ApplyOnChangeOnly then
      Result := FDetailFilter <> FilterStr
  else Result := True;
  if Result then
  begin
    FDetailFilter := FilterStr;
    try
      DataTable.DefaultView.RowFilter := FDetailFilter;
    except
      CursorPosChanged;
      raise;
    end;
  end;
end;

procedure TCustomADONETConnector.DoOnNewRecord;
var
  I: Integer;
  LinkField: TField;
begin
  if MasterDataLink.Active and (MasterDataLink.Fields.Count > 0) then
    for I := 0 to MasterDataLink.Fields.Count - 1 do
    begin
      if IndexFieldCount > I then
        LinkField := IndexFields[I] else
        LinkField := FindField(TField(MasterDataLink.Fields[I]).FieldName);
      if LinkField <> nil then
        LinkField.Assign(TField(MasterDataLink.Fields[I]));
    end;
  inherited DoOnNewRecord;
end;

function TCustomADONETConnector.GetMasterFields: string;
begin
  Result := MasterDataLink.FieldNames;
end;

procedure TCustomADONETConnector.SetMasterFields(const Value: string);
begin
  MasterDataLink.FieldNames := Value;
end;

procedure TCustomADONETConnector.GetDetailLinkFields(MasterFields,
  DetailFields: TObjectList);
var
  P, I: Integer;
  Field: TField;
begin
  MasterFields.Clear;
  DetailFields.Clear;
  if (DataSource <> nil) and (DataSource.DataSet <> nil) and
    (Self.MasterFields <> '') then
  begin
    DataSource.DataSet.GetFieldList(MasterFields, Self.MasterFields);
    if IndexFieldNames = '' then
      GetFieldList(DetailFields, Self.MasterFields)
    else begin
      P := 1;
      I := 0;
      while (P <= Length(IndexFieldNames)) and (I < MasterFields.Count) do
      begin
        Field := FieldByName(ExtractFieldName(IndexFieldNames, P));
        DetailFields.Add(Field);
        Inc(I);
      end;
    end;
  end;
end;

{ Bookmarks }

procedure TCustomADONETConnector.InternalGotoBookmark(const Bookmark: TBookmark);
begin
  FCursorPos := Integer(Bookmark);
end;

procedure TCustomADONETConnector.InternalSetToRecord(Buffer: TRecordBuffer);
var
  I: Integer;
begin
  I := BufferToIndex(Buffer);
  if FRecordCache[I].BookmarkFlag in [bfCurrent, bfInserted] then
    FCursorPos := FRecordCache[I].RecordNumber;
end;

procedure TCustomADONETConnector.FreeBookmark(var Bookmark: TBookmark);
begin
end;

function TCustomADONETConnector.GetBookmark: TBookmark;
begin
  if BookmarkAvailable then
    GetBookmarkData(ActiveBuffer, Result)
  else
    Result := TBookmark(-1);
end;

function TCustomADONETConnector.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
begin
  Result := FRecordCache[BufferToIndex(Buffer)].BookmarkFlag;
end;

procedure TCustomADONETConnector.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
begin
  FRecordCache[BufferToIndex(Buffer)].BookmarkFlag := Value;
end;

procedure TCustomADONETConnector.GetBookmarkData(Buffer: TRecordBuffer; var Bookmark: TBookmark);
begin
  Marshal.WriteIntPtr(BookMark, IntPtr(FRecordCache[BufferToIndex(Buffer)].RecordNumber));
end;

procedure TCustomADONETConnector.SetBookmarkData(Buffer: TRecordBuffer; const Bookmark: TBookmark);
begin
  FRecordCache[BufferToIndex(Buffer)].RecordNumber := Marshal.ReadInt32(BookMark);
end;

function TCustomADONETConnector.BookmarkValid(const Bookmark: TBookmark): Boolean;
var
  RecNo, NRecs: Integer;
begin
  Result := False;
  RecNo := Integer(Bookmark);
  if RecNo >= 0 then
  begin
    CheckActive;
    NRecs := DataTable.DefaultView.Count;
    if (NRecs > 0) and (RecNo < NRecs) then
    begin
      SetRecNo(Integer(Bookmark));
      CursorPosChanged;
      Result := True;
    end;
  end;
end;

function TCustomADONETConnector.CompareBookmarks(const Bookmark1,
  Bookmark2: TBookmark): Integer;
var
  B1, B2: Integer;
begin
  B1 := Integer(Bookmark1);
  B2 := Integer(Bookmark2);
  Result := B1 - B2;
end;

{ Record Functions }

function TCustomADONETConnector.BufferToIndex(Buf: TRecordBuffer): Integer;
begin
  Result := Integer(Buf) - 1; // Buf is off by one so that nil (0) represents an invalid buffer
end;

function TCustomADONETConnector.IndexToBuffer(I: Integer): TRecordBuffer;
begin
  Result := TRecordBuffer(I + 1);
end;

function TCustomADONETConnector.AllocRecordBuffer: TRecordBuffer;
  function InitializeBuffer(I: Integer): TRecordBuffer;
  begin
    FRecordCache[I].InUse := True;
    FRecordCache[I].RecordNumber := -2;
    Result := IndexToBuffer(I);
  end;
var
  I, Len: Integer;
begin
  Len := Length(FRecordCache);
  for I := 0 to Len - 1 do
    if not FRecordCache[I].InUse then
    begin
      Result := InitializeBuffer(I);
      Exit;
    end;
  SetLength(FRecordCache, Len + 1);
  Result := InitializeBuffer(Len);
end;

procedure TCustomADONETConnector.FreeRecordBuffer(var Buffer: TRecordBuffer);
var
  I: Integer;
begin
  I := BufferToIndex(Buffer);
  if I = Length(FRecordCache) - 1 then
    SetLength(FRecordCache, I)
  else
  begin
    FRecordCache[I].InUse := False;
    FRecordCache[I].DataRow := nil;
    FRecordCache[I].FieldBuffer := nil;
    SetLength(FRecordCache[I].CalcBuffer, 0);
  end;
end;

function TCustomADONETConnector.GenerateSortForFields(Fields: TList): string;
var
  I: Integer;
  SB: StringBuilder;
  Field: TField;
begin
  if Fields.Count = 0 then
  begin
    Result := '';
    Exit;
  end;
  SB := StringBuilder.Create;
  for I := 0 to Fields.Count - 1 do
  begin
    Field := TField(Fields[I]);
    if not (Field.FieldKind in [fkData, fkInternalCalc, fkAggregate]) then
      DatabaseError(SCantUseCalcField, Field);
    if Assigned(DataTable) then
      SB.Append(ValidFieldNameForExpression(DataTable.Columns[Field.FieldNo - 1].ColumnName))
    else
      SB.Append(ValidFieldNameForExpression(Field.FieldName));
    SB.Append(', ');
  end;
  SB.Length := SB.Length - 2;
  SB.Append(' ASC');
  Result := SB.ToString;
end;

procedure TCustomADONETConnector.SetRecordBufferToRow(Index: Integer; Row: DataRow);
begin
  FRecordCache[Index].DataRow := Row;
  GetCalcFields(IndexToBuffer(Index));
end;

function TCustomADONETConnector.InternalGetRecord(Buffer: TRecordBuffer;
  GetMode: TGetMode; DoCheck: Boolean): TGetResult;
var
  I, RecNo, NRecs: Integer;
begin
  if (Assigned(FParentDataSet) and FParentDataSet.Active and
     (FParentDataSet.IsEmpty or (FParentDataset.State = dsInsert))) or
     (MasterDataLink.Active and (DataSource.DataSet.IsEmpty or
                                (DataSource.DataSet.State = dsInsert))) then
  begin
    Result := grEOF;
    Exit;
  end;
  try
    Result := grOK;
    I := BufferToIndex(Buffer);
    RecNo := FCursorPos;
    NRecs := DataTable.DefaultView.Count;
    case GetMode of
      gmNext:
        begin
          Inc(RecNo);
          if RecNo < NRecs then
            FRecordCache[I].RecordNumber := RecNo
          else
          begin
            Result := grEOF;
            FRecordCache[I].RecordNumber := NRecs;
            FRecordCache[I].BookmarkFlag := bfEOF;
          end;
        end;
      gmPrior:
        begin
          Dec(RecNo);
          if (RecNo >= 0) and (NRecs <> 0) then
            FRecordCache[I].RecordNumber := RecNo
          else
          begin
            Result := grBOF;
            FRecordCache[I].RecordNumber := -1;
            FRecordCache[I].BookmarkFlag := bfBOF;
          end;
        end;
      gmCurrent:
          if (NRecs = 0) or (RecNo < 0) then
          begin
            Result := grBOF;
            FRecordCache[I].BookmarkFlag := bfBOF;
            FRecordCache[I].RecordNumber := -1;
          end else if RecNo >= NRecs then
          begin
            Result := grEOF;
            FRecordCache[I].BookmarkFlag := bfEOF;
            FRecordCache[I].RecordNumber := NRecs;
          end
          else
            FRecordCache[I].RecordNumber := RecNo; 
    end;
    if Result = grOK then
    begin
      FRecordCache[I].BookmarkFlag := bfCurrent;
      SetRecordBufferToRow(I, DataTable.DefaultView[RecNo].Row);
      FCursorPos := RecNo;
    end else
    begin
      FRecordCache[I].DataRow := nil;
      FRecordCache[I].FieldBuffer := nil;
      if (NRecs <> 0) and (Result = grEOF) then
        FCursorPos := NRecs
      else
        FCursorPos := -1
    end;
  except
    if DoCheck then raise;
    Result := grError;
  end;
end;

function TCustomADONETConnector.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
var
  Accept: Boolean;
begin
  if Filtered and Assigned(OnFilterRecord) then
  begin
    FFilterBuffer := Buffer;
    try
      Accept := True;
      repeat
        Result := InternalGetRecord(Buffer, GetMode, DoCheck);
        if Result = grOK then
        begin
          OnFilterRecord(Self, Accept);
          if not Accept and (GetMode = gmCurrent) then
            Result := grError;
        end;
      until Accept or (Result <> grOK);
    except
      ApplicationHandleException(Self);
      Result := grError;
    end;
  end else
    Result := InternalGetRecord(Buffer, GetMode, DoCheck)
end;

procedure TCustomADONETConnector.InternalInitRecord(Buffer: TRecordBuffer);
var
  I: Integer;
begin
  I := BufferToIndex(Buffer);
  FRecordCache[I].FieldBuffer := DataTable.NewRow;
  FRecordCache[I].DataRow := nil;
  FRecordCache[I].BookmarkFlag := bfInserted;
  FRecordCache[I].RecordNumber := FCursorPos;
end;

procedure TCustomADONETConnector.ClearCalcFields(Buffer: TRecordBuffer);
var
  I, J, Len: Integer;
begin
  I := BufferToIndex(Buffer);
  if Length(FRecordCache[I].CalcBuffer) = 0 then
    SetLength(FRecordCache[I].CalcBuffer, Fields.Count);
  Len := 0;
  if CalcFieldsSize > 0 then
    for J := 0 to Fields.Count - 1 do
      with Fields[J] do
        if FieldKind in [fkCalculated, fkLookup] then
        begin
          FRecordCache[I].CalcBuffer[Len].Field := Fields[J];
          FRecordCache[I].CalcBuffer[Len].Value := Null;
          Inc(Len);
        end;
  SetLength(FRecordCache[I].CalcBuffer, Len);
end;

function TCustomADONETConnector.GetActiveRecBuf(var RecBuf: TRecordBuffer): Boolean;
begin
  case State of
    dsBlockRead,
    dsBrowse:
      if Assigned(FErrorBuffer) then
        RecBuf := FErrorBuffer
      else if IsEmpty then
        RecBuf := nil
      else
        RecBuf := ActiveBuffer;
    dsEdit, dsInsert, dsOldValue, dsCurValue, dsNewValue: RecBuf := ActiveBuffer;
    dsCalcFields,
    dsInternalCalc: RecBuf := CalcBuffer;
  else
    RecBuf := nil;
  end;
  Result := RecBuf <> nil;
end;

{ Field Data }

function TCustomADONETConnector.GetFieldData(Field: TField; Buffer: TValueBuffer): Boolean;
begin
  Result := GetFieldData(Field, Buffer, True);
end;

function TCustomADONETConnector.GetFieldData(Field: TField; Buffer: TValueBuffer;
  NativeFormat: Boolean): Boolean;
var
  Data: TObject;
  V: Variant;
  B: TBytes;
  Len: Integer;
  TimeStamp: TTimeStamp;
  D: Double;
begin
  Result := GetFieldData(Field, Data);
  if Result and (Data <> nil) then
  begin
    V := Variant(Data);
    case Field.DataType of
      ftWideString:
      begin
        B := WideBytesOf(Data.ToString);
        Len := Length(B);
        if Len > Field.Size * 2 then
        begin
          SetLength(B, Field.Size * 2);
          Len := Field.Size * 2;
        end;
        SetLength(B, Len + 2);
        B[Len - 1] := 0;
        B[Len] := 0; // add null terminator
        Marshal.Copy(B, 0, Buffer, Len + 2);
      end;
      ftString, ftGuid:
      begin
        B := BytesOf(Data.ToString);
        Len := Length(B);
        if Len > Field.Size then
        begin
          SetLength(B, Field.Size);
          Len := Field.Size;
        end;
        SetLength(B, Len + 1);
        B[Len] := 0; // add null terminator
        Marshal.Copy(B, 0, Buffer, Len + 1);
      end;
      ftFixedChar:
      begin
        B := BytesOf(System.String.Create(CharArray(Data)));
        Len := Length(B);
        if Len > Field.Size then
        begin
          SetLength(B, Field.Size);
          Len := Field.Size;
        end;
        SetLength(B, Len + 1);
        B[Len] := 0; // add null terminator
        Marshal.Copy(B, 0, Buffer, Len + 1);
      end;
      ftSmallint, ftWord:
        Marshal.WriteInt16(Buffer, SmallInt(V));
      ftAutoInc, ftInteger:
        Marshal.WriteInt32(Buffer, Integer(V));
      ftLargeInt:
        Marshal.WriteInt64(Buffer, Int64(V));
      ftBoolean:
        if Boolean(V) then
          Marshal.WriteInt16(Buffer, 1)
        else
          Marshal.WriteInt16(Buffer, 0);
      ftFloat, ftCurrency:
        Marshal.WriteInt64(Buffer, BitConverter.DoubleToInt64Bits(V));
      ftBCD:
        if NativeFormat then
          Marshal.Copy(TBcd.ToBytes(V), 0, Buffer, SizeOfTBCD)
        else
          Marshal.WriteInt64(Buffer, System.Decimal.ToOACurrency(System.Decimal(V)));
      ftDate, ftTime, ftDateTime:
        if NativeFormat then
        begin
          TimeStamp := DateTimeToTimeStamp(TDateTime(V));
          case Field.DataType of
            ftDate:
              Marshal.WriteInt32(Buffer, TimeStamp.Date);
           ftTime:
             Marshal.WriteInt32(Buffer, TimeStamp.Time);
           ftDateTime:
             begin
               D := TimeStampToMSecs(TimeStamp);
               Marshal.WriteInt64(Buffer, BitConverter.DoubleToInt64Bits(D));
             end;
          end;
        end
        else
          Marshal.WriteInt64(Buffer, BitConverter.DoubleToInt64Bits(Double(V)));
      ftBytes:
        Marshal.Copy(TBytes(TObject(V)), 0, Buffer, Length(TBytes(TObject(V))));
      ftVarBytes:
        begin
          Len := Length(TBytes(TObject(V)));
          if NativeFormat then
          begin
            Marshal.WriteInt16(Buffer, Len);
            Marshal.Copy(TBytes(TObject(V)), 0, IntPtr(Integer(Buffer) + 2), Len);
          end else
            Marshal.Copy(TBytes(TObject(V)), 0, Buffer, Len);
        end;
      ftTimeStamp:
        Marshal.StructureToPtr(TObject(V), Buffer, False);
      ftFMTBCD:
        Marshal.Copy(TBcd.ToBytes(V), 0, Buffer, SizeOfTBCD);
      else
        DatabaseErrorFmt(SUsupportedFieldType, [FieldTypeNames[Field.DataType],
          Field.DisplayName]);
    end;
  end;
end;

function TCustomADONETConnector.GetFieldData(Field: TField; var Data: TObject): Boolean;
var
  RecBuf: TRecordBuffer;
  Row: DataRow;
  I, J : Integer;
begin
  if Field.FieldNo <= 0 then
    Result := False
  else
    Result := GetActiveRecBuf(RecBuf);
  if not Result then Exit;
  I := BufferToIndex(RecBuf);
  if State in [dsEdit, dsInsert] then
  begin
    Row := FRecordCache[I].FieldBuffer;
    if Row = nil then
      Row := FRecordCache[I].DataRow; // for paints in grids
  end else
    Row := FRecordCache[I].DataRow;
  if Field.FieldKind in [fkData, fkInternalCalc, fkAggregate] then
    Data := Row[Field.FieldNo - 1]
  else {if Field.FieldKind in [fkCalculated, fkLookup] then }
  begin
    for J := 0 to Length(FRecordCache[I].CalcBuffer) - 1 do
      if FRecordCache[I].CalcBuffer[J].Field = Field then
      begin
        Data := TObject(FRecordCache[I].CalcBuffer[J].Value);
        Break;
      end;
  end;
  Result := (Data <> nil) and not VarIsNull(Variant(Data));
end;

function TCustomADONETConnector.GetStateFieldValue(State: TDataSetState;
  Field: TField): Variant;
var
  Row: DataRow;
  Version: System.Data.DataRowVersion;
  Buffer: TRecordBuffer;
begin
  if IsEmpty or not (Self.State in [dsBrowse, dsEdit]) then
    Result := Null
  else if (Field.FieldNo > 0) and (State in [dsOldValue, dsNewValue]) then
  begin
    if not GetActiveRecBuf(Buffer) then
    begin
      Result := Null;
      Exit;
    end else case State of
      dsOldValue:
        begin
          Version := System.Data.DataRowVersion.Original;
          Row := FRecordCache[BufferToIndex(Buffer)].DataRow;
        end;
      dsNewValue:
        if self.State = dsBrowse then
        begin
          Version := System.Data.DataRowVersion.Proposed;
          Row := FRecordCache[BufferToIndex(Buffer)].DataRow;
          if not Row.HasVersion(Version) then
            Version := System.Data.DataRowVersion.Current;
        end else
        begin
          Version := System.Data.DataRowVersion.Current;
          Row := FRecordCache[BufferToIndex(Buffer)].FieldBuffer;
        end;
      else
        begin
          Row := FRecordCache[BufferToIndex(ActiveBuffer)].DataRow;
          Version := System.Data.DataRowVersion.Current;
        end;
    end;
    if Row.HasVersion(Version) then
      Result := Variant(Row.get_Item(Field.FieldNo - 1, Version))
    else
      Result := Null;
  end else
    Result := inherited GetStateFieldValue(State, Field);
end;

function CoerceDataType(Data: TObject; FieldType: TFieldType; ColType: System.Type): TObject;
begin
  {We only need to worry about a few types, the others can be
   coerced by the DataTable}
  if (TypeOf(Data) = ColType) or
    not (FieldType in [ftDateTime, ftDate, ftTime, ftString, ftTimeStamp, ftFMTBcd]) then
    Result := Data
  else
  begin
    case FieldType of
      ftString:
        if ColType = TypeOf(AnsiString) then
          Result := AnsiString(System.String(Data))
        else
          Result := System.String(Data);
      ftTimeStamp:
        if ColType = TypeOf(TSQLTimeStamp) then
          Result := TObject(Variant(Data))
        else if Data is TSQLTimeStamp then
        begin
          Result := TObject(TSQLTimeStamp(Data).ToString(''));
        end else
          Result := VarToStr(Variant(Data));
      ftDate, ftTime, ftDateTime:
        if Data is TDateTime then
          Result := System.DateTime(TDateTime(Data))
        else
          Result := Data;
      ftFMTBcd:
        if ColType = TypeOf(TBcd) then
          Result := TObject(Variant(Data))
        else if Data is TBcd then
        begin
            Result := TBcd(Data).ToString;
        end else
          Result := VarToStr(Variant(Data));
      else
        Result := Data;
    end;
  end;
end;

procedure TCustomADONETConnector.SetFieldData(Field: TField; Data: TObject);
var
  RecBuf: TRecordBuffer;
  I, J: Integer;
begin
  if not (State in [dsEdit, dsInsert, dsSetKey, dsCalcFields, dsNewValue]) then
    DatabaseError(SNotEditing, Self);
  if Field.ReadOnly and (State <> dsSetKey) then
    DatabaseErrorFmt(SFieldReadOnly, [Field.DisplayName]);
  if not GetActiveRecBuf(RecBuf) then
    DatabaseError(SInvalidRecordBuffer);
  if Field is TADONETField then
    TADONETField(Field).ValidateValue(Data);
  if Field.FieldNo > 0 then
  begin
    if Field.FieldKind = fkData then
    begin
      Data := CoerceDataType(Data, Field.DataType, DataTable.Columns[Field.FieldNo - 1].DataType);
      FRecordCache[BufferToIndex(RecBuf)].FieldBuffer[Field.FieldNo - 1] := Data;
    end else
    begin
      I := BufferToIndex(RecBuf);
      for J := 0 to Length(FRecordCache[I].CalcBuffer) - 1 do
        if FRecordCache[I].CalcBuffer[J].Field = Field then
        begin
          FRecordCache[I].CalcBuffer[J].Value := Variant(Data);
          Break;
        end;
    end;
    if not (State in [dsCalcFields, dsNewValue]) then
      DataEvent(deFieldChange, Field);
  end;
end;

procedure TCustomADONETConnector.SetFieldData(Field: TField; Buffer: TValueBuffer);
begin
  SetFieldData(Field, Buffer, True);
end;

procedure TCustomADONETConnector.SetFieldData(Field: TField; Buffer: TValueBuffer; NativeFormat: Boolean);
var
  Data: TObject;
  B: TBytes;
  Len: Smallint;
begin
  Data := nil; // Remove compiler warning about uninitialized var
  if not (State in [dsEdit, dsInsert, dsSetKey, dsCalcFields, dsNewValue]) then
    DatabaseError(SNotEditing, Self);
  if Field.ReadOnly and (State <> dsSetKey) then
    DatabaseErrorFmt(SFieldReadOnly, [Field.DisplayName]);
  Field.Validate(Buffer);
  case Field.DataType of
    ftWideString:
      Data := TObject(Marshal.PtrToStringUni(Buffer));
    ftString, ftGuid, ftFixedChar:
      Data := TObject(Marshal.PtrToStringAnsi(Buffer));
    ftSmallint, ftWord:
      Data := TObject(Marshal.ReadInt16(Buffer));
    ftAutoInc, ftInteger:
      Data := TObject(Marshal.ReadInt32(Buffer));
    ftLargeInt:
      Data := TObject(Marshal.ReadInt64(Buffer));
    ftBoolean:
      if Marshal.ReadInt16(Buffer) <> 0 then
        Data := TObject(True)
      else
        Data := TObject(False);
    ftFloat, ftCurrency:
      Data := TObject(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Buffer)));
    ftBCD:
      if NativeFormat then
      begin
        SetLength(B, SizeOfTBCD);
        Marshal.Copy(Buffer, B, 0, SizeOfTBCD);
        Data := TObject(TBcd.FromBytes(B));
      end
      else
        Data := System.Decimal.FromOACurrency(Marshal.ReadInt64(Buffer));
    ftDate, ftTime, ftDateTime:
      if NativeFormat then
      begin
        case Field.DataType of
          ftDate:
            Data := System.DateTime.Create(0).AddDays(Marshal.ReadInt32(Buffer));
          ftTime:
            Data := System.DateTime.Create(0).AddMilliseconds(
              Marshal.ReadInt32(Buffer));
          ftDateTime:
            Data := System.DateTime.Create(0).AddMilliseconds(
              BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Buffer)));
        end;
      end
      else // data is TDateTime
        Data := System.DateTime.FromOADate(BitConverter.Int64BitsToDouble(
          Marshal.ReadInt64(Buffer)));
    ftBytes:
    begin
      SetLength(B, Field.Size);
      Marshal.Copy(Buffer, B, 0, Field.Size);
      Data := TObject(B);
    end;
    ftTimeStamp:
      Data := Marshal.PtrToStructure(Buffer, TypeOf(TSQLTimeStamp));
    ftFMTBCD:
    begin
      SetLength(B, SizeOfTBCD);
      Marshal.Copy(Buffer, B, 0, SizeOfTBCD);
      Data := TObject(TBcd.FromBytes(B));
    end;
    ftVarBytes:
      if NativeFormat then
      begin
        Len := Marshal.ReadInt16(Buffer);
        SetLength(B, Len);
        Marshal.Copy(IntPtr(Integer(Buffer) + 2), B, 0, Len);
        Data := TObject(B);
      end else
      begin
        {note, we cant support VarBytes if not length prefixed}
        DatabaseErrorFmt(SUsupportedFieldType, [FieldTypeNames[ftVarBytes],
            Field.DisplayName]);
        Data := nil; // never gets called but this makes the compiler happy
      end
    else
    begin
      {note, we cant support blob types in this way}
      DatabaseErrorFmt(SUsupportedFieldType, [FieldTypeNames[Field.DataType],
          Field.DisplayName]);
      Data := nil; // never gets called but this makes the compiler happy
    end;
  end;
  SetFieldData(Field, Data);
end;

function TCustomADONETConnector.CreateBlobStream(Field: TField;
  Mode: TBlobStreamMode): TStream;
begin
  DatabaseError(SNoBlobStreams);
  Result := nil; // never gets called but this makes the compiler happy
end;

{ Record Navigation / Editing }

procedure TCustomADONETConnector.InternalFirst;
begin
  FCursorPos := -1;
end;

procedure TCustomADONETConnector.InternalLast;
var
  NRecs: Integer;
begin
  NRecs := DataTable.DefaultView.Count;
  if NRecs = 0 then
    FCursorPos := -1
  else
    FCursorPos := NRecs;
end;

function TCustomADONETConnector.GetCanModify: Boolean;
begin
  CheckActive;
  Result := DataTable.DefaultView.AllowEdit;
end;

procedure TCustomADONETConnector.ViewChanged(Sender: TObject; Args: ListChangedEventArgs);
var
  I, RecNo: Integer;
begin
  case Args.ListChangedType of
    ListChangedType.ItemAdded:
      begin
        for I := 0 to Length(FRecordCache) - 1 do
          if FRecordCache[I].DataRow = DataTable.DefaultView[Args.NewIndex].Row then
            FRecordCache[I].RecordNumber := Args.NewIndex
          else
          begin
            RecNo := FRecordCache[I].RecordNumber;
            if RecNo >= Args.NewIndex then
              FRecordCache[I].RecordNumber := RecNo + 1;
          end;
        FCursorPos := Args.NewIndex;
      end;
    ListChangedType.ItemDeleted:
      begin
        for I := 0 to Length(FRecordCache) - 1 do
        begin
          RecNo := FRecordCache[I].RecordNumber;
          if RecNo > Args.NewIndex then
            FRecordCache[I].RecordNumber := RecNo - 1;
        end;
        FCursorPos := Args.NewIndex;
        if FCursorPos = DataTable.DefaultView.Count then
          Dec(FCursorPos);
      end;
    ListChangedType.ItemMoved:
      begin
        for I := 0 to Length(FRecordCache) - 1 do
        begin
          RecNo := FRecordCache[I].RecordNumber;
          if RecNo = Args.OldIndex then
            FRecordCache[I].RecordNumber := Args.NewIndex
          else if (RecNo >= Args.NewIndex) and (RecNo < Args.OldIndex) then
            FRecordCache[I].RecordNumber := RecNo + 1
          else if (RecNo > Args.OldIndex) and (RecNo <= Args.NewIndex) then
            FRecordCache[I].RecordNumber := RecNo - 1;
        end;
        FCursorPos := Args.NewIndex;
      end;
  end;
end;

function TCustomADONETConnector.IndexOfRow(Row: DataRow; StartAt: Integer): Integer;
var
  I: Integer;
begin
  if Row.RowState = DataRowState.Detached then
    DatabaseError(SRowNotInTable);
  for I := StartAt to DataTable.Rows.Count - 1 do
    if DataTable.Rows[I] = Row then
    begin
      Result := I;
      Exit;
    end;
  Result := -1;
end;

function TCustomADONETConnector.GetKeyValues(Row: DataRow): ObjArray;
var
  I: Integer;
begin
  SetLength(Result, IndexFieldCount);
  for I := 0 to Length(Result) - 1 do
    Result[I] := Row[GetIndexField(I).FieldNo - 1];
end;

function TCustomADONETConnector.IndexOfViewRow(Row: DataRow; StartAt: Integer): Integer;
var
  I, J: Integer;
  MatchKey: Boolean;
  Key: ObjArray;
begin
  if Length(Sort) <> 0 then
  begin
    Key := GetKeyValues(Row);
    StartAt := DataTable.DefaultView.Find(Key); // this is a match, but not necessarily the first
    if StartAt < 0 then   // no match
    begin
      Result := -1;
      Exit;
    end;
    MatchKey := True;
    for I := StartAt downto 0 do // check backwards in case there are multiple matches
    begin
      if DataTable.DefaultView[I].Row = Row then
      begin
        Result := I;
        Exit;
      end;
      for J := 0 to GetIndexFieldCount do
        if DataTable.DefaultView[I].Row[GetIndexField(J).FieldNo - 1] <> Key[J] then
        begin
          MatchKey := False;
          Break;
        end;
      if not MatchKey then Break;
    end;
    Inc(StartAt); // no match backwards, proceed with the forward match
  end;
  for I := StartAt to DataTable.DefaultView.Count - 1 do
    if DataTable.DefaultView[I].Row = Row then
    begin
      Result := I;
      Exit;
    end;
  Result := -1;
end;

procedure TCustomADONETConnector.InternalAddRecord(Buffer: TRecordBuffer; Append: Boolean);
var
  I, Location, RecNo: Integer;
  Row: DataRow;
begin
  inherited InternalPost; // this calls CheckForRequiredFields
  I := BufferToIndex(Buffer);
  Row := FRecordCache[I].FieldBuffer;
  case FRecordCache[I].BookmarkFlag of
    bfBOF: RecNo := 0;
    bfEOF: RecNo := DataTable.Rows.Count;
    else RecNo := FCursorPos;
  end;
  SetRecordBufferToRow(I, Row);
  FRecordCache[I].FieldBuffer := nil;
  if Append or (DataTable.Rows.Count = 0) then
    DataTable.Rows.Add(Row)
  else
  begin
    {Find insert location in datatable, rather than view}
    if Length(Sort) <> 0 then
      Location := 0
    else
      Location := RecNo;
    Location := IndexOfRow(DataTable.DefaultView[RecNo].Row, Location);
    {and insert the record}
    DataTable.Rows.InsertAt(Row, Location);
    {Hack Alert -- on unsorted tables, the presence of the ListChanged event
     handler causes all inserted records to appear at the end of the view. We
     have to change something to get it to update}
    if (Length(Sort) = 0) and LogChanges then
    begin
      DataTable.DefaultView.Sort :=
        ValidFieldNameForExpression(DataTable.Columns[0].ColumnName) + ' ASC';
      DataTable.DefaultView.Sort := '';
      FRecordCache[I].RecordNumber := RecNo;
      FCursorPos := RecNo;
    end;
  end;
  if not LogChanges then
    DataTable.AcceptChanges;
  CursorPosChanged;
  FRecordCache[I].BookmarkFlag := bfCurrent;
  UpdateCursorPos;
end;

procedure TCustomADONETConnector.InternalPost;
var
  I, J, RecNo: Integer;
  Row: DataRow;
begin
  I := BufferToIndex(ActiveBuffer);
  case State of
    dsInsert:
      begin
        InternalAddRecord(ActiveBuffer, FRecordCache[I].BookmarkFlag = bfEOF);
        Exit;
      end;
    dsEdit:
      begin
        Row := FRecordCache[I].DataRow;
        RecNo := FRecordCache[I].RecordNumber;
        Row.BeginEdit;
        try
          for J := 0 to DataTable.Columns.Count - 1 do
            if not DataTable.Columns[J].ReadOnly then
              Row[J] := FRecordCache[I].FieldBuffer[J];
        except
          Row.CancelEdit;
          raise;
        end;
        Row.EndEdit;
        if not LogChanges then
          DataTable.AcceptChanges;
        if FRecordCache[I].RecordNumber <> RecNo then {flyaway}
          CursorPosChanged;
      end;
  end;
end;

procedure TCustomADONETConnector.InternalDelete;
begin
  DataTable.DefaultView[FCursorPos].Row.Delete;
end;

procedure TCustomADONETConnector.InternalEdit;
var
  I, J: Integer;
begin
  CheckActive;
  I := BufferToIndex(ActiveBuffer);
  if FRecordCache[I].FieldBuffer = nil then
    FRecordCache[I].FieldBuffer := DataTable.NewRow;
  for J := 0 to DataTable.Columns.Count - 1 do
    FRecordCache[I].FieldBuffer[J] := FRecordCache[I].DataRow[J];
end;

procedure TCustomADONETConnector.InternalCancel;
begin
  FRecordCache[BufferToIndex(ActiveBuffer)].FieldBuffer := nil;
end;

function TCustomADONETConnector.get_ChangeCount: Integer;
var
  Updates: System.Data.DataTable;
begin
  CheckActive;
  Updates := DataTable.GetChanges;
  if Assigned(Updates) then
  begin
    Result := Updates.Rows.Count;
    Updates.Dispose;
  end else
    Result := 0;
end;

procedure TCustomADONETConnector.CancelUpdates;
begin
  if Assigned(DataTable) then
    DataTable.RejectChanges;
end;

procedure TCustomADONETConnector.MergeChangeLog;
begin
  if Assigned(DataTable) then
    DataTable.AcceptChanges;
end;

procedure TCustomADONETConnector.SetLogChanges(const Value: Boolean);
begin
  if Value <> FLogChanges then
  begin
    FLogChanges := Value;
    if not Value and Assigned(DataTable) then
      DataTable.AcceptChanges;
  end;
end;

procedure TCustomADONETConnector.ApplyUpdates(Adapter: DbDataAdapter);
var
  ErrorIndex, I: Integer;
  Delta: System.Data.DataTable;
  View: System.Data.DataView;
  ErrorRows: array of DataRow;
  ErrorRow: DataRow;
  Actn: TBeforeUpdateAction;
  Kind: TUpdateKind;
  ClearError, UseSubset: Boolean;
begin
  CheckBrowseMode;
  if ChangeCount > 0 then
  begin
    if Assigned(FBeforeApplyUpdates) then
      FBeforeApplyUpdates(self);
    UseSubset := (Length(DataTable.PrimaryKey) > 0) and Assigned(DataTable.DataSet);
    if UseSubset then
      Delta := DataTable.GetChanges
    else
      Delta := DataTable;
    Actn := buaApply;
    if (Delta.HasErrors) and Assigned(FBeforeUpdateError) then
    begin
      FErrorBuffer := TempBuffer;
      ErrorIndex := BufferToIndex(FErrorBuffer);
      ErrorRows := Delta.GetErrors;
      for I := 0 to Length(ErrorRows) - 1 do
      begin
        ErrorRow := ErrorRows[I];
        if ErrorRow.RowState <> DataRowState.Unchanged then
        begin
          FRecordCache[ErrorIndex].BookmarkFlag := bfCurrent;
          SetRecordBufferToRow(ErrorIndex, ErrorRow);
          if ErrorRow.RowState = DataRowState.Deleted then
            Kind := ukDelete
          else if ErrorRow.RowState = DataRowState.Modified then
            Kind := ukModify
          else
            Kind := ukInsert;
          Actn := buaAbort;
          ClearError := False;
          FBeforeUpdateError(self, ErrorRow, Kind, Actn, ClearError);
          if ClearError then
            ErrorRow.ClearErrors;
          case Actn of
            buaRevert: ErrorRow.RejectChanges;
            buaAccept: ErrorRow.AcceptChanges;
            buaAbort: Break;
          end;
        end;
      end;
      FErrorBuffer := nil;
    end;
    if Actn <> buaAbort then
    begin
      Adapter.Update(Delta);
      if UseSubset then
      begin
        // before merging, accept all deletions and reject other changes
        // otherwise, successfully deleted rows will not go away or RowState
        // of other rows may not be correct.
        View := DataView.Create(DataTable, '', '', DataViewRowState.Deleted);
        if Assigned(View) then
          for I := View.Count - 1 downto 0 do
             View[I].Row.AcceptChanges;
        DataTable.RejectChanges;
        DataTable.DataSet.Merge(Delta);
      end;
    end;
    if Assigned(FAfterApplyUpdates) then
      FAfterApplyUpdates(self);
    UpdateCursorPos;
    Resync([]);
  end;
end;

  { Filters }

function TCustomADONETConnector.GetFilterStr(Field: TField; Value: Variant;
  Partial: Boolean): string;
var
  Operator,
  FieldName,
  QuoteCh: string;
begin
  QuoteCh := '';
  Operator := '=';
  if Assigned(DataTable) then
    FieldName := ValidFieldNameForExpression(DataTable.Columns[Field.FieldNo - 1].ColumnName)
  else
    FieldName := ValidFieldNameForExpression(Field.FieldName);
  if VarIsNull(Value) or VarIsClear(Value) then
    Value := 'Null'
  else
    case Field.DataType of
      ftDate, ftTime, ftDateTime:
        QuoteCh := '#';
      ftString, ftFixedChar, ftWideString:
        begin
          if Partial and (Value <> '') then
          begin
            Value := Value + '*';
            Operator := ' like ';     { Do not localize }
          end;
          if Pos('''', Value) > 0 then
            QuoteCh := '#'
          else
            QuoteCh := '''';
        end;
    end;
  Result := Format('(%s%s%s%s%2:s)', [FieldName, Operator, QuoteCh, VarToStr(Value)]);
end;

procedure TCustomADONETConnector.SetFilterOptions(Value: TFilterOptions);
begin
  if Value <> [] then
    DatabaseError(SNoFilterOptions);
end;

procedure TCustomADONETConnector.SetFilterText(const Value: string);
begin
  if Filter <> Value then
  begin
    if Active and Filtered then
    begin
      CheckBrowseMode;
      if MasterDataLink.Active then
      begin
        inherited SetFilterText(Value);
        SetDetailFilter(True)
      end else
      begin
        DataTable.DefaultView.RowFilter := Value;
        inherited SetFilterText(Value);
      end;
      First;
    end else
      inherited SetFilterText(Value);
  end;
end;

procedure TCustomADONETConnector.SetFiltered(Value: Boolean);
begin
  if Filtered <> Value then
  begin
    if Active then
    begin
      CheckBrowseMode;
      if MasterDataLink.Active {and (Parameters.Count = 0)} then
      begin
        inherited SetFiltered(Value);
        SetDetailFilter(True)
      end else
      begin
        if Value then
          DataTable.DefaultView.RowFilter := Filter
        else
          DataTable.DefaultView.RowFilter := '';
        inherited SetFiltered(Value);
      end;
      First;
    end else
      inherited SetFiltered(Value);
  end;
end;

function TCustomADONETConnector.FindRecord(Restart,
  GoForward: Boolean): Boolean;
var
  I, J, RecNo, Increment: Integer;
  RecBuf: TRecordBuffer;
  FoundRow: Boolean;
  FilterBufferIndex: Integer;
  FilteredView: DataView;
  Row: DataRow;
  Key: ObjArray;
  RowsToCheck: array of DataRowView;
begin
  CheckBrowseMode;
  SetFound(False);
  UpdateCursorPos;
  CursorPosChanged;
  if not GetActiveRecBuf(RecBuf) then
    DatabaseError(SInvalidRecordBuffer);
  DoBeforeScroll;
  I := BufferToIndex(RecBuf);
  if GoForward then
  begin
    RecNo := -1;
    Increment := 1;
  end else
  begin
    Increment := -1;
    RecNo := DataTable.DefaultView.Count;
  end;
  if not Restart then
    RecNo := FRecordCache[I].RecordNumber;
  if (not Filtered) and (Length(Filter) > 0) then
    FilteredView := DataView.Create(DataTable, GetDetailFilterStr, Sort, DataViewRowState.CurrentRows)
  else
    FilteredView := nil;

  Inc(RecNo, Increment);
  FilterBufferIndex := 0; // Remove compiler warning about uninitialized var
  if Assigned(OnFilterRecord) then
  begin
    FFilterBuffer := TempBuffer;
    FilterBufferIndex := BufferToIndex(FFilterBuffer);
    FRecordCache[FilterBufferIndex].BookmarkFlag := bfCurrent;
  end;
  while (RecNo >= 0) and (RecNo < DataTable.DefaultView.Count) and not Found do
  begin
    Row := DataTable.DefaultView[RecNo].Row;
    if Assigned(FilteredView) then // check against filtered view if necessary
    begin
      FoundRow := False;
      if Length(Sort) <> 0 then
      begin
        Key := GetKeyValues(Row);
        RowsToCheck := FilteredView.FindRows(Key);
        for J := 0 to Length(RowsToCheck) - 1 do
        begin
          if RowsToCheck[J].Row = Row then
          begin
            FoundRow := True;
            Break;
          end;
        end;
      end else // no sort, we cant rely on FindRows
      begin
        J := RecNo;
        if FilteredView.Count <= J then
          J := FilteredView.Count - 1;
        while (J >= 0) and not FoundRow do
        begin
          if FilteredView[J].Row = Row then
          begin
            FoundRow := True;
            Break;
          end;
          Dec(J);
        end;
      end;
    end else
      FoundRow := True;
      // initialize filter buffer and let event check record as well
    if FoundRow and Assigned(OnFilterRecord) then
    begin
      FRecordCache[FilterBufferIndex].RecordNumber := RecNo;
      SetRecordBufferToRow(FilterBufferIndex, DataTable.DefaultView[RecNo].Row);
      OnFilterRecord(self, FoundRow);
    end;
    if FoundRow then
    begin
      FCursorPos := RecNo;
      Resync([rmExact, rmCenter]);
      SetFound(True);
    end
    else
      Inc(RecNo, Increment);
  end;
  Result := Found;
  if Result then DoAfterScroll;
end;

{ Lookup and Locate }

function TCustomADONETConnector.LocateRecord(const KeyFields: string;
  const KeyValues: Variant; Options: TLocateOptions;
  var FoundRow: DataRow): Boolean;
var
  Fields: TObjectList;
  I, FieldCount: Integer;
  Partial: Boolean;
  SortList: string;
  LocateFilter: StringBuilder;
  LookupView: DataView;
  Field: TField;
begin
  UpdateCursorPos;
  CursorPosChanged;
  Partial := loPartialKey in Options;
  try
    Fields := TObjectList.Create;
    try
      GetFieldList(Fields, KeyFields);
      SortList := GenerateSortForFields(Fields);
      LocateFilter := StringBuilder.Create;
      if Filtered and (Length(Filter) > 0) then
      begin
        LocateFilter.AppendFormat('({0})', TObject(GetDetailFilterStr));
        LocateFilter.Append(' AND ');
      end;
      FieldCount := Fields.Count;
      if (FieldCount = 1) and
        (TField(Fields[0]).FieldKind in [fkData, fkInternalCalc, fkAggregate]) then
        LocateFilter.Append(GetFilterStr(TField(Fields[0]), KeyValues, Partial))
      else
        for I := 0 to FieldCount - 1 do
        begin
          Field := TField(Fields[I]);
          if Field.FieldKind in [fkCalculated, fkLookup] then
            DatabaseError(SCantUseCalcField, Field);
          LocateFilter.Append(GetFilterStr(TField(Fields[I]),
            KeyValues[I], (Partial and (I = FieldCount-1))));
          if I <> FieldCount - 1 then
            LocateFilter.Append(' AND ');
        end;
      LookupView := DataView.Create(DataTable, LocateFilter.ToString, SortList, DataViewRowState.CurrentRows);
      Result := LookupView.Count > 0;
      if Result then FoundRow := LookupView[0].Row;
    finally
      Fields.Free;
    end;
  except
    Result := False;
  end;
end;

function TCustomADONETConnector.Lookup(const KeyFields: string; const KeyValues: Variant;
  const ResultFields: string): Variant;
var
  Row: DataRow;
  I: Integer;
begin
  Result := Null;
  CheckBrowseMode;
  if LocateRecord(KeyFields, KeyValues, [], Row) then
  begin
    I := BufferToIndex(TempBuffer);
    SetTempState(dsCalcFields);
    try
      SetRecordBufferToRow(I, Row);
      Result := FieldValues[ResultFields];
    finally
      RestoreState(dsBrowse);
    end;
  end;
end;

function TCustomADONETConnector.Locate(const KeyFields: string;
  const KeyValues: Variant; Options: TLocateOptions): Boolean;
var
  Row: DataRow;
  RecNo: Integer;
begin
  CheckBrowseMode;
  Result := LocateRecord(KeyFields, KeyValues, Options, Row);
  if Result then
  begin
    RecNo := IndexOfViewRow(Row, 0);
    if RecNo > 0 then
      SetRecNo(RecNo);
  end;
end;

{ Informational }

function TCustomADONETConnector.GetRecordCount: Longint;
begin
  CheckActive;
  Result := DataTable.DefaultView.Count;
end;

function TCustomADONETConnector.GetRecNo: Longint;
var
  BufPtr: TRecordBuffer;
begin
  CheckActive;
  if IsEmpty or (State = dsInsert) then
    Result := -1
  else
  begin
    if State = dsCalcFields then
      BufPtr := CalcBuffer else
      BufPtr := ActiveBuffer;
    Result := FRecordCache[BufferToIndex(BufPtr)].RecordNumber;
  end;
end;

procedure TCustomADONETConnector.SetRecNo(Value: Integer);
var
  Index: Integer;
begin
  if RecNo <> Value then
  begin
    DoBeforeScroll;
    if State = dsCalcFields then
      Index := BufferToIndex(CalcBuffer) else
      Index := BufferToIndex(ActiveBuffer);
    FRecordCache[Index].RecordNumber := Value;
    SetRecordBufferToRow(Index, DataTable.DefaultView[Value].Row);
    FCursorPos := Value;
    Resync([rmCenter]);
    DoAfterScroll;
  end;
end;

function TCustomADONETConnector.IsSequenced: Boolean;
begin
  Result := True;
end;

function TCustomADONETConnector.GetDataSource: TDataSource;
begin
  Result := MasterDataLink.DataSource;
end;

procedure TCustomADONETConnector.SetDataSource(const Value: TDataSource);
begin
  if IsLinkedTo(Value) then DatabaseError(SCircularDataLink, Self);
  MasterDataLink.DataSource := Value;
end;

procedure TCustomADONETConnector.set_DataTable(const Value: System.Data.DataTable);
begin
  Close;
  FDataTable := Value;
  if Assigned(Value) then
  try
    Open;
  except
    Close;
    raise;
  end;
end;

  { Sorting }

function TCustomADONETConnector.get_Sort: string;
begin
  if (Length(FSort) = 0) and (IndexFieldCount > 0) then
    FSort := GenerateSortForFields(FIndexFields);
  Result := FSort;
end;

function SortToIndexFieldNames(Value: string): string;
var
  SB: StringBuilder;
  I: Integer;
  InName: Boolean;
begin
  SB := StringBuilder.Create(Value);
  SB.Replace(' DESC', '');  { Do not localize }
  SB.Replace(' ASC', '');   { Do not localize }
  InName := False;
  I := 0;
  while I < SB.Length do
    case SB.Chars[I] of
      ',': if not InName then SB.Replace(',',';',I, 1) else Inc(I);
      ' ': if not InName then SB.Remove(I, 1) else Inc(I);
      '[': if not InName then
           begin
             InName := True;
             SB.Remove(I, 1);
           end else
             Inc(I);
      '\': begin
             if SB.Chars[I + 1] = ']' then SB.Remove(I, 1);
             Inc(I);
           end;
      ']': begin
             InName := False;
             SB.Remove(I, 1);
           end;
      else
        Inc(I);
    end;
  Result := SB.ToString;
end;

procedure TCustomADONETConnector.set_Sort(Value: string);
var
  S: string;
begin
  FIndexFields.Clear;
  if Value = '' then
    FIndexFieldNames := Value
  else
  begin
    S := SortToIndexFieldNames(Value);
    GetFieldList(FIndexFields, S);
    FIndexFieldNames := S;
  end;
  if Active then
  begin
    DataTable.DefaultView.Sort := Value;
    First;
  end;
end;

function TCustomADONETConnector.GetIndexFieldNames: string;
var
  I: Integer;
  SB: StringBuilder;
begin
  if (Length(FIndexFieldNames) = 0) and (IndexFieldCount > 0) then
  begin
    SB := StringBuilder.Create;
    for I := 0 to IndexFieldCount - 1 do
    begin
      SB.Append(TField(FIndexFields[I]).FieldName);
      if I < IndexFieldCount - 1 then
        SB.Append(';');
    end;
    FIndexFieldNames := SB.ToString;
  end;
  Result := FIndexFieldNames;
end;

procedure TCustomADONETConnector.SetIndexFieldNames(const Value: string);
begin
  if IndexFieldNames <> Value then
  begin
    FSort := '';
    FIndexFields.Clear;
    if Value <> '' then
      GetFieldList(FIndexFields, Value);
    FIndexFieldNames := Value;
    if Active then
    begin
      DataTable.DefaultView.Sort := Sort;
      First;
    end;
  end;
end;

function TCustomADONETConnector.GetIndexField(Index: Integer): TField;
begin
  Result := TField(FIndexFields[Index]);
end;

procedure TCustomADONETConnector.SetIndexField(Index: Integer;
  const Value: TField);
begin
  GetIndexField(Index).Assign(Value);
  FIndexFieldNames := '';
  FSort := '';
  if Active then
  begin
    DataTable.DefaultView.Sort := Sort;
    First;
  end;
end;

function TCustomADONETConnector.GetIndexFieldCount: Integer;
begin
  Result := FIndexFields.Count;
end;

function TCustomADONETConnector.UpdateStatus: TUpdateStatus;
var
  Index: Integer;
begin
  CheckActive;
  if State = dsCalcFields then
    Index := BufferToIndex(CalcBuffer)
  else
    Index := BufferToIndex(ActiveBuffer);

  case FRecordCache[Index].DataRow.RowState of
    DataRowState.Added: Result := usInserted;
    DataRowState.Deleted: Result := usDeleted;
    DataRowState.Modified: Result := usModified;
    DataRowState.Unchanged: Result := usUnmodified;
  else
    Result := usInserted; // we will call detached records an insert....
  end;
end;

{ TCustomADONETConnector IProviderSupport }

function TCustomADONETConnector.FieldNameForColumnName(Name: string; FldNo: Integer): string;
var
  I: Integer;
begin
  I := 0;
  Result := Name;
  while (Result = '') or (FieldByName(Result).FieldNo <> FldNo) do
  begin
    Inc(I);
    if Name = '' then
      Result := Format('COLUMN%d', [I]) { Do not localize }
    else
      Result := Format('%s_%d', [Name, I]);
  end;
end;

function TCustomADONETConnector.FieldListForColumns(Cols: array of DataColumn): string;
var
  I: Integer;
  SB: StringBuilder;
begin
  if Length(Cols) = 0 then
  begin
    Result := '';
    Exit;
  end;
  SB := StringBuilder.Create;
  for I := 0 to Length(Cols) - 1 do
  begin
    SB.Append(FieldNameForColumnName(Cols[I].ColumnName, Cols[I].Ordinal + 1));
    if I < Length(Cols) - 1 then
      SB.Append(';');
  end;
  Result := SB.ToString;
end;

function TCustomADONETConnector.GenerateIndexForUniqueConstraint: TIndexDef;
var
  I, Found: Integer;
  Options: TIndexOptions;
begin
  Found := -1;
  if DataTable.CaseSensitive then
    Options := [ixUnique]
  else
    Options := [ixUnique, ixCaseInsensitive];
  for I := 0 to DataTable.Constraints.Count - 1 do
  begin
    if DataTable.Constraints[I] is UniqueConstraint then
      with DataTable.Constraints[I] as UniqueConstraint do
      begin
        if IsPrimaryKey then
        begin
          Result := TIndexDef.Create(nil, '', FieldListForColumns(Columns),
            Options + [ixPrimary]);
          Exit;
        end;
        if Found < 0 then
          Found := I;
      end;
  end;
  if Found >= 0 then
    with DataTable.Constraints[Found] as UniqueConstraint do
      Result := TIndexDef.Create(nil, '', FieldListForColumns(Columns), Options)
  else
    Result := nil;
end;

function EndOfEscapedName(S: string; StartAt: Integer): Integer;
var
  P, Len: Integer;
begin
  Len := Length(S);
  P := StartAt;
  while P <= Len do
  begin
    P := PosEx(']', S, P);
    if P <= 0 then
      Break;  {no ']', go to end of string}
    if (P > StartAt) and (S[P - 1] = '\') then
      Inc(P)  { this one isnt real, keep looking }
    else
    begin
      Result := P; {found it, return the position}
      Exit;
    end;
  end;
  Result := Len + 1;
end;

procedure TCustomADONETConnector.AddFieldToList(SB: StringBuilder; Field: string);
begin
  if SB.Length > 0 then
    SB.Append(';');
  SB.Append(FieldNameForColumnName(Field, DataTable.Columns.IndexOf(Field) + 1));
end;

function TCustomADONETConnector.GenerateIndexForSort(SortString: string): TIndexDef;
var
  FieldList, CurFieldList, DescFieldList: StringBuilder;
  Pos, FieldStart, Len: Integer;
  TokenName: string;
begin
  Len := Length(SortString);
  FieldList := StringBuilder.Create(Len);
  DescFieldList := StringBuilder.Create(Len);
  CurFieldList := StringBuilder.Create;
  Pos := 1;
  FieldStart := 1;
  while Pos <= Len do
  begin
    case SortString[Pos] of
      '[':
        begin
          FieldStart := Pos + 1;
          Pos := EndOfEscapedName(SortString, FieldStart);
          if Pos > FieldStart then
          begin
            TokenName := Copy(SortString, FieldStart, Pos - FieldStart);
            AddFieldToList(FieldList, TokenName);
            AddFieldToList(CurFieldList, TokenName);
          end;
          Inc(Pos);
          FieldStart := Pos;
        end;
      ' ', ',':
        begin
          if Pos > FieldStart then
          begin
            TokenName := Copy(SortString, FieldStart, Pos - FieldStart);
            if SameText(TokenName, 'ASC') then
              CurFieldList.Remove(0, CurFieldList.Length) // clear the list
            else if SameText(TokenName, 'DESC') and (CurFieldList.Length > 0) then
            begin
              if DescFieldList.Length > 0 then
                DescFieldList.Append(';');
              DescFieldList.Append(CurFieldList.ToString);
              CurFieldList.Remove(0, CurFieldList.Length);
            end else  // its a field name
            begin
              AddFieldToList(FieldList, TokenName);
              if SortString[Pos] = ','  then
                CurFieldList.Remove(0, CurFieldList.Length)
              else
                AddFieldToList(CurFieldList, TokenName);
            end;
          end;
          Inc(Pos);
          FieldStart := Pos;
        end;
      else
        Inc(Pos);
    end;
  end;
  if DataTable.CaseSensitive then
    Result := TIndexDef.Create(nil, '', FieldList.ToString, [])
  else
    Result := TIndexDef.Create(nil, '', FieldList.ToString, [ixCaseInsensitive]);
  Result.DescFields := DescFieldList.ToString;
end;

function TCustomADONETConnector.PSGetDefaultOrder: TIndexDef;
var
  S: string;
begin
  Result := nil;
  if not Assigned(Result) then
  begin
    if Assigned(DataTable) then
    begin
      S := FieldListForColumns(DataTable.PrimaryKey);
      if S <> '' then
        if DataTable.CaseSensitive then
          Result := TIndexDef.Create(nil, '', S, [ixUnique, ixPrimary])
        else
          Result := TIndexDef.Create(nil, '', S, [ixUnique, ixPrimary, ixCaseInsensitive]);
      if not Assigned(Result) then
        Result := GenerateIndexForUniqueConstraint;
      if not Assigned(Result) then
        Result := GenerateIndexForSort(DataTable.DefaultView.Sort);
    end;
    if not Assigned(Result) and (IndexFieldNames <> '') then
      Result := TIndexDef.Create(nil, '', IndexFieldNames, []);
  end;
end;

function TCustomADONETConnector.PSGetKeyFields: string;
begin
  Result := inherited PSGetKeyFields;
  if Result = '' then
    if Assigned(DataTable) then
      Result := FieldListForColumns(DataTable.PrimaryKey);
end;

function TCustomADONETConnector.PSUpdateRecord(UpdateKind: TUpdateKind;
  Delta: TDataSet): Boolean;
begin
  { OnUpdateRecord is not supported }
  Result := False;
end;

end.
