
//////////////////////////////////////////////////
//  SQL Server Data Access Components
//  Copyright © 1998-2007 Core Lab. All right reserved.
//  Access via OLE DB
//////////////////////////////////////////////////

{$IFNDEF CLR}

{$I Sdac.inc}

unit OLEDBAccess;
{$ENDIF}

interface

uses
{$IFDEF CLR}
  Borland.Vcl.TypInfo, Variants,
{$ELSE}
  CLRClasses,
{$ENDIF}
  Windows, Classes, DBConsts,
{$IFNDEF LITE}
  DB, DBAccess,
{$ENDIF}
{$IFDEF VER6P}
  FMTBcd,
{$ENDIF}
  DAConsts, SysUtils, ActiveX, SyncObjs, OLEDBC, OLEDBIntf, CRAccess, MemData,
  MSConsts, CRThread, ComObj;

const
  DefaultSDACDatabase = 'master';
  DefaultPacketSize = 4096;
  DefaultConnectionTimeout = 15;

  OLE_DB_INDICATOR_SIZE = sizeof(DWORD);

  MaxNonBlobFieldLen = 8000;  // Maximum length of "char", "varchar", "nchar", "nvarchar", fields

  // properties
  prOleDBBase       = 1000;

  // connection properties
  prDatabase        = prOleDBBase;     // string
  prIsolationLevel  = prOleDBBase + 1; // TIsolationLevel
  prAuthentication  = prOleDBBase + 2; // TMSAuthentication

  // dataset options
  prReadOnly            = prOleDBBase + 6;  // boolean
  prEnableBCD           = prOleDBBase + 7;  // boolean
  prUniqueRecords       = prOleDBBase + 8;  // boolean
  prCursorType          = prOleDBBase + 9;  // TMSCursorType
  prRequestSQLObjects   = prOleDBBase + 10; // boolean
  prCursorUpdate        = prOleDBBase + 12; // boolean
  prLockClearMultipleResults = prOleDBBase + 13; // boolean
  prConnectionTimeout   = prOleDBBase + 14; // integer
  prCommandTimeout      = prOleDBBase + 15; // integer
  prQuotedIdentifier    = prOleDBBase + 16; // boolean
  prLanguage            = prOleDBBase + 17; // string
  prEncrypt             = prOleDBBase + 18; // boolean
  prNetworkLibrary      = prOleDBBase + 19; // string
  prPacketSize          = prOleDBBase + 20; // integer
  prRoAfterUpdate       = prOleDBBase + 21; // boolean
  //prMultipleConnections = prOleDBBase + 18; // boolean
  // if False then unicode fields is supported as TStringsField else as TWideStringField
  prWideStrings         = prOleDBBase + 22; // boolean
  prApplicationName     = prOleDBBase + 23; // string
  prWorkstationID       = prOleDBBase + 24; // string
{$IFDEF VER6P}
  prEnableFMTBCD        = prOleDBBase + 25; // boolean
{$ENDIF}
  prAutoTranslate       = prOleDBBase + 26; // boolean
  prIsSProc             = prOleDBBase + 27; // boolean
  prProvider            = prOleDBBase + 28; // string
  prCanReadParams       = prOleDBBase + 29; // boolean
  prPersistSecurityInfo = prOleDBBase + 30; // boolean
  prInitialFileName     = prOleDBBase + 31; // string
  prMARS                = prOleDBBase + 32; // boolean
  prSmartRefresh        = prOleDBBase + 33; // boolean
  prSmartRefreshMsg     = prOleDBBase + 34; // string
  prSmartRefreshService = prOleDBBase + 35; // string
  prNonBlocking         = prOleDBBase + 36; // boolean
  prOldPassword         = prOleDBBase + 37; // string
  prMaxDatabaseSize     = prOleDBBase + 38; // integer
  prFailoverPartner     = prOleDBBase + 39; // string

type
  TCursorTypeChangedProc = procedure of object;
  TMSCursorType = (ctDefaultResultSet, ctStatic, ctKeyset, ctDynamic);
  TMSCursorTypes = set of TMSCursorType;

  TReadParamsProc = procedure of object;

{ internal data types }  
const
  dtMSXML = 100;

{ internal sub data types }  
const
  dtChar = 20;
  dtText = 22;
  dtWide = $8000;

const
  ServerCursorTypes: TMSCursorTypes = [ctStatic, ctKeyset, ctDynamic];

type
  TOLEDBParamDesc = class(TParamDesc)
  protected
    FOLEDBType: DBTYPE; // Native OLE DB datatype
    FUseDefaultValue: boolean;

    property OLEDBType: DBTYPE read FOLEDBType write FOLEDBType;
    property UseDefaultValue: boolean read FUseDefaultValue write FUseDefaultValue;
  public
    function GetOLEDBType: DBTYPE;
    procedure SetOLEDBType(Value: DBTYPE); virtual;

    function GetUseDefaultValue: boolean;
    procedure SetUseDefaultValue(Value: boolean);

    function GetValue: variant; override;
    function GetAsBlobRef: TBlob;

    procedure SetNull(const Value: boolean); override;
  end;
  
  TRunMethod = procedure of object;

  TOLEDBThreadWrapper = class(TCRThreadWrapper)
  protected
    procedure DoException(E: Exception); override;
  end;
  
  TExecuteThread = class(TCRThread)
  protected
    FRunMethod: TRunMethod;
    procedure InternalExecute; override;
  {$IFDEF CLR}
    property Terminated;
  {$ENDIF}
  end;

{ TOLEDBConnection }

  TOLEDBProvider = (prAuto, prSQL, prNativeClient, prCompact);

  TMSAuthentication = (auWindows, auServer);

  TIsolationLevel = (ilReadCommitted, ilReadUnCommitted, ilRepeatableRead, ilIsolated, ilSnapshot);

  EMSError = class;
  // TMSErrorProc = procedure (E: {$IFDEF LITE}Exception{$ELSE}EDAError{$ENDIF}; var Fail: boolean) of object;
  TMSInfoMessageProc = procedure (E: EMSError) of object;

  // ----------
  // Must be declared before(!) TOLEDBConnection declaration to prevent CBuilder compiler bug (pas -> hpp) 

  {
  AccessorBlock types:
    1. Ordinary. May contain many ordinary fields and only one BLOB field
      1.1. For DefaultResultSet or for CursorUpdate = False. All fields in block must be not ReadOnly
      1.2. In other cases ReadOnly and not ReadOnly fields may contains in one AccessorBlock
    2. ReadOnly. Used only for KeySet and Dynamic if CursorUpdate is True. All fields in block must be ReadOnly
    3. BLOB. May contain only one BLOB field
    4. FetchBlock. Used for dtVariant and dtMemo (not BLOB) fields

    RecordSet may contain only one each of accessor types, and some BLOB accessors if need
  }

  TAccessorBlockType = (abOrdinary, abReadOnly, abBLOB, abFetchBlock);
  TAccessorBlock = record
    BlockType: TAccessorBlockType;
    hAcc: HACCESSOR; // OLE DB accessor
    BlobFieldNum: integer; // -1, if BLOB field not avaible. This member useful to fetch BLOB streams
    FieldNums: array of integer;  
  end;

  TOLEDBRecordSet = class;
  TRestrictions = array of OleVariant;
  
  TOLEDBConnection = class (TCRConnection)
  protected
    //FIMalloc: IMalloc;

  { DataSource }
    FIDBInitialize: IDBInitialize;
    FIDBProperties: IDBProperties;
    FIDBCreateSession: IDBCreateSession;

  { Session }
    FISessionProperties: ISessionProperties;
    FITransactionLocal: ITransactionLocal;

    FCommand: TCRCommand; // Used for ExecSQL. Created on first use

    FDatabase: string;
    FIsolationLevel: TIsolationLevel;
    // FMultipleConnections: boolean;
    FMaxDatabaseSize: integer; // Comact Edition

    FAuthentication: TMSAuthentication;
    FProvider: TOLEDBProvider;

    // Init properties
    FConnectionTimeout: integer;
    FQuotedIdentifier: boolean;
    FLanguage: string;
    FAutoTranslate: boolean;
    FEncrypt: boolean;
    FPersistSecurityInfo: boolean;
    FNetworkLibrary: string;
    FPacketSize: integer;
    FApplicationName: string;
    FWorkstationID: string;
    FInitialFileName: string;
    FFailoverPartner: string;
    FMultipleActiveResultSets: boolean;
    FOldPassword: string;

    FDBMSName: string;
    FDBMSVer: string;
    FDBMSPrimaryVer: integer;
    FProviderFriendlyName: string;
    FProviderVer: string;
    FProviderPrimaryVer: integer;
    FProviderId: TGuid;

    FColumnsRowsetFieldDescs: TFieldDescs; // FieldDescs for non-Native rowsets
    FColumnsMetaInfo: TOLEDBRecordSet; // ColumnsRowset
  {$IFNDEF LITE}
    FFldCatalogNameIdx, FFldSchemaNameIdx, FFldTableNameIdx, FFldColumnNameIdx,
    FFldFieldNameIdx, FFldActualFieldNameIdx, FFldPrecisionIdx, FFldScaleIdx, FFldGuidIdx: integer;
    FFldColumnNumberIdx, FFldIsAutoIncIdx, FFldTypeIdx, FFldFlagsIdx, FFldColumnSizeIdx, FFldComputeModeIdx: integer;
    FFldXMLSchemaCollCatalogNameIdx, FFldXMLSchemaCollSchemaNameIdx, FFldXMLSchemaCollNameIdx: integer;
  {$ENDIF}

    FOnInfoMessage: TMSInfoMessageProc;

    procedure ReleaseInterfaces;
    function GetSchemaRowset(const Schema: TGUID; rgRestrictions: TRestrictions): IRowset;

    procedure GetConnectionProperties;
    procedure SetConnectionProperties;

    procedure SetQuotedIdentifier(const Value: boolean);

    procedure ExecSQL(const Text: string);

    class procedure AssignFieldDescs(Source, Dest: TFieldDescs);

  {$IFDEF CLR}
    procedure DoError(E: Exception; var Fail: boolean); override;
  {$ENDIF}
    procedure CreateDatabase(const Database: string; const Password: string; const Encrypted: boolean);
  public
    constructor Create; override;
    destructor Destroy; override;

    procedure Check(const Status: HRESULT; Component: TObject); virtual;
    procedure OLEDBError(const ErrorCode: HRESULT; Component: TObject);

    procedure Connect(const ConnectString: string); override;
    procedure Disconnect; override;

    procedure Assign(Source: TOLEDBConnection);

    procedure SetIDBCreateSession(CreateSession: IDBCreateSession);

  { Transaction control }
    procedure StartTransaction; override;
    procedure Commit; override;
    procedure Rollback; override;

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

    procedure SetDatabase(const Value: string);
    
    function CheckIsValid: boolean; override;

    //property Malloc: IMalloc read FIMalloc;
    property SessionProperties: ISessionProperties read FISessionProperties;
    property DBProperties: IDBProperties read FIDBProperties;

    property DBMSName: string read FDBMSName;
    property DBMSVer: string read FDBMSVer;
    property DBMSPrimaryVer: integer read FDBMSPrimaryVer;
    property ProviderFriendlyName: string read FProviderFriendlyName;
    property ProviderVer: string read FProviderVer;
    property ProviderPrimaryVer: integer read FProviderPrimaryVer;

    property OnInfoMessage: TMSInfoMessageProc read FOnInfoMessage write FOnInfoMessage;
  end;

{ TOLEDBCommand }
  TIntPtrDynArray = array of IntPtr;

  TParamsAccessorData = record
    Accessor: IAccessor;
    ExecuteParams: DBPARAMS;
    rgBindings: TDBBindingArray;
  end;

  TOLEDBCommand = class (TCRCommand)
  protected
    FQueryIntCnt: integer; // Quantity of calls to QueryInterfaces
    FIsSProc: boolean;
    FRPCCall: boolean;

    FPrepared: boolean;
    FConnection: TOLEDBConnection;

    FBreakExecCS: TCriticalSection; // Used to prevent clear FICommandText on BreakExec
    FWaitForBreak: boolean;

    // If statement executed w/o errors and warnings then FLastExecWarning is False
    // If statement executed with warning then FLastExecWarning is True
    // If statement executed with error then raising exception
    // Used to analyze CursorType changes in RecordSet
    FLastExecWarning: boolean;

    FCommandTimeout: integer;

  { Smart Refresh}
    FSmartRefresh: boolean;
    FSmartRefreshMsg: string;
    FSmartRefreshService: string;

  { NonBlocking}
    FNonBlocking: boolean;
    FISSAsynchStatus: ISSAsynchStatus;
    FExecutor: TOLEDBThreadWrapper;

  { Command }
    FICommandText: ICommandText;
    FICommandPrepare: ICommandPrepare;
    FICommandProperties: ICommandProperties;

  { Rowset }
    FRequestIUnknown: boolean; // Indicate current command owner - MSSQL(False) or MSDataSet(True)
    FRequestMultipleResults: boolean; // True for ctDefaultResultSet only
    FIUnknown: IUnknown; // If requested then must be setted to a nil as it possible
    FIUnknownNext: IUnknown;
    FIMultipleResults: IMultipleResults;

    FCursorState: TCursorState;

  { Params }
    FParamsAccessorData: TParamsAccessorData;
    FParamsAccessorDataAvaible: boolean;
    FCanReadParams: boolean;
  {$IFDEF CLR}
    FParamsGC: TIntPtrDynArray; // List of AllocGCHandle(ParamDesc.Value, True)
  {$ENDIF}

  { Rows }
    FRowsAffected: integer;
    FRowsAffectedNext: integer;

    FReadParams: TReadParamsProc;
    FNextResultRequested: boolean; // FIUnknownNext getted from OLE DB

    procedure Check(const Status: HRESULT); virtual;
    procedure QueryInterfaces(const QueryPrepare: boolean); // QueryPrepare must be True to request IID_ICommandPrepare
    procedure ReleaseInterfaces;

    procedure SetCommandProp;
    procedure SetParameterInfo;

  { Params }
    procedure CreateAndFillParamAccs;
    procedure RequestParamsIfPossible; // Call RequestAndFreeParamAccs if interfaces is cleared

    procedure CheckAndAnalyze(const Status: HRESULT);
    procedure GetNextResult(out ResultSet: IUnknown; out RowsAffected: integer);

  public
    constructor Create; override;
    destructor Destroy; override;

    procedure Prepare; override;
    procedure Unprepare; override;
    function GetPrepared: boolean; override;

    function CreateProcCall(Name: string; const NeedDescribe: boolean; const WideStrings: boolean;
      const EnableBcd: boolean; const EnableFmtBcd: boolean): string;
    procedure Execute(Iters: integer = 1); override;
    procedure DoExecuteTerminate(Sender: TObject);
    procedure DoExecuteException(Sender: TObject; E: Exception; var Fail: boolean);
    procedure WaitAsynchCompletion;
    procedure EndExecute(E: Exception);

    procedure SetConnection(Value: TCRConnection); override;
    function GetCursorState: TCursorState; override;
    procedure SetCursorState(Value: TCursorState); override;

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

    procedure BreakExec;

    // Interface management
    function IUnknownIsNull: boolean;
    function IUnknownNextIsNull: boolean;
    function IMultipleResultsIsNull: boolean;
    function ISSAsynchStatusIsNull: boolean;
    procedure ClearIUnknown;
    procedure ClearIUnknownNext;
    procedure ClearIMultipleResults;
    procedure ClearISSAsynchStatus;

  { Params }
    function GetParamDescType: TParamDescClass; override;
    function AddParam: TParamDesc; override;
    function GetParam(Index: integer): TOLEDBParamDesc;
    property Params;

    property ReadParams: TReadParamsProc read FReadParams write FReadParams;
    property Executing;
  end;


{ TOLEDBRecordSet }

  TFetchAccessorData = record
    Accessor: IAccessor;

    AccessorBlocks: array of TAccessorBlock;
  end;

  TOLEDBFieldDesc = class (TCRFieldDesc)
  private
    FOLEDBType: DBTYPE; // Native OLE DB datatype
    FIsAutoIncrement: boolean;
    FBaseCatalogName: string;
    FBaseColumnName: string;
    FBaseSchemaName: string;
    FBaseTableName: string;
    FIsTimestamp: boolean;
    FXMLSchemaCollectionCatalogName: string;
    FXMLSchemaCollectionSchemaName: string;
    FXMLSchemaCollectionName: string;
    FXMLTyped: boolean;
  public
    function ActualNameQuoted(RecordSet: TCRRecordSet; const QuoteNames: boolean): string; override;

    property OLEDBType: DBTYPE read FOLEDBType; // Native OLE DB datatype

    property BaseCatalogName: string read FBaseCatalogName;
    property BaseSchemaName: string read FBaseSchemaName;
    property BaseTableName: string read FBaseTableName;
    property BaseColumnName: string read FBaseColumnName;
    property IsAutoIncrement: boolean read FIsAutoIncrement;
    property IsTimestamp: boolean read FIsTimestamp;

    property XMLSchemaCollectionCatalogName: string read FXMLSchemaCollectionCatalogName;
    property XMLSchemaCollectionSchemaName: string read FXMLSchemaCollectionSchemaName;
    property XMLSchemaCollectionName: string read FXMLSchemaCollectionName;
    property XMLTyped: boolean read FXMLTyped;
  end;

  TOLEDBTableInfo = class(TCRTableInfo)
  protected
    FMaxTimestamp: Int64;
  public
    class function LeftQuote: Char; override;
    class function RightQuote: Char; override;
    property MaxTimestamp: Int64 read FMaxTimestamp write FMaxTimestamp;
  end;

  TOLEDBRecordSet = class (TCRRecordSet)
  private
    FProviderPrimaryVer: integer;
    FDBMSPrimaryVer: integer;
    FProviderId: TGuid;
    FProvider: TOLEDBProvider;
    FDisconnectedMode: boolean;
    FDatabase: string;
    function GetProviderPrimaryVer: integer;
    function GetDBMSPrimaryVer: integer;
    function GetProviderId: TGuid;
    function GetProvider: TOLEDBProvider;
    function GetDisconnectedMode: boolean;
    function GetDatabase: string;
  protected
    FLockClearMultipleResults: boolean; // Used to prevent clear FCommand.FIMultipleResult on Close by OpenNext
    FroAfterUpdate: boolean; // DataSet.RefreshOptions.roAfterUpdate

    FNativeRowset: boolean; // For non-native rowsets output parameters not supported
    FIsColumnsRowset: boolean; // If True then FieldDescs was stored in FColumnsRowsetFieldDescs

    FCommand: TOLEDBCommand;
    FFetchAccessorData: TFetchAccessorData;

  { Rowset }
    FIRowset: IRowset;
    FIRowsetLocate: IRowsetLocate;
    FIRowsetUpdate: IRowsetUpdate;

    FCursorType: TMSCursorType;
    FReadOnly: boolean;

    FRequestSQLObjects: boolean;

    FEnableBCD: boolean;
  {$IFDEF VER6P}
    FEnableFMTBCD: boolean;
  {$ENDIF}
    FUniqueRecords: boolean;
    FWideStrings: boolean;

    // Bookmarks
    FFetchFromBookmark: boolean;
    FProcessDynBofEof: boolean; // True, if processing NoResult for ctDynamic
    FBookmarkValue: integer; // OLEDB IRowsetLocate Bookmark value. If FBookmarkValue = - 1 then last fetch is not OK for KeySet cursor
    FBookmarkSize: integer; // OLEDB IRowsetLocate Bookmark size. If FBookmarkSize = 4 then bookmark is ordinal otherwise bookmark is special (DBBMK_LAST, DBBMK_FIRST)
    FBookmarkOffset: integer; // Offset from pHBlock to bookmark field value

    FFetchBlock: IntPtr;
    FFetchBlockSize: integer;
    FLastFetchOK: boolean; // True, if previous Fetch was called succesfulity (Result is True)
    FLastFetchEnd: boolean; // True, If previous FIRowset.GetNextRows has return DB_S_ENDOFROWSET
    FLastFetchBack: boolean; // True, if previous Fetch was called with True parameter

    // HRow for IRowsetUpdate
    FHRow: HRow;
    FHRowAccessible: boolean; // True, if FHRow is setted to valid value

    FCursorUpdate: boolean;

    FCursorTypeChanged: TCursorTypeChangedProc;

    FSchema: TGUID;
    FRestrictions: TRestrictions;

    FFetching: boolean;
    FFetchExecutor: TOLEDBThreadWrapper;
    FBeforeFetch: boolean;
    FAfterFetch: boolean;

    FPopulatingKeyInfo: boolean;

    procedure ClearHRowIfNeed;

    procedure Check(const Status: HRESULT); virtual;
    procedure CheckBCDOverflow(const FieldNo: integer {from 1}; RecBuf: IntPtr);
    procedure CheckAndAnalyzeFieldsStatus(const Status: HRESULT; const pRec: IntPtr);

    procedure QueryCommandInterfaces(const QueryPrepare: boolean); // Create ConnectionSwap if need. Call FCommand.QueryInterfaces.
    procedure ReleaseCommandInterfaces;
    procedure QueryRecordSetInterfaces; // Reqests IRowset, IRowsetLocate, IRowsetUpdate
    procedure ReleaseRecordSetInterfaces;
    procedure ReleaseAllInterfaces(const ReleaseMultipleResults: boolean);

    procedure CreateCommand; override;
    procedure SetCommand(Value: TCRCommand); override;

  { Open / Close }
    function NeedInitFields: boolean; override;
    procedure InternalPrepare; override;
    procedure InternalUnPrepare; override;
    procedure InternalOpen; override;
    procedure InternalClose; override;

  { Fields }
    procedure InternalInitFields; override;
    function GetIndicatorSize: word; override;

  { Fetch }
    procedure AllocFetchBlock; // Also create fetch accessors
    function Fetch(FetchBack: boolean = False): boolean; override;
    procedure FreeFetchBlock; // Also free fetch accessors
    function CanFetchBack: boolean; override; // Return True, if BlockMan is store only one block of records

  { Modify }
    procedure RowsetUpdateCommit;
    procedure RowsetUpdateRollback;
    procedure InternalAppend(RecBuf: IntPtr); override;
    procedure InternalDelete; override;
    procedure InternalUpdate(RecBuf: IntPtr); override;
    procedure InternalAppendOrUpdate(RecBuf: IntPtr; const IsAppend: boolean);

    procedure SetCommandProp;

    procedure RequestParamsIfPossible; // Call FCommand.RequestAndFreeParamAccs if interfaces is cleared

  { TablesInfo }
    class function GetTableInfoClass: TTableInfoClass; override;

    //PreCached FConection properties
    property ProviderPrimaryVer: integer read GetProviderPrimaryVer;
    property DBMSPrimaryVer: integer read GetDBMSPrimaryVer;
    property ProviderID: TGuid read GetProviderId;
    property Provider: TOLEDBProvider read GetProvider;
    property DisconnectedMode: boolean read GetDisconnectedMode;
    property Database: string read GetDatabase;
  public
    constructor Create; override;
    destructor Destroy; override;

  { Fields}
    function GetFieldDescType: TFieldDescClass; override;

  { Open / Close }
    procedure ExecCommand; override;
    procedure Open; override;
    procedure Reopen; override;
    function GetSchemaRowset(const Schema: TGUID; rgRestrictions: TRestrictions): IRowset;
    procedure Disconnect; override;

  { Fetch }
    procedure FetchAll; override;
    procedure DoFetchAll; virtual;
    procedure DoFetchTerminate(Sender: TObject);
    procedure DoFetchException(Sender: TObject; E: Exception; var Fail: boolean);
    procedure DoFetchSendEvent(Sender: TObject; Event: TObject);
    procedure EndFetchAll(E: Exception); virtual;
    procedure BreakFetch; override;
    function CanDisconnect: boolean; override;

  { Fields }
    function GetNull(FieldNo: word; RecBuf: IntPtr): boolean; override;
    procedure SetNull(FieldNo: word; RecBuf: IntPtr; Value: boolean); override;
    function GetStatus(FieldNo: word; RecBuf: IntPtr): DWORD;
    procedure SetStatus(FieldNo: word; RecBuf: IntPtr; Value: DWORD);
    procedure GetFieldData(Field: TFieldDesc; RecBuf: IntPtr; Dest: IntPtr); override;
    procedure GetFieldAsVariant(FieldNo: word; RecBuf: IntPtr; var Value: variant);override;
    procedure PutFieldData(Field: TFieldDesc; RecBuf: IntPtr; Source: IntPtr); override;
    function IsBlobFieldType(DataType: word): boolean; override;

  { Records }
    procedure InsertRecord(RecBuf: IntPtr); override;
    procedure UpdateRecord(RecBuf: IntPtr); override;
    procedure DeleteRecord; override;

  { Sorting }
    procedure SetIndexFieldNames(Value: string); override;
    
  { Filter/Find/Locate }

    procedure CreateComplexField(RecBuf: IntPtr; FieldIndex: integer; WithBlob: boolean); override;
    procedure CreateComplexFields(RecBuf: IntPtr; WithBlob: boolean); override;
    procedure FreeComplexFields(RecBuf: IntPtr; WithBlob: boolean); override;

  { Navigation }
    procedure SetToBegin; override;
    procedure SetToEnd; override;
    //procedure GetBookmark(Bookmark: PRecBookmark); override;
    procedure SetToBookmark(Bookmark: PRecBookmark); override;
    function FetchToBookmarkValue(FetchBack: boolean = False): boolean; // Fetch to Bookmark. Bookmark value is stored in FBookmarkValue. Bookmark value used only for ctStatic and ctKeyset. For ctDynamic method refetched current record in specified direction
    function CompareBookmarks(Bookmark1, Bookmark2: PRecBookmark): integer; override;

    function RowsReturn: boolean; override;

    function GetIRowset: IRowset;
    function GetICommandText: ICommandText;
    procedure SetIRowset(
      Rowset: IRowset;
      const IsColumnsRowset: boolean); // If True then FieldDescs was stored in FColumnsRowsetFieldDescs

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

    property NativeRowset: boolean read FNativeRowset;
    property CursorTypeChanged: TCursorTypeChangedProc read FCursorTypeChanged write FCursorTypeChanged;

    property FetchExecutor: TOLEDBThreadWrapper read FFetchExecutor;
  end;

{ TOLEDBPropertiesSet }

  TOLEDBPropertiesSet = class
  protected
    FConnection: TOLEDBConnection;
    FInitPropSet: PDBPROPSET;

    procedure Check(const Status: HRESULT);
    function GetInitPropSetStatus: string;
    function GetDBPropPtr(Index: UINT): PDBProp;

    function InitProp(const dwPropertyID: DBPROPID; const Required: boolean = False): PDBProp;

  public
    constructor Create(Connection: TOLEDBConnection; const GuidPropertySet: TGUID);
    destructor Destroy; override;

//    procedure AddProp(const dwPropertyID: DBPROPID; const Value: OleVariant);
    procedure AddPropSmallInt(const dwPropertyID: DBPROPID; const Value: smallint);

    procedure AddPropInt(const dwPropertyID: DBPROPID; const Value: integer);
    procedure AddPropBool(const dwPropertyID: DBPROPID; const Value: boolean; const Required: boolean = False);
    procedure AddPropStr(const dwPropertyID: DBPROPID; const Value: string; const Required: boolean = False);

    procedure SetProperties(Obj: IDBProperties); overload;
    procedure SetProperties(Obj: ISessionProperties); overload;
    procedure SetProperties(Obj: ICommandProperties); overload;

    property InitPropSet: PDBPROPSET read FInitPropSet;
  end;

{ TOLEDBPropertiesGet }

  TPropValues = array of Variant;

  TOLEDBPropertiesGet = class
  protected
    FConnection: TOLEDBConnection;
    FInitPropSet: PDBPROPSET;

    FPropIds: array of DBPROPID;
    FPropIdsGC: IntPtr;

    procedure Check(const Status: HRESULT);
    function GetDBPropPtr(rgProperties: PDBPropArray; Index: UINT): PDBProp;

    procedure PrepareToGet;
    procedure ProcessResult(rgPropertySets: PDBPropSet; var PropValues: TPropValues);
    procedure ClearResult(rgPropertySets: PDBPropSet);

  public
    constructor Create(Connection: TOLEDBConnection; const GuidPropertySet: TGUID);
    destructor Destroy; override;

    procedure AddPropId(Id: DBPROPID);

    procedure GetProperties(Obj: IDBProperties; var PropValues: TPropValues); overload;
    procedure GetProperties(Obj: IRowsetInfo; var PropValues: TPropValues); overload;
    procedure GetProperties(Obj: ICommandProperties; var PropValues: TPropValues); overload;
  end;

{ TOLEDBErrors }
  EOLEDBError = class;

  TOLEDBErrors = class
  protected
    FList: TList;

    function GetCount: integer;
    function GetError(Index: Integer): EOLEDBError;

    procedure Assign(Source: TOLEDBErrors);
    procedure Clear;

  public
    constructor Create;
    destructor Destroy; override;

    property Count: integer read GetCount;
    property Errors[Index: Integer]: EOLEDBError read GetError; default;
  end;

{ EOLEDBError }

{$IFDEF LITE}
  EOLEDBError = class(Exception)
  protected
    FErrorCode: integer;

  public
    property ErrorCode: integer read FErrorCode;

{$ELSE}
  EOLEDBError = class(EDAError)
{$ENDIF}
  protected
    FErrors: TOLEDBErrors;
    FOLEDBErrorCode: integer;

    // GetBasicErrorInfo - ERRORINFO struct
    // FhrError: HResult; - equal to FOLEDBErrorCode
    // FMinor: UINT; - only for EMSError and equal to FMSSQLErrorCode
    // Fclsid: TGUID; - always CLSID_SQLOLEDB
    Fiid: TGUID;
    // Fdispid: Integer; - always 0

    // GetErrorInfo - IErrorInfo interface
    // Fguid: TGUID; - same as Fiid
    // FSource: WideString; - always "Microsoft OLE DB Provider for SQL Server"
    // FDescription: WideString; - equal to Message
    // FHelpFile: WideString; - not used by sqloledb
    // FHelpContext: Longint; - not used by sqloledb

    FMessageWide: WideString;

    function GetErrorCount: integer;
    function GetError(Index: Integer): EOLEDBError;

    procedure Assign(Source: EOLEDBError); virtual;

  protected
    property iid: TGUID read Fiid;

  public
    constructor Create(ErrorCode: integer; Msg: WideString);
    destructor Destroy; override;

    property ErrorCount: integer read GetErrorCount;
    property Errors[Index: Integer]: EOLEDBError read GetError; default;

    property OLEDBErrorCode: integer read FOLEDBErrorCode;
    property MessageWide: WideString read FMessageWide;
    // property hrError: HResult read FhrError;
    // property Minor: UINT read FMinor;
    // property clsid: TGUID read Fclsid;
    // property iid: TGUID read Fiid; - protected
    // property dispid: Integer read Fdispid;
    // property Source: WideString read FSource;
    // property Description: WideString read FDescription;
  end;


{ EMSError }

  EMSError = class(EOLEDBError)
  protected
    FMSSQLErrorCode: integer;

    FServerName: string;
    FProcName: string;
    FState: BYTE;
    FSeverityClass: BYTE;
    FLineNumber: WORD;
    FLastMessage: string;

    procedure Assign(Source: EOLEDBError); override;

  public
    constructor Create(
      const pServerErrorInfo: SSERRORINFO;
      OLEDBErrorCode: integer;
      Msg: WideString); overload;

    property MSSQLErrorCode: integer read FMSSQLErrorCode;
    property ServerName: string read FServerName;
    property ProcName: string read FProcName;
    property State: BYTE read FState;
    property SeverityClass: BYTE read FSeverityClass;
    property LineNumber: WORD read FLineNumber;

    property LastMessage: string read FLastMessage;
  end;

  function ConvertInternalTypeToOLEDB(const InternalType: word; const IsParam: boolean;
    ServerVersion: Integer): word;

  function ConvertOLEDBTypeToInternalFormat(
    const OLEDBType: DBTYPE;
    const IsLong: Boolean;
    const EnableBCD, EnableFMTBCD: boolean;
    const WideStrings: boolean;
    const IsParam: boolean;
    var InternalType: word; ServerVersion: Integer): boolean;

  function DBNumericToBCD(Value: TDBNumeric): TBCD;
  function BcdToDBNumeric(const Bcd: TBcd): TDBNumeric;
  
  function DBNumericToDouble(Value: TDBNumeric): double;
  function DoubleToDBNumeric(Value: double; Precision, Scale: integer): TDBNumeric;

  function IsLargeDataTypeUsed(const FieldDesc: TFieldDesc): boolean; overload;
  function IsLargeDataTypeUsed(const ParamDesc: TParamDesc): boolean; overload;

  function GetProviderName(const Provider: TOLEDBProvider): string;
  function GetProvider(const ProviderName: string): TOLEDBProvider;
const
  slDelimiter = #1;

  function BracketIfNeed(const Value: string): string; overload;
  function BracketIfNeed(const Value: string; const LeftQ: char; const RightQ: char): string; overload;
  function UnbracketIfPossible(const Value: string): string; overload;
  function UnbracketIfPossible(const Value: string; var sl: string {Result parts in reverse order delimited by slDelimiter}): string; overload;
  function UnbracketIfPossible(const Value: string; out DataBase: string; out Owner: string; out ObjName: string): string; overload;

  function GenerateTableName(const CatalogName: string;
    const SchemaName: string;
    const TableName: string;
    const DefaultCatalogName: string): string;

  function ChangeDecimalSeparator(const Value: Variant): string;
  function GetParamNameWODog(const ParamName: string): string;

{$IFDEF CLR}
  procedure QueryIntf(Source: IUnknown; const IID: TInterfaceRef; out Intf); // OLE QueryInterface analog
{$ELSE}
  procedure QueryIntf(Source: IUnknown; const IID: TGuid; out Intf); // OLE QueryInterface analog
{$ENDIF}

{$IFDEF DEBUG}
var
  StreamCnt: integer = 0;
{$ENDIF}

var
  __UseRPCCallStyle: boolean; // temporary
  ParamsInfoOldBehavior: boolean; // delete 03.06.2006

//procedure AddInfoToErr(var S: string; const FormatStr: string; const Args: array of const); overload; // Add additional info to exception message
procedure AddInfoToErr(var E: Exception; const FormatStr: string; const Args: array of const); overload; // Add additional info to exception message

{$IFDEF SDAC_TEST}
var
  __ServerPrepareCount: integer;
  __ServerExecuteCount: integer;
  __SetCommandPropCount: integer;
  __SetRecordSetCommandPropCount: integer;

{$ENDIF}

const
  LeftQuote = '[';
  RightQuote = ']';
  EmptyString = '';
const
  BytesByRef = [dtBlob, dtBytes, dtVarBytes];
  CharsByRef = [dtMemo, dtWideMemo, dtMSXML, dtString, dtWideString];

procedure FillBindingForParam(Ordinal: integer; ParamDesc: TOLEDBParamDesc; Connection: TOLEDBConnection;
  var pBind: TDBBinding; var BindMemorySize: UINT; const ValueAvaliable: boolean; const IsWide: boolean);
  
procedure SaveParamValue(const ParamDesc: TParamDesc; const pBind: TDBBinding;
  var ParamsAccessorData: TParamsAccessorData
  {$IFDEF HAVE_COMPRESS}; const CompressBlobMode: TCompressBlobMode{$ENDIF}
  {$IFDEF CLR}; var ParamsGC: TIntPtrDynArray{$ENDIF}; ServerVersion, ClientVersion: integer);

implementation

uses
  MemUtils,
{$IFDEF VER6P}
  DateUtils,
{$ENDIF}
{$IFDEF CLR}
  System.Text, System.Runtime.InteropServices, System.Globalization, System.IO, System.Threading,
{$ELSE}
{$IFDEF VER6P}
  Variants,
{$ENDIF}
{$ENDIF}
  CRParser, MSParser, Math;

type
  TOLEDBStream = class(TInterfacedObject, ISequentialStream)
  protected
    FSize: UINT;
    FBlob: TBlob;
    FPosInBlob: cardinal;
    FStreamList: TList;

  public
  {$IFDEF CLR}
    [PreserveSig]
    function Read(
      pv: IntPtr;
      cb: Longint;
      pcbRead: PLongint): HResult;

    [PreserveSig]
    function Write(
      pv: IntPtr;
      cb: Longint;
      pcbWritten: PLongint): HResult;
  {$ELSE}
    function Read(pv: IntPtr; cb: Longint; pcbRead: PLongint): HResult;
      stdcall;
    function Write(pv: IntPtr; cb: Longint; pcbWritten: PLongint): HResult;
      stdcall;
  {$ENDIF}

  public
    constructor Create(Blob: TBlob {to avoid data copy}; StreamList: TList);
    destructor Destroy; override;

    property Size: UINT read FSize; // Return size of stream in bytes
  end;

const
  MaxLength = 130;
  SizeOfLongWord = 4;

type
{$IFDEF CLR}
  [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Auto)]
{$ENDIF}
  RBigInteger = packed record
    Data: array[0..MaxLength - 1] of LongWord;
    DataLength: integer;
  end;

const
  SizeOfRBigInteger = 524;

type
  PBigInteger = IntPtr;

  TBigIntegerAccessor = class
  protected
    FSelfAllocated: boolean;
    FBigIntegerPtr: PBigInteger;
    procedure SetBigIntegerPtr(Value: PBigInteger);
    function GetDataLength: integer;
    procedure SetDataLength(Value: integer);
    function GetDataPtr: IntPtr;
    function GetData(Index: integer): LongWord;
    procedure SetData(Index: integer; Value: LongWord);
    function GetBigInteger: RBigInteger;
    procedure SetBigInteger(const Value: RBigInteger);
  public
    constructor Create(BigIntegerPtr: PBigInteger; Allocate: boolean);
    destructor Destroy; override;
    procedure ClearData;
    property DataLength: integer read GetDataLength write SetDataLength;
    property DataPtr: IntPtr read GetDataPtr;
    property Data[Index: integer]: LongWord read GetData write SetData;
    property BigIntegerPtr: PBigInteger read FBigIntegerPtr write SetBigIntegerPtr;
    property BigInteger: RBigInteger read GetBigInteger write SetBigInteger;
  end;

  TBigInteger = class
  public
    class procedure CreatePBigIntegerPtr(PBigInt: PBigInteger);
    class procedure CreateRBigIntegerStruct(var BigInt: RBigInteger);
    class procedure CreatePBigInteger64(PBigInt: PBigInteger; Value: Int64);
    class procedure CreatePBigIntegerBCD(PBigInt: PBigInteger; const Value: TBcd);

    class function Add(PBigInt1: PBigInteger; PBigInt2: PBigInteger): RBigInteger;
    class function NEGATEPtr(PBigInt: PBigInteger): RBigInteger;
    class function NEGATE(const BigInt: RBigInteger): RBigInteger;
    class function Mul(PBigInt1: PBigInteger; PBigInt2: PBigInteger): RBigInteger; 
    class function Mul64(PBigInt1: PBigInteger; Value: Int64): RBigInteger;
  end;

var
  dstSmallint: IntPtr;
  dstInt: IntPtr;
  dstReal: IntPtr;
  dstFloat: IntPtr;
  dstMoney: IntPtr;
  dstDateTime: IntPtr;
  dstNVarChar: IntPtr;
  dstNVarCharMax: IntPtr;
  dstVarChar: IntPtr;
  dstVarCharMax: IntPtr;

  dstBit: IntPtr;
  dstTinyInt: IntPtr;
  dstBigint: IntPtr;
  dstSql_variant: IntPtr;
  dstImage: IntPtr;
  dstBinary: IntPtr;
  dstVarBinary: IntPtr;
  dstGuid: IntPtr;

var
  IsWindowsVista: boolean;
{$IFNDEF CLR}
  GlobaIMalloc: IMalloc;
{$ENDIF}

procedure FreeCoMem(ptr: IntPtr);
begin
{$IFDEF CLR}
  Marshal.FreeCoTaskMem(ptr);
{$ELSE}
  if GlobaIMalloc = nil then
    CoGetMalloc(1, GlobaIMalloc);
  GlobaIMalloc.Free(ptr);
{$ENDIF}
end;

{$IFDEF LITE}
procedure DatabaseError(const Message: string; Component: TComponent = nil);
begin
  if Assigned(Component) and (Component.Name <> '') then
    raise Exception.Create(Format('%s: %s', [Component.Name, Message])) else
    raise Exception.Create(Message);
end;

procedure DatabaseErrorFmt(const Message: string; const Args: array of const;
  Component: TComponent = nil);
begin
  DatabaseError(Format(Message, Args), Component);
end;
{$ENDIF}

procedure AddInfoToErr(var S: WideString; const FormatStr: string; const Args: array of const); overload; // Add additional info to exception message
var
  s1: WideString;
begin
  s1 := Format(FormatStr, Args);
  if s1 <> '' then
    S := S + #10 + s1;
end;

procedure AddInfoToErr(var E: Exception; const FormatStr: string; const Args: array of const); overload; // Add additional info to exception message
var
  S: WideString;
{ $IFDEF CLR}
  ENew: Exception;
{ $ENDIF}
begin
  if IsClass(E, EOLEDBError) then
    S := EOLEDBError(E).MessageWide
  else
    S := E.Message;
  AddInfoToErr(S, FormatStr, Args);
{ $IFDEF CLR}
  if IsClass(E, EMSError) then begin
    ENew := EMSError.Create(EMSError(E).ErrorCode, S);
    EMSError(ENew).Assign(EMSError(E));
  end
  else
  if IsClass(E, EOLEDBError) then begin
    ENew := EOLEDBError.Create(EOLEDBError(E).ErrorCode, S);
    EOLEDBError(ENew).Assign(EOLEDBError(E));
  end
  else
{$IFNDEF LITE}
  if IsClass(E, EDAError) then
    ENew := EDAError.Create(EDAError(E).ErrorCode, S)
  else
{$ENDIF}
    ENew := Exception.Create(S);
  E := ENew;
{ $ELSE}
//  E.Message := S;
{ $ENDIF}
end;

{$WARNINGS OFF}
function ConvertInternalTypeToOLEDB(const InternalType: word; const IsParam: boolean;
  ServerVersion: Integer): word;
begin
  case InternalType of
    // Integer fields
    dtBoolean:
      Result := DBTYPE_BOOL;
    dtInt8:
      if ServerVersion <> 3 then
        Result := DBTYPE_I1
      else
        Result := DBTYPE_UI1;
    dtWord:
      if ServerVersion <> 3 then
        Result := DBTYPE_I2
      else
        Result := DBTYPE_UI2;
    dtInt16:
      Result := DBTYPE_I2;
    dtInt32:
      Result := DBTYPE_I4;
    dtUInt32:
      Result := DBTYPE_UI4;
    dtInt64:
      Result := DBTYPE_I8;

    // Float fields
    dtFloat:
      Result := DBTYPE_R8;
    dtCurrency:
      if ServerVersion <> 3 then
        Result := DBTYPE_R8
        // Result := DBTYPE_CY; Currency type cannot be used over TCurrencyField uses double to store
      else
        Result := DBTYPE_CY;
    // Multibyte fields
    dtDateTime:
      if ServerVersion <> 3 then
        Result := DBTYPE_DATE
      else
        Result := DBTYPE_DBTIMESTAMP;
    dtDate, dtTime:
      Result := DBTYPE_DATE;
    dtString:
      Result := DBTYPE_STR;
    dtWideString:
      Result := DBTYPE_WSTR;
    dtExtString:
      Result := DBTYPE_STR;
    dtExtWideString:
      Result := DBTYPE_WSTR;
    dtBytes, dtVarBytes:
      Result := DBTYPE_BYTES;
    dtExtVarBytes:
      Result := DBTYPE_BYTES;
    dtMemo, dtWideMemo, dtMSXML, dtBlob:
      Result := DBTYPE_IUNKNOWN;
  {$IFDEF VER5P}
    dtGuid:
      Result := DBTYPE_GUID;
    dtVariant:
      Result := DBTYPE_VARIANT;
  {$ENDIF}
    dtBCD:
      if ServerVersion <> 3 then
        Result := DBTYPE_CY
      else
        Result := DBTYPE_NUMERIC;
  {$IFDEF VER6P}
    dtFmtBCD:
      if IsParam and (ServerVersion <> 3) then
        Result := DBTYPE_STR
      else
        Result := DBTYPE_NUMERIC;
  {$ENDIF}
    dtUnknown:
      Result := DBTYPE_VARIANT;
    else
      Assert(False, Format('Invalid internal field type $%X (%d)', [InternalType, InternalType]));
  end;
end;
{$WARNINGS ON}

function ConvertOLEDBTypeToInternalFormat(
  const OLEDBType: DBTYPE;
  const IsLong: boolean;
  const EnableBCD, EnableFMTBCD: boolean;
  const WideStrings: boolean;
  const IsParam: boolean;
  var InternalType: word; ServerVersion: Integer): boolean;
begin
  Result := True;
  case OLEDBType of // List of types must be synchronized with InternalInitFields types list
    // Integer fields
    DBTYPE_BOOL:
      InternalType := dtBoolean;
    DBTYPE_UI1:
      InternalType := dtWord;
    DBTYPE_I2:
      InternalType := dtInt16;
    DBTYPE_I4:
      InternalType := dtInt32;
    DBTYPE_UI2, DBTYPE_UI4:{WAR For OLE DB info only. Signed/unsigned conversion}
      if ServerVersion <> 3 then
        InternalType := dtInt32
      else
        case OLEDBType of
          DBTYPE_UI2:
            InternalType := dtUInt16;
          DBTYPE_UI4:
            InternalType := dtUInt32;
        end;
    DBTYPE_I8:
      InternalType := dtInt64;

    // Float fields
    DBTYPE_NUMERIC:
      if EnableBCD then begin
      {$IFDEF VER6P}
        if EnableFMTBCD then
          InternalType := dtFmtBCD
        else
      {$ENDIF}
          InternalType := dtBCD;
      end
      else
        InternalType := dtFloat;
    DBTYPE_R4, DBTYPE_R8:
      InternalType := dtFloat;
    DBTYPE_CY:
      InternalType := dtCurrency;

    // Multibyte fields
    DBTYPE_DBTIMESTAMP, DBTYPE_DATE:
      InternalType := dtDateTime;
    DBTYPE_STR:
    begin
      if IsLong then
        InternalType := dtMemo
      else
        InternalType := dtString;
    end;
    DBTYPE_WSTR:
    begin
      if IsLong then begin
        if WideStrings then
          InternalType := dtWideMemo
        else
          InternalType := dtMemo;
      end
      else
        if WideStrings then
          InternalType := dtWideString
        else
          InternalType := dtString;
    end;
    DBTYPE_BYTES:
    begin
      if IsLong then
        InternalType := dtBlob
      else
        InternalType := dtBytes;
    end;
    DBTYPE_GUID:
    {$IFDEF VER5P}
      InternalType := dtGuid;
    {$ELSE}
      InternalType := dtString;
    {$ENDIF}
    DBTYPE_VARIANT:
    {$IFDEF VER5P}
    {$IFDEF LITE}
      InternalType := dtString;
    {$ELSE}
    {$IFDEF CLR}
      if IsParam and (ServerVersion = 9) then
        InternalType := dtVariant
      else
        InternalType := dtString;
    {$ELSE}
      InternalType := dtVariant;
    {$ENDIF}
    {$ENDIF}
    {$ELSE}
      InternalType := dtString;
    {$ENDIF}
    DBTYPE_XML:
      InternalType := dtMSXML;
    DBTYPE_UDT:
      InternalType := dtVarBytes;
    else
      Result := False;
  end;
end;

function ConvertCRParamTypeToOLEDB(const InternalType: TParamDirection): word;
begin
  case InternalType of       
    pdUnknown:
      Result := DBPARAMIO_INPUT;
    pdInput:
      Result := DBPARAMIO_INPUT;
    pdOutput, pdResult:
      Result := DBPARAMIO_OUTPUT;
    pdInputOutput:
      Result := DBPARAMIO_INPUT + DBPARAMIO_OUTPUT;
    else
      Result := DBPARAMIO_INPUT;
  end;
end;

{$WARNINGS OFF}
function ConvertOLEDBParamTypeToCR(const Value: word): TParamDirection;
begin
  case Value of
    DBPARAMTYPE_INPUT:
      Result := pdInput;
    DBPARAMTYPE_INPUTOUTPUT:
      Result := pdInputOutput;
    DBPARAMTYPE_RETURNVALUE:
      Result := pdResult;
    else
      Assert(False, Format('Invalid value %d', [Value]));
  end;
end;
{$WARNINGS ON}

{$WARNINGS OFF}
function ConvertIsolationLevelToOLEDBIsoLevel(const Value: TIsolationLevel):Integer;
begin
  case Value of
    ilReadCommitted:
      Result := ISOLATIONLEVEL_READCOMMITTED;
    ilReadUnCommitted:
      Result := ISOLATIONLEVEL_READUNCOMMITTED;
    ilRepeatableRead:
      Result := ISOLATIONLEVEL_REPEATABLEREAD;
    ilIsolated:
      Result := ISOLATIONLEVEL_ISOLATED;
    ilSnapshot:
      Result := ISOLATIONLEVEL_SNAPSHOT;
    else
      Assert(False, Format('Invalid value %d', [Integer(Value)]));
  end;
end;
{$WARNINGS ON}

{$IFNDEF CLR}
const
  SizeOfFraction = 32;
{$ENDIF}

function DBNumericToBCD(Value: TDBNumeric): TBCD;
var
  i, j, k: integer;
  SignificantBytes: integer;
  Remainder, tmp: word;
{$IFDEF CLR}
  ResultCLR: array[0..33] of byte;
{$ENDIF}
  function FindStart: boolean;
  begin
    SignificantBytes := 16;
    while (SignificantBytes > 0) and (Value.Val[SignificantBytes - 1] = 0) do
      dec(SignificantBytes);
    Result := SignificantBytes > 0;
  end;

begin
  if Value.Sign = 0 then
    {$IFDEF CLR}ResultCLR[1]{$ELSE}Result.SignSpecialPlaces{$ENDIF} := (1 shl 7) + Value.Scale
  else
    {$IFDEF CLR}ResultCLR[1]{$ELSE}Result.SignSpecialPlaces{$ENDIF} := (0 shl 7) + Value.Scale;

  if not FindStart then begin
  {$IFDEF CLR}
    for i := 2 to 33 do
      ResultCLR[i] := 0;
    Result.FromBytes(ResultCLR);
  {$ELSE}
    System.FillChar(Result.Fraction, Length(Result.Fraction), 0);
  {$ENDIF}

  {$IFDEF CLR}ResultCLR[0]{$ELSE}Result.Precision{$ENDIF} := 8;       // if value is zero
  {$IFDEF CLR}ResultCLR[1]{$ELSE}Result.SignSpecialPlaces{$ENDIF} := 2;

  {$IFDEF CLR}
    Result.FromBytes(ResultCLR);
  {$ENDIF}
    exit;
  end;

  {$IFDEF CLR}
    for i := 2 to 33 do
      ResultCLR[i] := 0;
    Result.FromBytes(ResultCLR);
  {$ELSE}
    System.FillChar(Result.Fraction, Length(Result.Fraction), 0);
  {$ENDIF}

  k := SignificantBytes - 1;
  j := 31;
  while k >= 0 do begin
    Remainder := 0;
    for i := k downto 0 do begin
      tmp := (Byte(Value.Val[i]) + Remainder);
      Value.Val[i] := tmp div 100;
      Remainder := (tmp mod 100) shl 8;
    end;
    {$IFDEF CLR}ResultCLR[j + 2]{$ELSE}Result.Fraction[j]{$ENDIF} := (((Remainder shr 8) mod 10)) + (((Remainder shr 8) div 10) shl 4);
    if Value.Val[k] = 0 then
      dec(k);
    dec(j);
  end;

  {$IFDEF CLR}ResultCLR[0]{$ELSE}Result.Precision{$ENDIF} := Value.Precision;
  i := 31 - (Value.Precision div 2);
  j := i;

  if (Value.Precision mod 2) = 1 then begin
    while i <= 30 do begin
      {$IFDEF CLR}ResultCLR[i - j + 2]{$ELSE}Result.Fraction[i - j]{$ENDIF} :=
        ({$IFDEF CLR}ResultCLR[i + 2]{$ELSE}Result.Fraction[i]{$ENDIF} and $0f) shl 4 +
        {$IFDEF CLR}ResultCLR[i + 1 + 2]{$ELSE}Result.Fraction[i + 1]{$ENDIF} shr 4;
      inc(i);
    end;
    {$IFDEF CLR}ResultCLR[i - j + 2]{$ELSE}Result.Fraction[i - j]{$ENDIF} :=
      ({$IFDEF CLR}ResultCLR[i + 2]{$ELSE}Result.Fraction[i]{$ENDIF} and $0f) shl 4;
  end
  else begin
    while i <= 30 do begin
      {$IFDEF CLR}ResultCLR[i - j + 2]{$ELSE}Result.Fraction[i - j]{$ENDIF} :=
        {$IFDEF CLR}ResultCLR[i + 1 + 2]{$ELSE}Result.Fraction[i + 1]{$ENDIF};
      inc(i);
    end;
  end;

  for i := 31 - j + 1 to 31 do
    {$IFDEF CLR}ResultCLR[i + 2]{$ELSE}Result.Fraction[i]{$ENDIF} := 0;
{$IFDEF CLR}
  Result := TBcd.FromBytes(ResultCLR);
{$ENDIF}
end;

{$IFNDEF VER6P}
function IsBcdNegative(const Bcd: TBcd): Boolean;
begin
  Result := (Bcd.SignSpecialPlaces and (1 shl 7)) <> 0;
end;
{$ENDIF}

function BcdToDBNumeric(const Bcd: TBcd): TDBNumeric;
var
  Value: TBigIntegerAccessor;
  BytesCount: integer;
{$IFDEF CLR}
  i: integer;
{$ENDIF}  
begin
  Value := TBigIntegerAccessor.Create(nil, True);
  try
    Value.ClearData;
    TBigInteger.CreatePBigIntegerBCD(Value.BigIntegerPtr, Bcd);

    if Value.DataLength > (Length(Result.val) div SizeOfLongWord) then
      raise Exception.Create(SNumericOverflow);

    Result.precision := Bcd.Precision;

    Result.scale := Bcd.SignSpecialPlaces and $3F;

    if IsBcdNegative(Bcd) then
      Result.Sign := 0
    else
      Result.Sign := 1;

    BytesCount := Result.precision div 2;
    if (Result.precision mod 2) <> 0 then
      Inc(BytesCount);
  {$IFDEF CLR}
    for i := 0 to BytesCount - 1 do
      Result.val[i] := Marshal.ReadByte(Value.DataPtr, i);
  {$ELSE}
    FillChar(@Result.val[0], Length(Result.val), $00);
    CopyBuffer(Value.DataPtr, @Result.val[0], BytesCount);
  {$ENDIF}
  finally
    Value.Free;
  end;
end;

{$IFNDEF VER6P}
const
  MaxFMTBcdFractionSize = 64;
  SInvalidBcdValue = '%s is not a valid BCD value';
  
type
{ Exception classes }

  EBcdException = class(Exception);
  EBcdOverflowException = class(EBcdException);
  
procedure OverflowError(const Message: string);
begin
  raise EBcdOverflowException.Create(Message);
end;

procedure BcdErrorFmt(const Message, BcdAsString: string);
begin
  raise EBcdException.Create(Format(Message, [BcdAsString]));
end;

function FractionToStr(const pIn: PChar; count: SmallInt;
         DecPosition: ShortInt; Negative: Boolean;
         StartWithDecimal: Boolean): string;
var
  NibblesIn, BytesIn, DigitsOut: Integer;
  P, POut: PChar;
  Dot: Char;

  procedure AddOneChar(Value: Char);
  begin
    P[0] := Value;
    Inc(P);
    Inc(DigitsOut);
  end;
  procedure AddDigit(Value: Char);
  begin
    if ((DecPosition > 0) and (NibblesIn  = DecPosition)) or
       ((NibblesIn = 0) and StartWithDecimal) then
    begin
      if DigitsOut = 0 then AddOneChar('0');
      AddOneChar(Dot);
    end;
    if (Value > #0) or (DigitsOut > 0) then
      AddOneChar(Char(Integer(Value)+48));
    Inc(NibblesIn);
  end;

begin
  POut := AllocMem(Count + 3);  // count + negative/decimal/zero
  try
    Dot := DecimalSeparator;
    P := POut;
    DigitsOut := 0;
    BytesIn := 0;
    NibblesIn := 0;
    while NibblesIn < Count do
    begin
      AddDigit(Char(Integer(pIn[BytesIn]) SHR 4));
      if NibblesIn < Count then
        AddDigit(Char(Integer(pIn[BytesIn]) AND 15));
      Inc(BytesIn);
    end;
    while (DecPosition > 0) and (NibblesIn  > DecPosition) and (DigitsOut > 1) do
    begin
      if POut[DigitsOut-1] = '0' then
      begin
        Dec(DigitsOut);
        POut[DigitsOut] := #0;
      end else
        break;
    end;
    if POut[DigitsOut-1] = Dot then
      Dec(DigitsOut);
    POut[DigitsOut] := #0;
    SetString(Result, POut, DigitsOut);
  finally
    FreeMem(POut, Count + 2);
  end;
  if Result = '' then Result := '0'
  else if Negative then Result := '-' + Result;
end;

function BcdToStr(const Bcd: TBcd): string;
var
  NumDigits: Integer;
  pStart: PChar;
  DecPos: SmallInt;
  Negative: Boolean;
begin
  if (Bcd.Precision = 0) or (Bcd.Precision > MaxFMTBcdFractionSize) then
    OverFlowError(SBcdOverFlow)
  else
  begin
    Negative := Bcd.SignSpecialPlaces and (1 shl 7) <> 0;
    NumDigits := Bcd.Precision;
    pStart := pCHAR(@Bcd.Fraction);   // move to fractions
    // use lower 6 bits of iSignSpecialPlaces.
    if (Bcd.SignSpecialPlaces and 63) > 0 then
    begin
      DecPos := ShortInt(NumDigits - (Bcd.SignSpecialPlaces and 63));
    end else
      DecPos := NumDigits + 1;     // out of range
    Result := FractionToStr(pStart, NumDigits, DecPos, Negative,
           (NumDigits = Bcd.SignSpecialPlaces and 63));
    if Result[1] in ['0', '-'] then
      if (Result = '-0') or (Result = '0.0') or (Result = '-0.0') then Result := '0';
  end;
end;

function BcdToDouble(const Bcd: TBcd): Double;
begin
  Result := StrToFloat(BcdToStr(Bcd));
end;

function InvalidBcdString(PValue: PChar): Boolean; 
var
  Dot: Char;
  P: PChar;
begin
  Dot := DecimalSeparator;
  P := PValue;
  Result := False;
  while P^ <> #0 do
  begin
    if not (P^ in ['0'..'9', '-', Dot]) then
    begin
      Result := True;
      break;
    end;
    Inc(P);
  end;
end;

procedure StrToFraction(pTo: PChar; pFrom: PChar; count: SmallInt); pascal;
var
  Dot: Char;
begin
  Dot := DecimalSeparator;
  asm
   // From bytes to nibbles, both left aligned
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX
        MOV     ESI,pFrom  // move pFrom to ESI
        MOV     EDI,pTo    // move pTo to EDI
        XOR     ECX,ECX    // set ECX to 0
        MOV     CX,count   // store count in CX
        MOV     DL,0       // Flag: when to store
        CLD
@@1:    LODSB              // moves [ESI] into al
        CMP     AL,Dot
        JE      @@4
        SUB     AL,'0'
        CMP     DL,0
        JNE     @@2
        SHL     AL,4
        MOV     AH,AL
        JMP     @@3
@@2:    OR      AL,AH     // takes AH and ors in AL
        STOSB             // always moves AL into [EDI]
@@3:    NOT     dl        // flip all bits
@@4:    LOOP    @@1       // decrements cx and checks if it's 0
        CMP     DL,0      // are any bytes left unstored?
        JE      @@5
        MOV     AL,AH     // if so, move to al
        STOSB             // and store to [EDI]
@@5:    POP     EBX
        POP     EDI
        POP     ESI
  end;
end;

function TryStrToBcd(const AValue: string; var Bcd: TBcd): Boolean;
const
  spaceChars: set of Char = [ ' ', #6, #10, #13, #14];
  digits: set of Char = ['0'..'9'];
var
  Neg: Boolean;
  NumDigits, DecimalPos: Word;
  pTmp, pSource: PChar;
  Dot : Char;
begin
  Dot := DecimalSeparator;
  if InvalidBcdString(PChar(AValue)) then
  begin
    Result := False;
    exit;
  end;
  if (AValue = '0') or (AValue = '') then
  begin
    Result := True;
    Bcd.Precision := 8;
    Bcd.SignSpecialPlaces := 2;
    pSource := PChar(@Bcd.Fraction);
    System.FillChar(PSource^, SizeOf(Bcd.Fraction), 0);
    Exit
  end;
  Result := True;
  Neg := False;
  DecimalPos := Pos(Dot, AValue);

  pSource := pCHAR(AValue);
  { Strip leading whitespace }
  while (pSource^ in spaceChars) or (pSource^ = '0') do
  begin
    Inc(pSource);
    if DecimalPos > 0 then Dec(DecimalPos);
  end;

  { Strip trailing whitespace }
  pTmp := @pSource[ StrLen( pSource ) -1 ];
  while pTmp^ in spaceChars do
  begin
    pTmp^ := #0;
    Dec(pTmp);
  end;

  { Is the number negative? }
  if pSource^ = '-' then
  begin
    Neg := TRUE;
    if DecimalPos > 0 then Dec(DecimalPos);
  end;
  if (pSource^ = '-') or (pSource^ ='+') then
    Inc(pSource);

  { Clear structure }
  pTmp := pCHAR(@Bcd.Fraction);
  System.FillChar(pTmp^, SizeOf(Bcd.Fraction), 0);
  if (pSource[0] = '0') then
  begin
    Inc(PSource);  // '0.' scenario
    if DecimalPos > 0 then Dec(DecimalPos);
  end;
  NumDigits := StrLen(pSource);
  if (NumDigits > MaxFMTBcdFractionSize) then
  begin
    if (DecimalPos > 0) and (DecimalPos <= MaxFMTBcdFractionSize) then
      NumDigits := MaxFMTBcdFractionSize // truncate to 64
    else begin
      Bcd.Precision := NumDigits;
      Exit;
    end;
  end;

  if NumDigits > 0 then
    StrToFraction(pTmp, pSource, SmallInt(NumDigits))
  else begin
    Bcd.Precision := 10;
    Bcd.SignSpecialPlaces := 2;
  end;

  if DecimalPos > 0 then
  begin
    Bcd.Precision := Byte(NumDigits-1);
    if Neg then
      Bcd.SignSpecialPlaces := ( 1 shl 7 ) + (BYTE(NumDigits - DecimalPos))
    else
      Bcd.SignSpecialPlaces := ( 0 shl 7 ) + (BYTE(NumDigits - DecimalPos));
  end else
  begin
    Bcd.Precision := Byte(NumDigits);
    if Neg then
      Bcd.SignSpecialPlaces := (1 shl 7)
    else
      Bcd.SignSpecialPlaces := (0 shl 7);
  end;
end;

function StrToBcd(const AValue: string): TBcd;
var
  Success: Boolean;
begin
  Success := TryStrToBcd(AValue, Result);
  if not Success then
    BcdErrorFmt(SInvalidBcdValue, AValue);
end;

{$ENDIF}

function DBNumericToDouble(Value: TDBNumeric): double;
var
  Bcd: TBcd;
begin
  Bcd := DBNumericToBcd(Value);
  Result := BcdToDouble(Bcd);
end;

function DoubleToDBNumeric(Value: double; Precision, Scale: integer): TDBNumeric;
var
  Bcd: TBcd;
  Str: string;
begin
  //Str := FloatToStrF(Value, ffFixed, Precision, Scale);
  if (Precision = 0) and (Scale = 0) then
    Str := Format('%f', [Value])
  else
    Str := Format('%.' + IntToStr(Scale) + 'f', [Value]);
  Bcd := StrToBcd(Str);
  Result := BcdToDBNumeric(Bcd);
end;

function IsLargeDataTypeUsed(const FieldDesc: TFieldDesc): boolean;
begin
  Result := FieldDesc.DataType = dtBlob;
  if not Result then
    Result := ((FieldDesc.DataType = dtMemo) or (FieldDesc.DataType = dtWideMemo)) and (((not dtWide) and FieldDesc.SubDataType) = dtText);
  if not Result then
    Result := FieldDesc.DataType = dtMSXML;
end;

function IsLargeDataTypeUsed(const ParamDesc: TParamDesc): boolean;
begin
  Result :=
    (ParamDesc.GetDataType = dtBlob) or
    (ParamDesc.GetDataType = dtMemo) or
    (ParamDesc.GetDataType = dtWideMemo) or
    (ParamDesc.GetDataType = dtMSXML);
end;

function IsOutputLOB(ParamDesc: TParamDesc; ServerVersion, ClientVersion: integer): boolean;
begin
  Result := ((ParamDesc.GetDataType = dtMemo) or (ParamDesc.GetDataType = dtWideMemo)) and
    (ParamDesc.GetParamType in [pdOutput, pdInputOutput]) and
    (ServerVersion = 9) and
    (ClientVersion = 9);
end;

function GetProviderName(const Provider: TOLEDBProvider): string;
begin
  case Provider of
    prAuto, prSQL:
      Result := 'SQLOLEDB.1';
    prNativeClient:
      Result := 'SQLNCLI.1';
    prCompact:
      Result := 'MICROSOFT.SQLSERVER.MOBILE.OLEDB.3.0';
    else
      Assert(False);
      Result := '';
  end;
end;

function GetProvider(const ProviderName: string): TOLEDBProvider;
var
  Name: string;
begin
  Name := Uppercase(ProviderName);
  if Name = SSQLOLEDB then
    Result := prSQL
  else
    if Name = SSQLNativeClient then
      Result := prNativeClient
    else
      if Name = SSQLEverywhere then
        Result := prCompact
      else
        Result := prAuto;
end;

// FetchBlock support
function IsNeedFetchBlock(const FieldDesc: TFieldDesc; ServerVersion: integer): boolean; // Return True if field need to fetch into separate buffer
var
  DataType: word;
begin
  Assert(FieldDesc.FieldDescKind = fdkData, 'IsNeedFetchBlock ' + FieldDesc.Name);
  DataType := FieldDesc.DataType;
  case DataType of
    dtExtString, dtExtWideString, dtExtVarBytes, dtVariant:
      Result := True;
    dtMemo, dtWideMemo, dtMSXML:
      Result := not IsLargeDataTypeUsed(FieldDesc);
    dtFloat, dtBcd:
      Result := (ServerVersion = 3) and (TOLEDBFieldDesc(FieldDesc).OLEDBType = DBTYPE_NUMERIC);
    else
      Result := False;
  end;
end;

procedure IncFetchBlockOffset(var FetchBlockOffset: integer; const DataType: TDataType);
begin
  case DataType of
    dtExtString, dtExtWideString:
      Inc(FetchBlockOffset, OLE_DB_INDICATOR_SIZE + MaxNonBlobFieldLen + 2 {#0#0 terminator} + sizeof(UINT) {Length});
    dtMemo, dtWideMemo, dtMSXML:
      Inc(FetchBlockOffset, OLE_DB_INDICATOR_SIZE + MaxNonBlobFieldLen + 2 {#0#0 terminator});
    dtExtVarBytes:
      Inc(FetchBlockOffset, OLE_DB_INDICATOR_SIZE + MaxNonBlobFieldLen + sizeof(UINT) {Length});
    dtVariant:
      Inc(FetchBlockOffset, OLE_DB_INDICATOR_SIZE + sizeof(OleVariant));
    dtFloat, dtBcd:
      Inc(FetchBlockOffset, OLE_DB_INDICATOR_SIZE + SizeOfTDBNumeric);
    else
      Assert(False);
  end;
end;

// Brackets support
function IsBracketPresent(const Value: string): boolean;
var
  l: integer;
begin
  Assert(Pos('.', Value) = 0, 'In func IsBracketPresent delimited values not allowed');

  l := Length(Value);
  if (l <= 1) then
    Result := False
  else
    Result := ((Value[1] = '"') and (Value[l] = '"')) or
              ((Value[1] = '[') and (Value[l] = ']'));
end;

function Unbracket(const Value: string): string;
begin
  if IsBracketPresent(Value) then
    Result := Copy(Value, 2, length(Value) - 2)
  else
    Result := Value;
end;

function IsBracketNeed(Value: string): boolean;
var
  i: integer;
begin
  Assert(Pos('.', Value) = 0, 'In func IsBracketNeeds delimited values not allowed');

  Value := Unbracket(Value);
  Result := False;
  for i := 1 to Length(Value) do
    if i = 1 then
      case Value[i] of
        'a'..'z', 'A'..'Z', '_', '@', '#':; // NoBracketableStartingChars = ['a'..'z', 'A'..'Z', '_', '@', '#'];
        else
        begin
          Result := True;
          Exit;
        end;
      end
    else
      case Value[i] of
        'a'..'z', 'A'..'Z', '_', '@', '#', '0'..'9', '$':; // NoBracketableChars = NoBracketableStartingChars + ['0'..'9', '$'];
        else
        begin
          Result := True;
          Exit;
        end;
      end;
end;

// Rules see in MSDN (mk:@MSITStore:D:\Program%20Files\Microsoft%20Visual%20Studio\MSDN\2003JAN\1033\acdata.chm::/ac_8_con_03_89rn.htm,
//                    mk:@MSITStore:D:\Program%20Files\Microsoft%20Visual%20Studio\MSDN\2003JAN\1033\acdata.chm::/ac_8_con_03_6e9e.htm)
function BracketIfNeed(const Value: string): string;
begin
  Result := BracketIfNeed(Value, Char('['), Char(']'));
end;

function BracketIfNeed(const Value: string; const LeftQ: char; const RightQ: char): string;
var
  i: integer;
begin
  i := Pos('.', Value);
  if i <> 0 then
    Result := BracketIfNeed(Copy(Value, 1, i - 1)) + '.' + BracketIfNeed(Copy(Value, i + 1, Length(Value) - i))
  else
    if not IsBracketPresent(Value) and
      IsBracketNeed(Value) then
      Result := LeftQ + Value + RightQ
    else
      Result := Value;
end;

function UnbracketIfPossible(const Value: string; const NeedSl: boolean; var sl: String {Result parts in reverse order delimited by slDelimiter}): string; overload;
var
{$IFNDEF CLR}
  p: PChar;
{$ENDIF}
  DotPos: integer;
  l: integer;
begin
  Result := Value;
  l := Length(Value);
{$IFDEF CLR}
  DotPos := Value.IndexOf('.');
{$ELSE}
  p := StrScan(PChar(Value), '.'); // i := Pos('.', Value);
  if p = nil then
    DotPos := -1
  else
    DotPos := p - PChar(Value);
{$ENDIF}

  if (l < 3) then begin
    if (DotPos = -1) and NeedSl then
      sl := Result + slDelimiter + sl;
    Exit;
  end;

  if DotPos <> -1 then begin
{    if DotPos = 0 then
      Result := UnbracketIfPossible(Copy(Value, DotPos + 2, l - DotPos - 1), sl)
    else}
      Result := UnbracketIfPossible(Copy(Value, 1, DotPos), sl) + '.' + UnbracketIfPossible(Copy(Value, DotPos + 2, l - DotPos - 1), sl)
  end
  else
  begin
    if IsBracketPresent(Value) and
      not IsBracketNeed(Value) then
      Result := Copy(Value, 2, l - 2)
    else
      Result := Value;
    if NeedSl then
      sl := Result + slDelimiter + sl;
  end;
end;

function UnbracketIfPossible(const Value: string): string; overload;
var
  sl: string;
begin
  Result := UnbracketIfPossible(Value, False, sl);
end;

function UnbracketIfPossible(const Value: string; var sl: string {Result parts in reverse order delimited by slDelimiter}): string; overload;
begin
  Result := UnbracketIfPossible(Value, True, sl);
end;

{$IFDEF CLR}
function UnbracketIfPossible(const Value: string; out DataBase: string; out Owner: string; out ObjName: string): string;
const
  d: array[0..0] of char = (slDelimiter);
var
  sl: string;
  i: integer;

  SubSl: array of string;
begin
  sl := '';
  UnbracketIfPossible(Value, sl);
  if sl = '' then
    Exit;

  SubSl := sl.Split(d);
  i := Length(SubSl);
  if i > 0 then begin
    ObjName := SubSl[0];
    if i > 1 then begin
      Owner := SubSl[1];
      if i > 2 then
        Database := SubSl[2];
    end;
  end;
end;
{$ELSE}
function UnbracketIfPossible(const Value: string; out DataBase: string; out Owner: string; out ObjName: string): string;
  function GetSubStr(var Src: PChar): string;
  var
    p: PChar;
    l: integer;
  begin
    p := StrScan(Src, slDelimiter);
    if p = nil then
      Result := ''
    else
    begin
      l := p - Src;
      SetLength(Result, l);
      Move(Src^, PChar(Result)^, l);
      Src := Src + l + 1{slDelimiter};
    end;
  end;

var
  sl: string;
  p: PChar;
begin
  sl := '';
  UnbracketIfPossible(Value, sl);
  if sl = '' then
    Exit;

  p := PChar(sl);
  ObjName := GetSubStr(p);
  Owner := GetSubStr(p);
  Database := GetSubStr(p);
end;
{$ENDIF}

function GenerateTableName(const CatalogName: string;
  const SchemaName: string;
  const TableName: string;
  const DefaultCatalogName: string): string;
begin
  if (CatalogName <> '') and not SameText(CatalogName, DefaultCatalogName) then
    Result := Format('%s.%s.%s',
      [BracketIfNeed(CatalogName),
       BracketIfNeed(SchemaName),
       BracketIfNeed(TableName)])
  else
  if SchemaName <> '' then
    Result := Format('%s.%s',
      [BracketIfNeed(SchemaName),
       BracketIfNeed(TableName)])
  else
    Result := Format('%s',
      [BracketIfNeed(TableName)]);
end;

function ChangeDecimalSeparator(const Value: Variant): string;
var
  i: integer;
begin
{$IFDEF VER6P}
  Result := Value;
{$ELSE}
  if TVarData(Value).VType = varSingle then
    Result := FloatToStr(TVarData(Value).VSingle)
  else
  if TVarData(Value).VType = varDouble then
    Result := FloatToStr(TVarData(Value).VDouble)
  else
    Result := Value;
{$ENDIF}
  if DecimalSeparator <> '.' then begin
    i := 2;
    while i < Length(Result) do begin
      if Result[i] = DecimalSeparator then begin
        Result[i] := '.';
        Break;
      end;
      Inc(i);
    end;
  end;
end;

function GetParamNameWODog(const ParamName: string): string;
begin
  if (ParamName <> '') and (ParamName[1] = '@') then
    Result := Copy(ParamName, 2, 1000)
  else
    Result := ParamName;
end;

{$IFDEF CLR}
procedure QueryIntf(Source: IUnknown; const IID: TInterfaceRef; out Intf); // OLE QueryInterface analog
begin
  Assert(Source <> nil);
  Intf := nil;
  if Source is IID then
    Intf := Source as IID
  else
    raise EOLEDBError.Create(E_NOINTERFACE, 'QueryInterface failed');
end;

{$ELSE}

procedure QueryIntf(Source: IUnknown; const IID: TGuid; out Intf); // OLE QueryInterface analog
begin
  Assert(Source <> nil);
  if Source.QueryInterface(IID, Intf) <> S_OK then
    raise EOLEDBError.Create(E_NOINTERFACE, 'QueryInterface failed');
end;
{$ENDIF}

function Min(const A, B: Integer): Integer;
begin
  if A < B then
    Result := A
  else
    Result := B;
end;

{ TOLEDBParamDesc }

function TOLEDBParamDesc.GetOLEDBType: DBTYPE;
begin
  Result := FOLEDBType;
end;

procedure TOLEDBParamDesc.SetOLEDBType(Value: DBTYPE);
begin
  FOLEDBType := Value;
end;

function TOLEDBParamDesc.GetUseDefaultValue: boolean;
begin
  Result := FUseDefaultValue;
end;

procedure TOLEDBParamDesc.SetUseDefaultValue(Value: boolean);
begin
  FUseDefaultValue := Value;
end;

function TOLEDBParamDesc.GetValue: variant;
begin
  if not GetNull
    and not VarIsEmpty(FData)
    and not VarIsNull(FData)
  {$IFDEF CLR}
    and (FData is TSharedObject)
  {$ELSE}
    and (TVarData(FData).VType = varByRef)
  {$ENDIF}
  then begin
    if GetAsBlobRef.IsUnicode then
      Result := GetAsBlobRef.AsWideString
    else
      Result := GetAsBlobRef.AsString;
  end
  else
    Result := inherited GetValue;
end;

function TOLEDBParamDesc.GetAsBlobRef: TBlob;
begin
  Result := TBlob({$IFDEF CLR}FData{$ELSE}TVarData(FData).VPointer{$ENDIF});
end;

procedure TOLEDBParamDesc.SetNull(const Value: boolean);
begin
  FIsNull := Value;
  if not((DataType = dtBlob) or (DataType = dtMemo) or (DataType = dtWideMemo)) then
    FData := Unassigned;
end;
{ TExecuteThread }

procedure TExecuteThread.InternalExecute;
begin
  if Assigned(FRunMethod) then
    FRunMethod;
end;

{ TOLEDBThreadWrapper }

procedure TOLEDBThreadWrapper.DoException(E: Exception);
begin
  if E is EOLEDBError then begin
    Assert(FException = nil);
    FException := EOLEDBError.Create(EOLEDBError(E).ErrorCode, EOLEDBError(E).Message);
    EOLEDBError(FException).Assign(EOLEDBError(E));
  end
  else
    inherited;
end;

{ TOLEDBStream }

constructor TOLEDBStream.Create(Blob: TBlob {to avoid data copy}; StreamList: TList);
begin
  inherited Create;

  {$IFDEF DEBUG} Inc(StreamCnt); {$ENDIF}

  FBlob := Blob;
  FPosInBlob := 0;

  FSize := Blob.Size;
  FStreamList := StreamList;

  if FStreamList <> nil then
    FStreamList.Add(Self);
end;

destructor TOLEDBStream.Destroy;
begin
  if FStreamList <> nil then
  {$IFDEF CLR}
    FStreamList.Remove(Self);
  {$ELSE}
    FStreamList.Remove(pointer(Self));
  {$ENDIF}

  inherited;
  {$IFDEF DEBUG} Dec(StreamCnt); {$ENDIF}
end;

function TOLEDBStream.Read(
  pv: IntPtr;
  cb: Longint;
  pcbRead: PLongint
): HResult;
var
  cbSrcReadBytes: longint;
  cbDstWriteBytes: longint;

begin
  try
    cbSrcReadBytes := Min(Integer(FSize - FPosInBlob), cb);
    cbDstWriteBytes := FBlob.Read(FPosInBlob, cbSrcReadBytes, pv);
    Inc(FPosInBlob, cbDstWriteBytes);

    if pcbRead <> nil then
      Marshal.WriteInt32(pcbRead, cbDstWriteBytes);
    Result := S_OK;
  except
    Result := S_FALSE;
  end;
end;

function TOLEDBStream.Write(
  pv: IntPtr;
  cb: Longint;
  pcbWritten: PLongint
): HResult;
begin
  try
    FBlob.Write(FPosInBlob, cb, pv);
    Inc(FPosInBlob, cb);
    Inc(FSize, cb);

    if pcbWritten <> nil then
      Marshal.WriteInt32(pcbWritten, cb);
    Result := S_OK;
  except
    Result := S_FALSE;
  end;
end;

{ TBigIntegerAccessor }

constructor TBigIntegerAccessor.Create(BigIntegerPtr: PBigInteger; Allocate: boolean);
begin
  inherited Create;

  if not Allocate then
    FBigIntegerPtr := BigIntegerPtr
  else begin
    FBigIntegerPtr := Marshal.AllocHGlobal(SizeOfRBigInteger);
    FSelfAllocated := True;
  end;
end;

destructor TBigIntegerAccessor.Destroy;
begin
  if FSelfAllocated then
    Marshal.FreeHGlobal(FBigIntegerPtr);

  inherited;
end;

procedure TBigIntegerAccessor.SetBigIntegerPtr(Value: PBigInteger);
begin
  if FSelfAllocated then begin
    Marshal.FreeHGlobal(FBigIntegerPtr);
    FSelfAllocated := False;
  end;
  FBigIntegerPtr := Value;
end;

function TBigIntegerAccessor.GetDataLength: integer;
begin
  Result := Marshal.ReadInt32(FBigIntegerPtr, MaxLength * SizeOfLongWord)
end;

procedure TBigIntegerAccessor.SetDataLength(Value: integer);
begin
  Marshal.WriteInt32(FBigIntegerPtr, MaxLength * SizeOfLongWord, Value);
end;

function TBigIntegerAccessor.GetDataPtr: IntPtr;
begin
  Result := FBigIntegerPtr;
end;

function TBigIntegerAccessor.GetData(Index: integer): LongWord;
begin
  Result := LongWord(Marshal.ReadInt32(FBigIntegerPtr, Index * SizeOfLongWord));
end;

procedure TBigIntegerAccessor.SetData(Index: integer; Value: LongWord);
begin
  Marshal.WriteInt32(FBigIntegerPtr, Index * SizeOfLongWord, Integer(Value));
end;

procedure TBigIntegerAccessor.ClearData;
var
  i: integer;
begin
  for i := 0 to MaxLength - 1 do
    Data[i] := 0;
end;

function TBigIntegerAccessor.GetBigInteger: RBigInteger;
{$IFDEF CLR}
var
  i: integer;
{$ENDIF}  
begin
{$IFDEF CLR}
  Result.DataLength := GetDataLength;
  for i := 0 to MaxLength - 1 do
    Result.Data[i] := Data[i];
  //Result := RBigInteger(Marshal.PtrToStructure(FBigIntegerPtr, TypeOf(RBigInteger)));
{$ELSE}
  Result := RBigInteger(FBigIntegerPtr^);
{$ENDIF}
end;

procedure TBigIntegerAccessor.SetBigInteger(const Value: RBigInteger);
{$IFDEF CLR}
var
  i: integer;
{$ENDIF}
begin
  Assert(FBigIntegerPtr <> nil);
{$IFDEF CLR}
  DataLength := Value.DataLength;
  for i := 0 to MaxLength - 1 do
    Data[i] := Value.Data[i];
  //Marshal.StructureToPtr(TObject(Value), FBigIntegerPtr, False);
{$ELSE}
  RBigInteger(FBigIntegerPtr^) := Value;
{$ENDIF}  
end;

{ TBigInteger }

class procedure TBigInteger.CreatePBigIntegerPtr(PBigInt: PBigInteger);
var
  BigIntAcc: TBigIntegerAccessor;
begin
  BigIntAcc := TBigIntegerAccessor.Create(PBigInt, False);
  try
    BigIntAcc.DataLength := 1;
    BigIntAcc.ClearData;
  finally
    BigIntAcc.Free;
  end;
end;

class procedure TBigInteger.CreateRBigIntegerStruct(var BigInt: RBigInteger);
{$IFDEF CLR}
var
  i: integer;
{$ENDIF}  
begin
  BigInt.DataLength := 1;
{$IFDEF CLR}
  for i := 0 to MaxLength - 1 do
    BigInt.Data[i] := 0;
{$ELSE}
  MemUtils.FillChar(@BigInt.Data[0], MaxLength * SizeOfLongWord, $00);
{$ENDIF}    
end;

class procedure TBigInteger.CreatePBigInteger64(PBigInt: PBigInteger; Value: Int64);
var
  BigIntAcc: TBigIntegerAccessor;
begin
  BigIntAcc := TBigIntegerAccessor.Create(PBigInt, False);
  try
    BigIntAcc.ClearData;
    BigIntAcc.DataLength := 0;

    while (Value <> 0) and (BigIntAcc.DataLength < MaxLength) do begin
      BigIntAcc.Data[BigIntAcc.DataLength] := LongWord(Value and $FFFFFFFF);
      Value := Value shr 32;
      BigIntAcc.DataLength := BigIntAcc.DataLength + 1;
    end;

    if (Value <> 0) or (LongWord(BigIntAcc.Data[MaxLength - 1] and $80000000) <> 0) then
      raise Exception.Create('Positive overflow in constructor.');

    if BigIntAcc.DataLength = 0 then
      BigIntAcc.DataLength := 1;
  finally
    BigIntAcc.Free;
  end;
end;

class procedure TBigInteger.CreatePBigIntegerBCD(PBigInt: PBigInteger; const Value: TBcd);
var
  Multiplier: TBigIntegerAccessor;
  Result, Tmp: TBigIntegerAccessor;
  BigIntAcc: TBigIntegerAccessor;
  Radix: integer;
  posVal: Integer;
  Val: TBytes;
  i: Integer;
  k: integer;
begin
  Multiplier := nil;
  Result := nil;
  Tmp := nil;
  BigIntAcc := nil;
  try
    Multiplier := TBigIntegerAccessor.Create(nil, True);
    Result := TBigIntegerAccessor.Create(nil, True);
    Tmp := TBigIntegerAccessor.Create(nil, True);
    BigIntAcc := TBigIntegerAccessor.Create(PBigInt, False);

    TBigInteger.CreatePBigInteger64(Multiplier.BigIntegerPtr, 1);
    TBigInteger.CreatePBigIntegerPtr(Result.BigIntegerPtr);

    SetLength(Val, SizeOfFraction);
  {$IFDEF CLR}
    for i := 0 to SizeOfFraction - 1 do
      Val[i] := Value.Fraction[i];
  {$ELSE}
    MemUtils.CopyBuffer(@Value.Fraction[0], @Val[0], Length(Val));
  {$ENDIF}

    k := (Value.Precision div 2);
    if (Value.Precision mod 2) = 0 then
      Dec(k);

    for i := k downto 0 do begin
      Radix := 100;
      if (i = k) then begin
        if (Value.Precision mod 2) = 0 then
          posVal := (val[i] and $0F) + ((val[i] and $F0) shr 4) * 10
        else begin
          posVal := (val[i] and $F0) shr 4;
          Radix := 10;
        end;
      end
      else
        posVal := (val[i] and $0F) + ((val[i] and $F0) shr 4) * 10;

      Assert(posVal < Radix);

      Tmp.BigInteger := Mul64(Multiplier.BigIntegerPtr, posVal);
      Result.BigInteger := Add(Result.BigIntegerPtr, Tmp.BigIntegerPtr);

      if (i - 1) >= 0 then
        Multiplier.BigInteger := Mul64(Multiplier.BigIntegerPtr, Radix);
    end;

    BigIntAcc.ClearData;
    for i := 0 to Result.DataLength - 1 do
      BigIntAcc.Data[i] := Result.Data[i];

    BigIntAcc.DataLength := Result.DataLength;
  finally
    Multiplier.Free;
    Result.Free;
    Tmp.Free;
    BigIntAcc.Free;
  end;
end;

class function TBigInteger.Add(PBigInt1: PBigInteger; PBigInt2: PBigInteger): RBigInteger;
var
  carry, sum: Int64;
  i, lastPos: Integer;
  BigInt1Acc, BigInt2Acc: TBigIntegerAccessor;
begin
  BigInt1Acc := TBigIntegerAccessor.Create(PBigInt1, False);
  BigInt2Acc := TBigIntegerAccessor.Create(PBigInt2, False);
  try
    TBigInteger.CreateRBigIntegerStruct(Result);

    if BigInt1Acc.DataLength > BigInt2Acc.DataLength then
      Result.DataLength := BigInt1Acc.DataLength
    else
      Result.dataLength := BigInt2Acc.DataLength;
    carry := 0;
    for i := 0 to Result.DataLength - 1 do begin
      sum := Int64(BigInt1Acc.data[i]) + Int64(BigInt2Acc.data[i]) + carry;
      carry := sum shr 32;
      Result.Data[i] := LongWord(sum and $FFFFFFFF);
    end;

    if (carry <> 0) and (Result.DataLength < MaxLength) then begin
      Result.Data[Result.DataLength] := LongWord(carry);
      Result.DataLength := Result.DataLength + 1;
    end;

    while (Result.DataLength > 1) and (Result.Data[Result.DataLength - 1] = 0) do
      Result.DataLength := Result.DataLength - 1;

    // overflow check
    lastPos := maxLength - 1;
    if (longword(BigInt1Acc.Data[lastPos] and $80000000) = longword(BigInt2Acc.Data[lastPos] and $80000000)) and
       (longword(Result.Data[lastPos] and $80000000) <> longword(BigInt1Acc.Data[lastPos] and $80000000)) then
    begin
      raise Exception.Create('overflow!');
    end;
  finally
    BigInt1Acc.Free;
    BigInt2Acc.Free;
  end;
end;

class function TBigInteger.NEGATEPtr(PBigInt: PBigInteger): RBigInteger;
var
  carry, val: Int64;
  i, index: Integer;
  BigIntAcc: TBigIntegerAccessor;
begin
  BigIntAcc := TBigIntegerAccessor.Create(PBigInt, False);
  try
    // handle neg of zero separately since it'll cause an overflow
    // if we proceed.
    if (BigIntAcc.DataLength = 1) and (BigIntAcc.Data[0] = 0) then begin
      TBigInteger.CreateRBigIntegerStruct(Result);
      Exit;
    end;

    // 1's complement
    for i := 0 to MaxLength - 1 do
      Result.Data[i] := LongWord(not(BigIntAcc.Data[i]));

    // add one to result of 1's complement
    carry := 1;
    index := 0;

    while (carry <> 0) and (index < maxLength) do begin
      val := Int64(Result.Data[index]);
      Inc(val);

      Result.Data[index] := LongWord(val and $FFFFFFFF);
      carry := val shr 32;
      Inc(index);
    end;

    if LongWord(BigIntAcc.Data[maxLength-1] and $80000000) = LongWord(Result.Data[MaxLength-1] and $80000000) then
      raise Exception.Create('Overflow in negation.');

    Result.DataLength := MaxLength;

    while (Result.DataLength > 1) and (Result.Data[Result.DataLength - 1] = 0) do
      Result.DataLength := Result.DataLength - 1;
  finally
    BigIntAcc.Free;
  end;
end;

class function TBigInteger.NEGATE(const BigInt: RBigInteger): RBigInteger;
{$IFDEF CLR}
var
  BigIntAcc: TBigIntegerAccessor;
{$ENDIF}  
begin
{$IFDEF CLR}
  BigIntAcc := TBigIntegerAccessor.Create(nil, True);
  try
    BigIntAcc.BigInteger := BigInt; 
    Result := NEGATEPtr(BigIntAcc.BigIntegerPtr);
  finally
    BigIntAcc.Free;
  end;
{$ELSE}
  Result := NEGATEPtr(@BigInt);
{$ENDIF}
end;

class function TBigInteger.Mul(PBigInt1: PBigInteger; PBigInt2: PBigInteger): RBigInteger;
var
  BigInt1Acc, BigInt2Acc: TBigIntegerAccessor;
  nbi1, nbi2: RBigInteger;
  mcarry, val: Int64;
  bi1Neg, bi2Neg, isMaxNeg: Boolean;
  b1, b2: Longword;
  i, j, k, lastPos: Integer;
begin
  BigInt1Acc := TBigIntegerAccessor.Create(PBigInt1, False);
  BigInt2Acc := TBigIntegerAccessor.Create(PBigInt2, False);
  try
    lastPos := maxLength - 1;
    bi1Neg := false;
    bi2Neg := false;

    // take the absolute value of the inputs
    try
      if longword(BigInt1Acc.Data[lastPos] and $80000000) <> 0 then begin
        bi1Neg := true;
        nbi1 := NEGATEPtr(PBigInt1);
      end
      else
        //nbi1 := bi1^;
        nbi1 := BigInt1Acc.BigInteger;


      if longword(BigInt2Acc.Data[lastPos] and $80000000) <> 0 then begin
        bi2Neg := true;
        nbi2 := NEGATEPtr(PBigInt2);
      end
      else
        //nbi2 := bi2^;
        nbi2 := BigInt2Acc.BigInteger;
    except
    end;

    TBigInteger.CreateRBigIntegerStruct(Result);

    // multiply the absolute values
    for i := 0 to nbi1.dataLength - 1 do begin
      if nbi1.data[i] = 0 then
        continue;

      mcarry := 0;
      k := i;
      for j := 0 to nbi2.dataLength - 1 do begin
        // k = i + j
  //      val := Int64(nbi1.data[i]) * Int64(nbi2.data[j]);
        b1 := nbi1.data[i];
        b2 := nbi2.data[j];
      {$IFDEF CLR}
        val := Int64(b1) * Int64(b2);
      {$ELSE}
        asm
          MOV    EAX, b1
          MUL    b2
          MOV    dword ptr [val+$00], EAX
          MOV    dword ptr [val+$04], EDX
        end;
      {$ENDIF}
        val := val + Int64(result.data[k]) + mcarry;

        result.data[k] := longword(val and $FFFFFFFF);
        mcarry := val shr 32;
        Inc(k);
      end;

      if mcarry <> 0 then
        result.data[i + nbi2.dataLength] := longword(mcarry);
    end;

    result.dataLength := nbi1.dataLength + nbi2.dataLength;
    if result.dataLength > maxLength then
      result.dataLength := maxLength;

    while (result.dataLength > 1) and (result.data[result.dataLength - 1] = 0) do
      result.dataLength := result.dataLength - 1;

    // overflow check (result is -ve)
    if longword(result.data[lastPos] and $80000000) <> 0 then begin
      if (bi1Neg <> bi2Neg) and (result.data[lastPos] = $80000000) then begin
        // handle the special case where multiplication produces
        // a max negative number in 2's complement.

        if result.dataLength = 1 then
          Exit
        else begin
          isMaxNeg := true;
          i := 0;
          while (i < result.dataLength - 1) and isMaxNeg do begin
            if result.data[i] <> 0 then
              isMaxNeg := false;
            Inc(i);
          end;

          if isMaxNeg then
            Exit;
        end;
      end;

      raise Exception.Create('Multiplication overflow.');
    end;

    // if input has different signs, then result is -ve
    if bi1Neg <> bi2Neg then
      Result := NEGATE(Result);
  finally
    BigInt1Acc.Free;
    BigInt2Acc.Free;
  end;
end;

class function TBigInteger.Mul64(PBigInt1: PBigInteger; value: Int64): RBigInteger;
var
  BigIntAcc2: TBigIntegerAccessor;
begin
  BigIntAcc2 := TBigIntegerAccessor.Create(nil, True);
  try
    TBigInteger.CreatePBigInteger64(BigIntAcc2.BigIntegerPtr, Value);
    Result := Mul(PBigInt1, BigIntAcc2.BigIntegerPtr);
  finally
    BigIntAcc2.Free;
  end;
end;

{ TOLEDBConnection }

constructor TOLEDBConnection.Create;
begin
  inherited;

  FIsolationLevel := ilReadCommitted;
  FCommand := nil;

  FAuthentication := auServer;
  FProvider := prSQL;

  FDatabase := '';
  FQuotedIdentifier := True;
  FPacketSize := DefaultPacketSize;
  FAutoTranslate := True;
  FConnectionTimeout := DefaultConnectionTimeout;
  FOldPassword := '';
  FMaxDatabaseSize := 4091;
  FFailoverPartner := '';
end;

destructor TOLEDBConnection.Destroy;
begin
  Disconnect;
  FCommand.Free;
  inherited;
end;

procedure TOLEDBConnection.ReleaseInterfaces;
begin
  FISessionProperties := nil;
  FITransactionLocal := nil;

  FIDBInitialize := nil;
  FIDBProperties := nil;
  FIDBCreateSession := nil;
end;

{$IFDEF SQL_TRANSACTION}
procedure TOLEDBConnection.StartTransaction;
begin
{$WARNINGS OFF}
  case FIsolationLevel of
    ilReadCommitted:
      ExecSQL('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
    ilReadUnCommitted:
      ExecSQL('SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
    ilRepeatableRead:
      ExecSQL('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
    ilIsolated:
      ExecSQL('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE ');
    else
      Assert(False, Format('Invalid value %d', [Integer(FIsolationLevel)]));
  end;
{$WARNINGS ON}
  ExecSQL('BEGIN TRAN');
end;

procedure TOLEDBConnection.Commit;
begin
  ExecSQL('COMMIT');
end;

procedure TOLEDBConnection.Rollback;
begin
  ExecSQL('ROLLBACK');
end;

{$ELSE}
{$IFDEF CLR}
procedure TOLEDBConnection.StartTransaction;
var
  pulTransactionLevel: PUINT;
begin
  if FITransactionLocal <> nil then begin
    pulTransactionLevel := Marshal.AllocHGlobal(4);
    try
      Check(FITransactionLocal.StartTransaction(ConvertIsolationLevelToOLEDBIsoLevel(FIsolationLevel), 0, nil, pulTransactionLevel), Component);
    finally
      Marshal.FreeHGlobal(pulTransactionLevel);
    end;
  end;
end;
{$ELSE}
procedure TOLEDBConnection.StartTransaction;
var
  ulTransactionLevel: UINT;
begin
  if FITransactionLocal <> nil then
    Check(FITransactionLocal.StartTransaction(ConvertIsolationLevelToOLEDBIsoLevel(FIsolationLevel), 0, nil, @ulTransactionLevel), Component);
end;
{$ENDIF}

procedure TOLEDBConnection.Commit;
begin
  if FITransactionLocal <> nil then
    Check(ITransaction(FITransactionLocal).Commit(False{WAR may be troubles with server cursors}, XACTTC_SYNC, 0), Component);
end;

procedure TOLEDBConnection.Rollback;
begin
  if FITransactionLocal <> nil then
    Check(ITransaction(FITransactionLocal).Abort(nil, False, False), Component);
end;
{$ENDIF}

procedure TOLEDBConnection.Check(const Status: HRESULT; Component: TObject);
begin
  if Status <> S_OK then
    OLEDBError(Status, Component);
end;

procedure TOLEDBConnection.OLEDBError(const ErrorCode: HRESULT; Component: TObject);
{$IFDEF CLR}
  function MarshalErrInfo(Err: PSSERRORINFO): SSERRORINFO;
  begin
    Result := SSERRORINFO(Marshal.PtrToStructure(Err, TypeOf(SSERRORINFO)));
  end;
{$ELSE}
  function MarshalErrInfo(Err: PSSERRORINFO): SSERRORINFO;
  begin
    Result.pwszMessage := Err.pwszMessage;
    Result.pwszServer := Err.pwszServer;
    Result.pwszProcedure := Err.pwszProcedure;
    Result.lNative := Err.lNative;
    Result.bState := Err.bState;
    Result.bClass := Err.bClass;
    Result.wLineNumber := Err.wLineNumber;
  end;
{$ENDIF}

var
  pIErrorInfoAll: IErrorInfo;
  // pIErrorInfo: IErrorInfo;
  pIErrorRecords: IErrorRecords;
  pISQLServerErrorInfo: ISQLServerErrorInfo;
  pssErrInfo: PSSERRORINFO;
  ssErrInfo: SSERRORINFO;
  pStrBuf: IntPtr; // pWideChar;

  Msg: WideString;
  s1: WideString;

  i, RecordCount: Cardinal;
  //pIMalloc: IMalloc;

  Err: EOLEDBError;
  Fail: boolean;

  // Descr, Src: WideString;

  ErrInfo: ERRORINFO;
  iu: IUnknown;

  // Buffer
  ssErrInfoBuf: SSERRORINFO;

begin
  if (ErrorCode = 0) and not Assigned(FOnInfoMessage) then
    Exit;

  s1 := '';
  Err := nil;
  RecordCount := 0;
  //CoGetMalloc(1, pIMalloc);

  if GetErrorInfo(0, pIErrorInfoAll) = S_OK then begin
    pIErrorInfoAll.GetDescription(Msg);
  {$IFDEF CLR}
    pIErrorRecords := pIErrorInfoAll as IErrorRecords;
  {$ELSE}
    pIErrorInfoAll.QueryInterface(IID_IErrorRecords, pIErrorRecords);
  {$ENDIF}
    pIErrorRecords.GetRecordCount(RecordCount);

    if RecordCount > 0 then begin /// i and RecordCount is unigned int!
      for i := RecordCount - 1 downto 0 do begin// Ignore all error messages without last
        if (pIErrorRecords.GetCustomErrorObject(i, IID_ISQLServerErrorInfo, iu) = S_OK) then
          pISQLServerErrorInfo := ISQLServerErrorInfo(iu);
          iu := nil;
          if (pISQLServerErrorInfo <> nil) and
            (pISQLServerErrorInfo.GetErrorInfo(pssErrInfo, pStrBuf) = S_OK) and
            (pssErrInfo <> nil) then begin
          {$IFDEF CLR}
            ssErrInfo := SSERRORINFO(Marshal.PtrToStructure(pssErrInfo, TypeOf(SSERRORINFO)));
            if s1 = '' then begin
              ssErrInfoBuf := ssErrInfo;
              s1 := ssErrInfo.pwszMessage;
            end
            else
              s1 := ssErrInfo.pwszMessage + #$D#$A + s1;
          {$ELSE}
            if s1 = '' then begin
              ssErrInfoBuf := MarshalErrInfo(pssErrInfo);
              s1 := pssErrInfo^.pwszMessage;
            end
            else
              s1 := pssErrInfo^.pwszMessage + WideString(#$D#$A) + s1;
          {$ENDIF}
            FreeCoMem(pStrBuf);
            FreeCoMem(pssErrInfo);
            //pIMalloc.Free(pStrBuf);
            //pIMalloc.Free(pssErrInfo);
          end;
      end;
      if s1 <> '' then
        Err := EMSError.Create(ssErrInfoBuf, ErrorCode, s1);
    end;
  end;

  if Err = nil then // this is OLE DB error. As example - repeated Connection.Rollback or non-convergence types of field and parameter
  begin
    if ErrorCode = 0 then
      Exit; // No error and no message

    if Msg = '' then
      Msg := Format(SOLEDBError, [ErrorCode]);
    if ErrorCode = CO_E_NOTINITIALIZED then
      Msg := Msg + '.'#$D#$A'CoInitialize has not been called.';

    Err := EOLEDBError.Create(ErrorCode, Msg);
  end;

{$IFNDEF LITE}
  Err.Component := Component;
{$ENDIF}
  Err.FOLEDBErrorCode := ErrorCode;
  if RecordCount > 0 then /// i and RecordCount is unigned int!
    for i := 0 to RecordCount - 1 do begin// Ignore all error messages without last
      if pIErrorRecords.GetCustomErrorObject(i, IID_ISQLServerErrorInfo, iu) = S_OK then begin
        pISQLServerErrorInfo := ISQLServerErrorInfo(iu);
        if (pISQLServerErrorInfo <> nil) and
          (pISQLServerErrorInfo.GetErrorInfo(pssErrInfo, pStrBuf) = S_OK) and
          (pssErrInfo <> nil) then begin
          // EMSError

          ssErrInfo := MarshalErrInfo(pssErrInfo);
          Err.FErrors.FList.Add(EMSError.Create(ssErrInfo, 0, ssErrInfo.pwszMessage));
          
          FreeCoMem(pStrBuf);
          FreeCoMem(pssErrInfo);
          //pIMalloc.Free(pStrBuf);
          //pIMalloc.Free(pssErrInfo);
        end
        else
          Err.FErrors.FList.Add(EOLEDBError.Create(ErrorCode, Msg));
      end;

      with Err.Errors[Err.ErrorCount - 1] do begin
        if pIErrorRecords.GetBasicErrorInfo(i, ErrInfo) = S_OK then begin
          // GetBasicErrorInfo - ERRORINFO struct
          FOLEDBErrorCode := ErrInfo.hrError;
          // FMinor := ErrInfo.dwMinor;
          // Fclsid := ErrInfo.clsid;
          Fiid := ErrInfo.iid;
          // Fdispid := ErrInfo.dispid;
        end;

  (*      if pIErrorRecords.GetErrorInfo(i, GetUserDefaultLCID(), pIErrorInfo) = S_OK then begin
          // GetErrorInfo - IErrorInfo interface
          // pIErrorInfo.GetGUID(Fguid); - same as Fiid
          pIErrorInfo.GetSource(FSource);
          pIErrorInfo.GetDescription(FDescription);
          // pIErrorInfo.GetHelpFile(FHelpFile); - not used by sqloledb
          // pIErrorInfo.GetHelpContext(FdwHelpContext); - not used by sqloledb
        end;*)
      end;
    end;

  if (ErrorCode = 0) and Assigned(FOnInfoMessage) then begin
    Assert(Err <> nil);
    try
      FOnInfoMessage(Err as EMSError);
    finally
      Err.Free;
    end;
  end
  else
  begin
    Fail := True;
    try
      if Assigned(OnError) then
        DoError(Err{$IFNDEF LITE} as EDAError{$ENDIF}, Fail);
      if Fail then
        raise Err
      else
        Abort;
    finally
      if not Fail then
        Err.Free;
    end;
  end;
end;

procedure TOLEDBConnection.GetConnectionProperties;
var
  PropValues: TPropValues;
begin
  with TOLEDBPropertiesGet.Create(Self, DBPROPSET_DATASOURCEINFO) do
    try
      AddPropId(DBPROP_DBMSNAME);
      AddPropId(DBPROP_DBMSVER);
      AddPropId(DBPROP_PROVIDERFRIENDLYNAME);
      AddPropId(DBPROP_PROVIDERVER);
      if FProvider = prCompact then
        AddPropId(DBPROP_PROVIDERNAME);
      GetProperties(FIDBProperties, PropValues);
    finally
      Free;
    end;
    FDBMSName := PropValues[0];
    FDBMSVer := PropValues[1];
    FDBMSPrimaryVer := StrToInt(Copy(FDBMSVer, 1, Pos('.', FDBMSVer) - 1));

    FProviderFriendlyName := PropValues[2];
    FProviderVer := PropValues[3];
    FProviderPrimaryVer := StrToInt(Copy(FProviderVer, 1, Pos('.', FProviderVer) - 1));

    if (FProvider = prCompact) and (FProviderFriendlyName = '') then begin
      FProviderFriendlyName := PropValues[4];
      if Pos('.dll', LowerCase(FProviderFriendlyName)) > 0 then
        SetLength(FProviderFriendlyName, Length(FProviderFriendlyName) - 4);
    end;

    if FProvider = prCompact then begin
      if (FProviderPrimaryVer <> 3) then
        DatabaseError(SBadProviderName)
    end
    else
      if (FProviderPrimaryVer < 7) and not IsWindowsVista then
        DatabaseError(SWrongMDACVer);
end;

procedure TOLEDBConnection.SetConnectionProperties;
var
  BufLen: cardinal;
//  ComputerName: array[0..MAX_COMPUTERNAME_LENGTH] of char;
{$IFDEF CLR}
  AppName: StringBuilder;
{$ELSE}
  AppName: array[0..MAX_PATH + 100] of char;
{$ENDIF}
  s: string;

begin
  // Set initialization properties
  if FProvider <> prCompact then begin
    with TOLEDBPropertiesSet.Create(Self, DBPROPSET_DBINIT) do
      try
        // Auth props
        if FServer <> '' then
          AddPropStr(DBPROP_INIT_DATASOURCE, FServer)
        else
          AddPropStr(DBPROP_INIT_DATASOURCE, '(local)');
        AddPropStr(DBPROP_INIT_CATALOG, FDatabase);
        AddPropInt(DBPROP_INIT_TIMEOUT, FConnectionTimeout);

        case FAuthentication of
          auWindows:
            AddPropStr(DBPROP_AUTH_INTEGRATED, '');
          auServer:
            if (FUserName = '') and (FPassword = '') then
              AddPropStr(DBPROP_AUTH_USERID, 'sa')
            else
            begin
              AddPropStr(DBPROP_AUTH_USERID, FUserName);
              AddPropStr(DBPROP_AUTH_PASSWORD, FPassword);
            end;
        end;

        if FPersistSecurityInfo then
          AddPropBool(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, FPersistSecurityInfo);

        // Prompt props
        AddPropSmallInt(DBPROP_INIT_PROMPT, DBPROMPT_NOPROMPT);
        AddPropInt(DBPROP_INIT_HWND, 0);

        SetProperties(FIDBProperties);
      finally
        Free;
      end;

    // Set common SQL Server properties
    with TOLEDBPropertiesSet.Create(Self, DBPROPSET_SQLSERVERDBINIT) do
      try
        if FWorkstationID <> '' then
          AddPropStr(SSPROP_INIT_WSID, FWorkstationID);

        if FApplicationName <> '' then
          AddPropStr(SSPROP_INIT_APPNAME, FApplicationName)
        else
        begin
        {$IFDEF CLR}
          AppName := StringBuilder.Create(MAX_PATH + 100);
          try
            BufLen := MAX_PATH + 100;
            GetModuleFileName(0, AppName, BufLen);
            s := ExtractFileName(AppName.ToString);
          finally
            AppName.Free;
          end;
        {$ELSE}
          BufLen := sizeof(AppName);
          GetModuleFileName(0, AppName, BufLen);
          s := ExtractFileName(AppName);
        {$ENDIF}
          AddPropStr(SSPROP_INIT_APPNAME, s);
        end;

        if FLanguage <> '' then
          AddPropStr(SSPROP_INIT_CURRENTLANGUAGE, FLanguage, True);

        AddPropBool(SSPROP_INIT_AUTOTRANSLATE, FAutoTranslate, True);

        if GUIDToString(FProviderId) = GUIDToString(CLSID_SQLNCLI) then begin
          if FMultipleActiveResultSets then
            AddPropBool(SSPROP_INIT_MARSCONNECTION, FMultipleActiveResultSets);
          if FOldPassword <> '' then
            AddPropStr(SSPROP_AUTH_OLD_PASSWORD, FOldPassword);
        end
        else
          if FOldPassword <> '' then
            raise Exception.Create(SSQLNCLINeedsChangePwd);

        if FInitialFileName <> '' then
          AddPropStr(SSPROP_INIT_FILENAME, FInitialFileName);
          
        if FFailoverPartner <> '' then
          AddPropStr(SSPROP_INIT_FAILOVERPARTNER, FFailoverPartner);
          
        SetProperties(FIDBProperties);
      finally
        Free;
      end;
      
    /// Isolated for easy error detection
    if FEncrypt then /// Set only if FEncrypt = True. This needs for prevent troubles with 7.xxx clients
      with TOLEDBPropertiesSet.Create(Self, DBPROPSET_SQLSERVERDBINIT) do
        try
          AddPropBool(SSPROP_INIT_ENCRYPT, FEncrypt);
          try
            SetProperties(FIDBProperties);
          except
            on E: Exception do
            begin
              AddInfoToErr(E, SBadEncrypt, []);
              raise E;
            end;
          end;
        finally
          Free;
        end;
    /// Isolated for easy error detection
    if FNetworkLibrary <> '' then
      with TOLEDBPropertiesSet.Create(Self, DBPROPSET_SQLSERVERDBINIT) do
        try
          AddPropStr(SSPROP_INIT_NETWORKLIBRARY, FNetworkLibrary, True);
          try
            SetProperties(FIDBProperties);
          except
            on E: Exception do begin
              AddInfoToErr(E, SBadNetworkLibrary, []);
              raise E;
            end;
          end;
        finally
          Free;
        end;

    /// Isolated for easy error detection
    with TOLEDBPropertiesSet.Create(Self, DBPROPSET_SQLSERVERDBINIT) do
      try
        AddPropInt(SSPROP_INIT_PACKETSIZE, FPacketSize);
        try
          SetProperties(FIDBProperties);
        except
          on E: Exception do begin
            AddInfoToErr(E, SBadPacketSize, []);
            raise E;
          end;
        end;
      finally
        Free;
      end;
  end
  else begin
    with TOLEDBPropertiesSet.Create(Self, DBPROPSET_DBINIT) do
      try
        AddPropStr(DBPROP_INIT_DATASOURCE, FDatabase, True);
        try
          SetProperties(FIDBProperties);
        except
          on E: Exception do begin
            AddInfoToErr(E, SBadDatabaseFile, []);
            raise E;
          end;
        end;
      finally
        Free;
      end;


      with TOLEDBPropertiesSet.Create(Self, DBPROPSET_SSCE_DBINIT) do
        try
          if FPassword <> '' then
            AddPropStr(DBPROP_SSCE_DBPASSWORD, FPassword);
          //if FMaxDatabaseSize <> 128 then
          AddPropInt(DBPROP_SSCE_MAX_DATABASE_SIZE, FMaxDatabaseSize);

          SetProperties(FIDBProperties);
        finally
          Free;
        end;
  end;
end;

procedure TOLEDBConnection.Connect(const ConnectString: string);
  procedure SetSessionProperties;
  var
    OLEDBProperties: TOLEDBPropertiesSet;
  begin
    OLEDBProperties := TOLEDBPropertiesSet.Create(Self, DBPROPSET_SESSION);
    try
      //Set transaction properties
      with OLEDBProperties do begin
        AddPropInt(DBPROP_SESS_AUTOCOMMITISOLEVELS, ConvertIsolationLevelToOLEDBIsoLevel(FIsolationLevel))
      end;
      OLEDBProperties.SetProperties(FISessionProperties);
    finally
      OLEDBProperties.Free;
    end;
  end;

  procedure SetDataSourceProperties;
  var
    OLEDBProperties: TOLEDBPropertiesSet;
  begin
    OLEDBProperties := TOLEDBPropertiesSet.Create(Self, DBPROPSET_DATASOURCE);
    try
      //Set transaction properties
      with OLEDBProperties do begin
        AddPropBool(DBPROP_MULTIPLECONNECTIONS, True);
        SetProperties(FIDBProperties);
      end;
    finally
      OLEDBProperties.Free;
    end;
  end;

  function OpenProvider(const clsid: array of TGuid): HRESULT;
  var
    i: integer;
  begin
    Result := 0;
    for i := Low(clsid) to High(clsid) do begin
      Result := CoCreateInstance(clsid[i],
        nil,
        CLSCTX_INPROC_SERVER,
        IID_IDBInitialize,
        FIDBInitialize);
      if Result <> REGDB_E_CLASSNOTREG then begin
        FProviderId := clsid[i];
        Exit;
      end;
    end;
  end;

var
  hr: HRESULT;
  iu: IUnknown;
begin
  if not FConnected then begin
    try
      hr := 0;
      if FProvider = prAuto then
        hr := OpenProvider([CLSID_SQLNCLI, CLSID_SQLOLEDB])
      else
      if FProvider = prSQL then
        hr := OpenProvider([CLSID_SQLOLEDB])
      else
      if FProvider = prNativeClient then
        hr := OpenProvider([CLSID_SQLNCLI])
      else
      if FProvider = prCompact then
        hr := OpenProvider([CLSID_SQLSERVERCE_3_0])
      else
        DatabaseError(SBadProviderName);
      if hr = REGDB_E_CLASSNOTREG then
        DatabaseError(SMSSQLNotFound)
      else
        Check(hr, Component);

      QueryIntf(FIDBInitialize, {$IFDEF CLR}IDBProperties{$ELSE}IID_IDBProperties{$ENDIF}, FIDBProperties);

      //Set initialization properties.
      SetConnectionProperties;
      // SetDatabase(FDatabase); - setted on SetConnectionProperties

      //Now establish the connection to the data source.
      hr := FIDBInitialize.Initialize;
      if (hr = E_FAIL) and (FProvider = prCompact) then begin // Database file is not exist
        CreateDatabase(FDatabase, FPassword, FEncrypt);
        hr := FIDBInitialize.Initialize;
      end;
      Check(hr, Component);

      if FProvider <> prCompact then
        SetDataSourceProperties;

      //Create the SessionObject
      QueryIntf(FIDBInitialize, {$IFDEF CLR}IDBCreateSession{$ELSE}IID_IDBCreateSession{$ENDIF}, FIDBCreateSession);
      Check(FIDBCreateSession.CreateSession(nil, IID_ISessionProperties, iu), Component);
      FISessionProperties := ISessionProperties(iu);
      if FProvider <> prCompact then
        SetSessionProperties;
      QueryIntf(FISessionProperties, {$IFDEF CLR}ITransactionLocal{$ELSE}IID_ITransactionLocal{$ENDIF}, FITransactionLocal);

      // Get properties ------------------------------
      GetConnectionProperties;
      inherited;
      FConnected := True;

      if not FQuotedIdentifier then
        SetQuotedIdentifier(FQuotedIdentifier);

    except
      on EFailOver do;
      else begin
        ReleaseInterfaces;
        raise;
      end;
    end;
  end;
end;

procedure TOLEDBConnection.Disconnect;
begin
  if FConnected then begin
    if FIDBInitialize <> nil then
      FIDBInitialize.Uninitialize;// check not need

    ReleaseInterfaces;

    FConnected := False;
    FreeAndNil(FColumnsMetaInfo);
    FreeAndNil(FColumnsRowsetFieldDescs);
  end;
end;

procedure TOLEDBConnection.Assign(Source: TOLEDBConnection);
begin
  FConnectionTimeout := Source.FConnectionTimeout;
  FServer := Source.FServer;
  FUsername := Source.FUsername;
  FPassword := Source.FPassword;
  FDatabase := Source.FDatabase;

  FIsolationLevel := Source.FIsolationLevel;
  FAuthentication := Source.FAuthentication;
  FProvider := Source.FProvider;

  FQuotedIdentifier := Source.FQuotedIdentifier;
  FLanguage := Source.FLanguage;
  FEncrypt := Source.FEncrypt;
  FPersistSecurityInfo := Source.FPersistSecurityInfo;
  FAutoTranslate := Source.FAutoTranslate;
  FNetworkLibrary := Source.FNetworkLibrary;
  FApplicationName := Source.FApplicationName;
  FWorkstationID := Source.FWorkstationID;
  FPacketSize := Source.FPacketSize;
  FMaxDatabaseSize := Source.FMaxDatabaseSize;
end;

procedure TOLEDBConnection.SetIDBCreateSession(CreateSession: IDBCreateSession);
var
  iu: IUnknown;
begin
  Assert(not FConnected);

  //Obtain access to the SQLOLEDB provider.
  try
    FIDBCreateSession := CreateSession;
    Check(FIDBCreateSession.CreateSession(nil, IID_ISessionProperties, iu), Component);
    FISessionProperties := ISessionProperties(iu);
    QueryIntf(FISessionProperties, {$IFDEF CLR}ITransactionLocal{$ELSE}IID_ITransactionLocal{$ENDIF}, FITransactionLocal);

    FConnected := True;
    if not FQuotedIdentifier then
      SetQuotedIdentifier(FQuotedIdentifier);
  except
    ReleaseInterfaces;
    raise;
  end;
end;

function TOLEDBConnection.GetProp(Prop: integer; var Value: variant): boolean; 
begin
  Result := True;

  case Prop of
    prDatabase:
      Value := FDatabase; // string
    prIsolationLevel:
      Value := Integer(FIsolationLevel);
    prAuthentication:
      Value := Integer(FAuthentication); //TMSAuthentication
    prConnectionTimeout:
      Value := Integer(FConnectionTimeout);
    prMaxDatabaseSize:
      Value := Integer(FMaxDatabaseSize);
  else
    Result := inherited GetProp(Prop, Value);
  end;

end;

function TOLEDBConnection.SetProp(Prop: integer; const Value: variant): boolean; 
begin
  Result := True;
  case Prop of
    prDatabase:
      SetDatabase(Value);
    prIsolationLevel:
      FIsolationLevel := TIsolationLevel(Integer(Value));
    {prMultipleConnections:
      FMultipleConnections := boolean(Value^);}
    prAuthentication:
      FAuthentication := TMSAuthentication(Integer(Value)); //TMSAuthentication
    prProvider:
      FProvider := TOLEDBProvider(Integer(Value));
    prConnectionTimeout:
      FConnectionTimeout := Integer(Value);
    prQuotedIdentifier: 
      SetQuotedIdentifier(boolean(Value));
    prLanguage:
      FLanguage := Value;
    prAutoTranslate:
      FAutoTranslate := Boolean(Value);
    prEncrypt:
      FEncrypt := Boolean(Value);
    prPersistSecurityInfo:
      FPersistSecurityInfo := Boolean(Value);
    prNetworkLibrary:
      FNetworkLibrary := Value;
    prApplicationName:
      FApplicationName := Value;
    prWorkstationID:
      FWorkstationID := Value;
    prPacketSize:
      FPacketSize := Integer(Value);
    prInitialFileName:
      FInitialFileName := Value;
    prMARS:
      FMultipleActiveResultSets := Value;
    prOldPassword:
      FOldPassword := Value;
    prMaxDatabaseSize:
      FMaxDatabaseSize := Value;
    prFailoverPartner:
      FFailoverPartner := Value;
  else
    Result := inherited SetProp(Prop, Value);
  end;
end;

function TOLEDBConnection.CheckIsValid: boolean;
begin
  FIsValid := FIDBInitialize <> nil;
  if FIsValid then
    try
      ExecSQL(SCheckConnection);
    except
      FIsValid := False;
    end;
  Result := FIsValid;
end;

procedure TOLEDBConnection.SetDatabase(const Value: string);
var
  OLEDBProperties: TOLEDBPropertiesSet;
begin
  if FIDBProperties <> nil then begin
    OLEDBProperties := TOLEDBPropertiesSet.Create(Self, DBPROPSET_DATASOURCE);
    try
      with OLEDBProperties do
        AddPropStr(DBPROP_CURRENTCATALOG, BracketIfNeed(Value));
      OLEDBProperties.SetProperties(FIDBProperties);
    finally
      OLEDBProperties.Free;
    end;
  end;
  FDatabase := Value;
end;

procedure TOLEDBConnection.SetQuotedIdentifier(const Value: boolean);
begin
  FQuotedIdentifier := Value;

  if FConnected then
    if Value then 
      ExecSQL('SET QUOTED_IDENTIFIER ON')
    else
      ExecSQL('SET QUOTED_IDENTIFIER OFF');
end;

procedure TOLEDBConnection.ExecSQL(const Text: string);
begin
  if FCommand = nil then begin
    FCommand := TOLEDBCommand.Create;
    TOLEDBCommand(FCommand).FConnection := Self;
  end;

  with FCommand do begin
    SetSQL(Text);
    try
      Execute;
    finally
      FCommand.SetCursorState(csInactive); // To prevent blocking execute on second exec
    end;
  end;
end;

class procedure TOLEDBConnection.AssignFieldDescs(Source, Dest: TFieldDescs);
var
  i: integer;
  Field, FieldSource: TOLEDBFieldDesc;
begin
  Dest.Clear;
  for i := 0 to Source.Count - 1 do begin
    FieldSource := Source[i] as TOLEDBFieldDesc;
    Field := TOLEDBFieldDesc.Create;
    Dest.Add(Field);
    Field.Name := FieldSource.Name;
    Field.ActualName := FieldSource.ActualName;
    // FTableName: string;      //table of name that holds this field
    Field.DataType := FieldSource.DataType;
    Field.SubDataType := FieldSource.SubDataType;
    Field.Length := FieldSource.Length;       // precision for number
    Field.Scale := FieldSource.Scale;
    Field.FieldNo := FieldSource.FieldNo;
    Field.ActualFieldNo := FieldSource.ActualFieldNo;
    Field.Size := FieldSource.Size;
    //Field.DataSize := FieldSource.DataSize;
    Field.Offset := FieldSource.Offset;
    Field.DataOffset := FieldSource.DataOffset;
    Field.Required := FieldSource.Required;
    Field.ReadOnly := FieldSource.ReadOnly;
    Field.IsKey := FieldSource.IsKey;
    Field.Fixed := FieldSource.Fixed;
    Field.Hidden := FieldSource.Hidden;
    //Field.ObjectType := FieldSource.ObjectType;
    //Field.ParentField := FieldSource.ParentField;
    //Field.HiddenObject := FieldSource.HiddenObject;
    //FHandle: IntPtr;    // IntPtr to field specific data
    //FReserved: boolean;  // reserved flag for perfomance optimization
  end;
end;

{$IFDEF CLR}
procedure TOLEDBConnection.DoError(E: Exception; var Fail: boolean);
begin
  inherited;
end;
{$ENDIF}

procedure TOLEDBConnection.CreateDatabase(const Database: string; const Password: string; const Encrypted: boolean);
var
  pIDBDataSourceAdmin: IDBDataSourceAdmin;
  pIUnknownSession: IUnknown;

  PPropertySet: IntPtr;
  PropertySet: PDBPropSet;
  PDBProperty: IntPtr;
  DBProperty: PDBProp;
  PSSCEProperty: IntPtr;
  SSCEProperty: PDBPROP;
begin
  PPropertySet := nil;
  PDBProperty := nil;
  PSSCEProperty := nil;
  try
    Check(CoCreateInstance(CLSID_SQLSERVERCE_3_0, nil, CLSCTX_INPROC_SERVER,
      IID_IDBDataSourceAdmin, pIDBDataSourceAdmin), Component);

    PDBProperty := Marshal.AllocHGlobal(SizeOfDBProp);
    FillChar(PDBProperty, SizeOfDBProp, $00);
    DBProperty := PDBProperty;
    DBProperty.dwPropertyID := DBPROP_INIT_DATASOURCE;
    DBProperty.dwOptions := DBPROPOPTIONS_REQUIRED;
    DBProperty.vValue := Database;

    PSSCEProperty := Marshal.AllocHGlobal(SizeOfDBProp * 2);
    FillChar(PSSCEProperty, SizeOfDBProp * 2, $00);
    SSCEProperty := PSSCEProperty;
    SSCEProperty.dwPropertyID := DBPROP_SSCE_ENCRYPTDATABASE;
    SSCEProperty.dwOptions := DBPROPOPTIONS_REQUIRED;
    SSCEProperty.vValue := VarAsType(Encrypted, VT_BOOL);

    SSCEProperty := IntPtr(Integer(PSSCEProperty) + SizeOfDBProp);
    SSCEProperty.dwPropertyID := DBPROP_SSCE_DBPASSWORD;
    SSCEProperty.dwOptions := DBPROPOPTIONS_REQUIRED;
    SSCEProperty.vValue := Password;

    PPropertySet := Marshal.AllocHGlobal(SizeOf(DBPROPSET) * 2);
    FillChar(PPropertySet, SizeOf(DBPROPSET) * 2, $00);
    PropertySet := PPropertySet;
    PropertySet.guidPropertySet := DBPROPSET_DBINIT;
    PropertySet.rgProperties := PDBProperty;
    PropertySet.cProperties := 1;

    PropertySet := IntPtr(Integer(PPropertySet) + SizeOf(DBPROPSET));
    PropertySet.guidPropertySet := DBPROPSET_SSCE_DBINIT ;
    PropertySet.rgProperties := PSSCEProperty;
    PropertySet.cProperties := 2;

    Check(pIDBDataSourceAdmin.CreateDataSource(2, PPropertySet, nil,
      IID_IUnknown, pIUnknownSession), Component);
  finally
    pIUnknownSession := nil;
    if PPropertySet <> nil then
      Marshal.FreeHGlobal(PPropertySet);
    if PSSCEProperty <> nil then
      Marshal.FreeHGlobal(PSSCEProperty);
    if PDBProperty <> nil then
      Marshal.FreeHGlobal(PDBProperty);
  end;
end;

function TOLEDBConnection.GetSchemaRowset(const Schema: TGUID; rgRestrictions: TRestrictions): IRowset;
var
  i: integer;
  iu: IUnknown;
  DBSchemaRowset: IDBSchemaRowset;
  rgRestrictionsPtr: IntPtr;
begin
  for i := Low(rgRestrictions) to High(rgRestrictions) do
    if (VarType(rgRestrictions[i]) = varOleStr) and (String(rgRestrictions[i]) = '') then
      rgRestrictions[i] := Null; //
      // rgRestrictions[i] := Unassigned;
      // TVarData(rgRestrictions[i]).VType := varNull;

  QueryIntf(SessionProperties, {$IFDEF CLR}IDBSchemaRowset{$ELSE}IID_IDBSchemaRowset{$ENDIF}, DBSchemaRowset);

{$IFDEF CLR}
  i := 16 {SizeOf(OleVariant)} * Length(rgRestrictions);
  rgRestrictionsPtr := Marshal.AllocHGlobal(i);
  try
    FillChar(rgRestrictionsPtr, i, 0);
    for i := Low(rgRestrictions) to High(rgRestrictions) do
      SetOleVariant(IntPtr(Integer(rgRestrictionsPtr) + 16 {SizeOf(OleVariant)} * (Integer(i) - Low(rgRestrictions))), rgRestrictions[i]);
{$ELSE}
    rgRestrictionsPtr := @rgRestrictions[0];
{$ENDIF}
    Check(DBSchemaRowset.GetRowset(nil, Schema, Length(rgRestrictions), rgRestrictionsPtr,
      IID_IRowset, 0, nil, iu), Component);
{$IFDEF CLR}
  finally
    for i := Low(rgRestrictions) to High(rgRestrictions) do
      OleVarClear(IntPtr(Integer(rgRestrictionsPtr) + 16 {SizeOf(OleVariant)} * (Integer(i) - Low(rgRestrictions))));
    Marshal.FreeHGlobal(rgRestrictionsPtr);
  end;
{$ENDIF}
  Result := IRowset(iu);
  iu := nil;
end;

{ TOLEDBCommand }

procedure TOLEDBCommand.Check(const Status: HRESULT);
begin
  Assert(FConnection <> nil);
  if Status <> S_OK then
    FConnection.Check(Status, Component);
end;

constructor TOLEDBCommand.Create;
begin
  inherited;
  FQueryIntCnt := 0;
  FRequestIUnknown := False;
  FParamsAccessorDataAvaible := False;

  FRowsAffected := -1;

  FCursorState := csInactive;

  FBreakExecCS := TCriticalSection.Create;
end;

destructor TOLEDBCommand.Destroy;
begin
  if FNonBlocking and (FISSAsynchStatus <> nil) then
    BreakExec;
  FISSAsynchStatus := nil;
  ClearIMultipleResults;
  FIUnknownNext := nil; /// Clear this interface before RequestParams to avoid AV. See TDbxSdaTestSet.DoTestSPNextRecordSet 
  RequestParamsIfPossible;
  UnPrepare;
  Assert(FQueryIntCnt = 0, Format('TOLEDBCommand.Destroy - interfaces not released (%d)', [FQueryIntCnt]));
  FBreakExecCS.Free;

  inherited;
end;

procedure TOLEDBCommand.QueryInterfaces(const QueryPrepare: boolean); // QueryPrepare must be True to request IID_ICommandPrepare
var
  CreateCommand: IDBCreateCommand;
  iu: IUnknown;
begin
  if FQueryIntCnt = 0 then begin
    Assert(FConnection <> nil);
    QueryIntf(FConnection.FISessionProperties, {$IFDEF CLR}IDBCreateCommand{$ELSE}IID_IDBCreateCommand{$ENDIF}, CreateCommand);

    Check(CreateCommand.CreateCommand(nil, IID_ICommandText, iu));
    FICommandText := ICommandText(iu);
    if QueryPrepare then
      QueryIntf(FICommandText, {$IFDEF CLR}ICommandPrepare{$ELSE}IID_ICommandPrepare{$ENDIF}, FICommandPrepare)
    else
      FICommandPrepare := nil;
    QueryIntf(FICommandText, {$IFDEF CLR}ICommandProperties{$ELSE}IID_ICommandProperties{$ENDIF}, FICommandProperties);
  end;

  Inc(FQueryIntCnt);
end;

procedure TOLEDBCommand.ReleaseInterfaces;
begin
  if FQueryIntCnt = 0 then // Exception on TOLEDBRecordSet.InternalOpen -> TOLEDBRecordSet.QueryCommandInterfaces -> TOLEDBCommand.QueryInterfaces
    Exit;

  if FQueryIntCnt = 1 then begin
    FBreakExecCS.Acquire;
    try
      FICommandText := nil;
    finally
      FBreakExecCS.Release;
    end;
    FICommandPrepare := nil;
    FICommandProperties := nil;
  end;

  Dec(FQueryIntCnt);
end;
(*procedure TOLEDBCommand.GetCommandProp;
var
  rgPropertySets, pPropSet: PDBPropSet;
  cPropertySets: UINT;
  s: string;
//  PropCnt: integer;
begin
  try
    Check(FICommandProperties.GetProperties(0, nil,
      cPropertySets, PDBPropSet(rgPropertySets)));

    Assert(rgPropertySets <> nil, 'Cannot get connection properties');

    pPropSet := rgPropertySets; // DBPROPSET_ROWSET
//    PropCnt := pPropSet.cProperties;
    s := pPropSet.rgProperties[0].vValue;
    PChar(pPropSet) := PChar(pPropSet) + sizeof(DBPropSet);//DBPROPSET_SQLSERVERROWSET
    s := pPropSet.rgProperties[0].vValue;
    s := pPropSet.rgProperties[1].vValue;
    s := pPropSet.rgProperties[2].vValue;
    s := pPropSet.rgProperties[3].vValue;
  finally

    FConnection.Malloc.Free(rgPropertySets.rgProperties);
    FConnection.Malloc.Free(rgPropertySets);
  end;
end;

type
  PDBParamInfoArray = ^TDBParamInfoArray;
  TDBParamInfoArray = array[0..MAXBOUND] of DBPARAMINFO;
*)

procedure TOLEDBCommand.SetCommandProp;
var
  OLEDBProperties: TOLEDBPropertiesSet;
  Str: WideString;
  g: TGUID;
begin
{$IFDEF SDAC_TEST}
  Inc(__SetCommandPropCount);
{$ENDIF}
  Assert(FICommandText <> nil);

  Str := FSQL;
  g := DBGUID_DEFAULT;

  Check(FICommandText.SetCommandText(g, {$IFDEF CLR}Str{$ELSE}PWideChar(Str){$ENDIF}));

  OLEDBProperties := TOLEDBPropertiesSet.Create(FConnection, DBPROPSET_ROWSET);
  try
    with OLEDBProperties do begin
      if FConnection.FProvider <> prCompact then
        AddPropInt(DBPROP_COMMANDTIMEOUT, FCommandTimeout);
      if FNonBlocking and (not FRequestIUnknown) then begin
        if GUIDToString(FConnection.FProviderId) <> GUIDToString(CLSID_SQLNCLI) then
          raise Exception.Create(SSQLNCLINeeds);
        AddPropInt(DBPROP_ROWSET_ASYNCH, DBPROPVAL_ASYNCH_INITIALIZE);
      end;
    end;
    OLEDBProperties.SetProperties(FICommandProperties);
  finally
    OLEDBProperties.Free;
  end;

  if FSmartRefresh then begin
    OLEDBProperties := TOLEDBPropertiesSet.Create(FConnection, DBPROPSET_SQLSERVERROWSET);
    try
      with OLEDBProperties do begin
        AddPropStr(SSPROP_QP_NOTIFICATION_MSGTEXT, WideString(FSmartRefreshMsg), True);
        AddPropStr(SSPROP_QP_NOTIFICATION_OPTIONS, Format('service=%s', [FSmartRefreshService]), True);
      end;
      OLEDBProperties.SetProperties(FICommandProperties);
    finally
      OLEDBProperties.Free;
    end;
  end;
end;

procedure TOLEDBCommand.SetParameterInfo;
var
  CommandWithParameters: ICommandWithParameters;

  i: integer;
  rgParamOrdinals: array of UInt;
  rgParamBindInfo: array of TDBParamBindInfo;
  ParamDesc: TParamDesc;
  hr: HResult;
  ParamCount: integer;

  ParamVarType: TVarType;
  prgParamOrdinals: IntPtr;
  prgParamBindInfo: IntPtr;
{$IFDEF CLR}
  rgParamOrdinalsGC: GCHandle;
  rgParamBindInfoGC: GCHandle;
{$ENDIF}
  IsUnicode: boolean;

begin
  QueryIntf(FICommandText, {$IFDEF CLR}ICommandWithParameters{$ELSE}IID_ICommandWithParameters{$ENDIF}, CommandWithParameters);
  CommandWithParameters.SetParameterInfo(0, nil, nil); /// Clear, just in case

  ParamCount := FParams.Count;

  if ParamCount <> 0 then begin
    SetLength(rgParamOrdinals, ParamCount);
    SetLength(rgParamBindInfo, ParamCount);

    //OFS('=================================');
    for i := 0 to ParamCount - 1 do begin
      ParamDesc := FParams[i];
      rgParamOrdinals[i] := i + 1;

      case ParamDesc.GetDataType and varTypeMask of
        dtUnknown: begin
          ParamVarType := VarType(ParamDesc.Value);
          case ParamVarType of
            varSmallint: { vt_i2           2 }
              rgParamBindInfo[i].pwszDataSourceType := dstSmallint;
            varInteger: { vt_i4           3 }
              rgParamBindInfo[i].pwszDataSourceType := dstInt;
            varSingle: { vt_r4           4 }
              rgParamBindInfo[i].pwszDataSourceType := dstReal;
            varDouble: { vt_r8           5 }
              rgParamBindInfo[i].pwszDataSourceType := dstFloat;
            varCurrency: { vt_cy           6 }
              rgParamBindInfo[i].pwszDataSourceType := dstMoney;
            varDate: { vt_date         7 }
              rgParamBindInfo[i].pwszDataSourceType := dstDatetime;
            varOleStr: { vt_bstr         8 }
              rgParamBindInfo[i].pwszDataSourceType := dstNVarchar;
          {$IFNDEF CLR}
            varString:
              rgParamBindInfo[i].pwszDataSourceType := dstVarchar;
          {$ENDIF}
            varBoolean: { vt_bool        11 }
              rgParamBindInfo[i].pwszDataSourceType := dstBit;
          {$IFDEF VER6P}
            varShortInt:
              rgParamBindInfo[i].pwszDataSourceType := dstTinyint;
            varWord:
              rgParamBindInfo[i].pwszDataSourceType := dstInt;
            varInt64: { vt_i8          20 }
              rgParamBindInfo[i].pwszDataSourceType := dstBigint;
          {$ENDIF}
            varByte: { vt_ui1         17 }
              rgParamBindInfo[i].pwszDataSourceType := dstSmallint;
            varLongWord: { vt_ui4         19 }
              rgParamBindInfo[i].pwszDataSourceType := dstBigint;
            else
              rgParamBindInfo[i].pwszDataSourceType := dstSql_variant;
          end;
        end;

        dtString, dtExtString:
          if IsOutputLOB(ParamDesc, FConnection.DBMSPrimaryVer, FConnection.ProviderPrimaryVer) then
            rgParamBindInfo[i].pwszDataSourceType := dstVarcharMax
          else
            rgParamBindInfo[i].pwszDataSourceType := dstVarchar;
            
        dtWideString, dtExtWideString:
          if IsOutputLOB(ParamDesc, FConnection.DBMSPrimaryVer, FConnection.ProviderPrimaryVer) then
            rgParamBindInfo[i].pwszDataSourceType := dstNVarcharMax
          else
            rgParamBindInfo[i].pwszDataSourceType := dstNVarchar;
            
        dtInt8:
          rgParamBindInfo[i].pwszDataSourceType := dstTinyint;
        dtInt16:
          rgParamBindInfo[i].pwszDataSourceType := dstSmallint;
        dtInt32, dtUInt16:
          rgParamBindInfo[i].pwszDataSourceType := dstInt;
        dtInt64, dtUInt32:
          rgParamBindInfo[i].pwszDataSourceType := dstBigint;
        dtFloat:
          rgParamBindInfo[i].pwszDataSourceType := dstFloat;
        dtDate, dtTime, dtDateTime:
          rgParamBindInfo[i].pwszDataSourceType := dstDatetime;
        dtBoolean:
          rgParamBindInfo[i].pwszDataSourceType := dstBit;
        dtCurrency:
          rgParamBindInfo[i].pwszDataSourceType := dstMoney;
        dtBlob:
          rgParamBindInfo[i].pwszDataSourceType := dstImage;
        dtMemo, dtWideMemo, dtMSXML: begin
        {$IFDEF CLR}
          IsUnicode := (VarType(ParamDesc.Value) = varOleStr) or (VarType(ParamDesc.Value) = varString);
          if not IsUnicode
            and (ParamDesc.Value <> nil)
            and (ParamDesc.Value is TBlob) then
            IsUnicode := TBlob(ParamDesc.Value).IsUnicode;
        {$ELSE}
          IsUnicode := VarType(ParamDesc.Value) = varOleStr;
          if not IsUnicode
            and (VarType(ParamDesc.Value) = varByRef)
            and (TVarData(ParamDesc.Value).VPointer <> nil) then begin
            // Assert(TObject(TVarData(ParamDesc.Value).VPointer) is TBlob); - trial
            IsUnicode := TBlob(TVarData(ParamDesc.Value).VPointer).IsUnicode;
          end;
        {$ENDIF}

          if IsUnicode then
            rgParamBindInfo[i].pwszDataSourceType := dstNVarchar
          else
            rgParamBindInfo[i].pwszDataSourceType := dstVarchar;
        end;

      {$IFDEF VER5P}
        dtVariant:
          rgParamBindInfo[i].pwszDataSourceType := dstSql_variant;
      {$ENDIF}
        dtBytes:
          rgParamBindInfo[i].pwszDataSourceType := dstBinary;
        dtVarBytes, dtExtVarBytes:
          rgParamBindInfo[i].pwszDataSourceType := dstVarbinary;

      {$IFDEF VER6P}
        dtFMTBCD,
      {$ENDIF}
        dtBCD:
          rgParamBindInfo[i].pwszDataSourceType := dstMoney;

        dtGuid:
          rgParamBindInfo[i].pwszDataSourceType := dstGuid;

        else
          Assert(False, Format('Unknown datatype for param %s[%d] = %X', [ParamDesc.GetName, i, ParamDesc.GetDataType]));
      end;

      case ParamDesc.GetDataType of
        dtString, dtExtString, dtBytes, dtVarBytes, dtExtVarBytes:
          if ParamDesc.GetSize > 0 then
            rgParamBindInfo[i].ulParamSize := ParamDesc.GetSize
          else
            rgParamBindInfo[i].ulParamSize := MaxNonBlobFieldLen;
        dtWideString, dtExtWideString:
          if ParamDesc.GetSize > 0 then
            rgParamBindInfo[i].ulParamSize := ParamDesc.GetSize
          else
            rgParamBindInfo[i].ulParamSize := MaxNonBlobFieldLen div SizeOf(WideChar);
        else
          rgParamBindInfo[i].ulParamSize := $FFFFFFF;
      end;
      case ParamDesc.GetParamType of
        pdInput:
          rgParamBindInfo[i].dwFlags := DBPARAMFLAGS_ISINPUT + DBPARAMFLAGS_ISNULLABLE;
        pdOutput, pdResult:
          rgParamBindInfo[i].dwFlags := DBPARAMFLAGS_ISOUTPUT + DBPARAMFLAGS_ISNULLABLE;
        pdUnknown, pdInputOutput:
          rgParamBindInfo[i].dwFlags := DBPARAMFLAGS_ISINPUT + DBPARAMFLAGS_ISOUTPUT + DBPARAMFLAGS_ISNULLABLE;
      end;

      rgParamBindInfo[i].pwszName := nil;

      {OFS(ParamDesc.GetName);
      OFS('  ' + Marshal.PtrToStringUni(rgParamBindInfo[i].pwszDataSourceType));
      OFS('  ulParamSize = ' + IntToStr(rgParamBindInfo[i].ulParamSize));
      OFS('  dwFlags = ' + IntToStr(rgParamBindInfo[i].dwFlags));
      OFS('  bPrecision = ' + IntToStr(rgParamBindInfo[i].bPrecision));
      OFS('  bScale = ' + IntToStr(rgParamBindInfo[i].bScale));//}

    end;
  {$IFDEF CLR}
    try
      rgParamOrdinalsGC := GCHandle.Alloc(rgParamOrdinals, GCHandleType.Pinned);
      prgParamOrdinals := Marshal.UnsafeAddrOfPinnedArrayElement(rgParamOrdinals, 0);

      rgParamBindInfoGC := GCHandle.Alloc(rgParamBindInfo, GCHandleType.Pinned);
      prgParamBindInfo := Marshal.UnsafeAddrOfPinnedArrayElement(rgParamBindInfo, 0);

  {$ELSE}

    prgParamOrdinals := @rgParamOrdinals[0];
    prgParamBindInfo := @rgParamBindInfo[0];
  {$ENDIF}

    {OFS('prgParamOrdinals');
    OFS(prgParamOrdinals, ParamCount * 4, OFSFileName);
    OFS('prgParamBindInfo');
    OFS(prgParamBindInfo, ParamCount * sizeof(rgParamBindInfo[0]), OFSFileName);//}
    hr := CommandWithParameters.SetParameterInfo(ParamCount, prgParamOrdinals, prgParamBindInfo);
  {$IFDEF CLR}
    finally
      if IntPtr(rgParamOrdinalsGC) <> nil then
        rgParamOrdinalsGC.Free;
      if IntPtr(rgParamBindInfoGC) <> nil then
        rgParamBindInfoGC.Free;
    end;
  {$ENDIF}

    if hr <> DB_S_TYPEINFOOVERRIDDEN then
      Check(hr);
  end;
end;

procedure TOLEDBCommand.Prepare;
begin
  if GetPrepared then
    Exit;

  QueryInterfaces(True);
  try
    FRPCCall := FIsSProc and __UseRPCCallStyle;
    SetCommandProp;
    if FRPCCall or (not ParamsInfoOldBehavior) then
      SetParameterInfo;

    Check(FICommandPrepare.Prepare(0)); // If statement is wrong in some cases exception may be occured in other place
  {$IFDEF SDAC_TEST}
    Inc(__ServerPrepareCount);
  {$ENDIF}

    inherited;
    FPrepared := True;
  except
    ReleaseInterfaces;
    raise;
  end;
end;

procedure TOLEDBCommand.Unprepare;
begin
  if GetPrepared then begin
    FIUnknown := nil;
    RequestParamsIfPossible;

    if (FICommandPrepare <> nil) and not FRPCCall then
      Check(FICommandPrepare.UnPrepare);
    inherited;
    FPrepared := False;
    FRPCCall := False;

    ReleaseInterfaces;
  end;
end;

function TOLEDBCommand.GetPrepared: boolean;
begin
  Result := FPrepared;
end;

{$IFNDEF VER6P}
type
  _TParamDesc = class (TParamDesc);
{$ENDIF}

procedure FillBindingForParam(Ordinal: integer; ParamDesc: TOLEDBParamDesc; Connection: TOLEDBConnection;
  var pBind: TDBBinding; var BindMemorySize: UINT; const ValueAvaliable: boolean; const IsWide: boolean);

  procedure SetBindDefaults;
  begin
    pBind.dwPart := DBPART_STATUS or DBPART_LENGTH or DBPART_VALUE; // WAR Length is not always need and may be removed in some cases
  {$IFNDEF CLR}
    pointer(pBind.pTypeInfo) := nil;
  {$ENDIF}
    pBind.pBindExt := nil;
    pBind.dwMemOwner := DBMEMOWNER_CLIENTOWNED;
    pBind.dwFlags := 0;
    pBind.bPrecision := 11;
    pBind.bScale := 0;
  end;

  procedure SetBindData;
    function GetMaxLen: integer; // Also correct pBind.wType. Must be called AFTER ConvertInternalTypeToOLEDB
    var
      IsUnicode: boolean;
      DataType: word;
    begin
      DataType := ParamDesc.GetDataType;
      case DataType of
        dtUnknown:
          Result := sizeof(OleVariant);
        dtString:
          if not ValueAvaliable then
            Result := MaxNonBlobFieldLen
          else
            Result := ParamDesc.GetSize + 1{#0};
        dtWideString:
          Result := (ParamDesc.GetSize + 1{#0}) * sizeof(WideChar);
        dtBytes, dtVarBytes:
          Result := ParamDesc.GetSize;
        dtInt8:
          Result := sizeof(byte);
        dtInt16, dtWord:
          if Connection.FProvider = prCompact then begin
            if (ParamDesc.OLEDBType = DBTYPE_UI1) then begin
              Result := SizeOf(Byte);
              pBind.wType := DBTYPE_UI1;
            end
            else
              Result := sizeof(word);
          end
          else
            Result := sizeof(word);
        dtInt32:
          Result := sizeof(dword);
        dtFloat:
          if Connection.FProvider = prCompact then begin
            if (ParamDesc.OLEDBType = DBTYPE_R4) then begin
              Result := SizeOf(Single);
              pBind.wType := DBTYPE_R4;
            end
            else
              if (ParamDesc.OLEDBType = DBTYPE_NUMERIC) then begin
                Result := SizeOfTDBNumeric;
                pBind.wType := DBTYPE_NUMERIC;
              end
              else
                Result := SizeOf(double);
          end
          else
            Result := sizeof(double);
        dtCurrency:
          Result := sizeof(double);
          // Result := sizeof(Currency); Currency type cannot be used over TCurrencyField uses double to store
        dtDate:
          Result := sizeof(TDateTime);
        dtDateTime, dtTime:
        begin
          Result := sizeof(TDBTimeStamp);
          pBind.wType := DBTYPE_DBTIMESTAMP;
        end;
        dtBoolean:
          Result := sizeof(WordBool);
        dtInt64:
          Result := sizeof(Int64);
        dtBlob, dtMemo, dtWideMemo, dtMSXML:
          if Connection.FProvider <> prCompact then
            if IsOutputLOB(ParamDesc, Connection.DBMSPrimaryVer, Connection.ProviderPrimaryVer) then
              Result := SizeOf(TOLEDBStream) // varchar(max)
            else
              Result := 0 // Only Input, store ByRef
          else begin
            if ((DataType = dtMemo) or (DataType = dtWideMemo)) and (ParamDesc.OLEDBType in [DBTYPE_STR, DBTYPE_WSTR]) then begin
              Result := TBlob(ParamDesc.GetObject).Size;
              pBind.wType := ParamDesc.OLEDBType;
            end
            else
              Result := SizeOf(TOLEDBStream);
          end;
      {$IFDEF VER5P}
        dtGuid:
          Result := sizeof(TGuid);
        dtVariant:
          Result := sizeof(OleVariant);
      {$ENDIF}
        dtBCD:
          if Connection.FProvider <> prCompact then
            Result := sizeof(Currency)
          else begin
            Result := SizeOfTDBNumeric;
            pBind.wType := DBTYPE_NUMERIC;
          end;
      {$IFDEF VER6P}
        dtFmtBCD:
          if Connection.FProvider <> prCompact then
            Result := SizeOfTBcd * 2
          else
            Result := SizeOfTDBNumeric;
      {$ENDIF}
        else
          Result := 0;
      end;

      if (Connection.FProvider = prCompact) and IsLargeDataTypeUsed(ParamDesc) and ParamDesc.GetNull then begin
        Result := 0;
        case ParamDesc.GetDataType of
          dtBlob:
            pBind.wType := DBTYPE_BYTES;
          dtMemo, dtWideMemo:
            pBind.wType := DBTYPE_WSTR;
          else
            Assert(False);
        end;
      end;

      // Optimization for input-only parameters
      if (((ParamDesc.GetParamType = pdInput) and (DataType in CharsByRef + BytesByRef) {$IFDEF CLR}and (DataType <> dtString){$ENDIF})
        or (DataType = dtBlob)
        or (DataType = dtMemo)
        or (DataType = dtWideMemo)
        or (DataType = dtMSXML))
        and (Connection.FProvider <> prCompact)
        and (not IsOutputLOB(ParamDesc, Connection.DBMSPrimaryVer, Connection.ProviderPrimaryVer)) then begin
        // Only Input, store ByRef
        Result := 4;
        if (DataType in BytesByRef) then // This is Bytes by Ref
          pBind.wType := DBTYPE_BYREF or DBTYPE_BYTES
        else // This is (Wide)String by Ref
        begin
        {$IFDEF CLR}
          if not ValueAvaliable then
            IsUnicode := (ParamDesc.GetDataType in [dtWideString, dtExtWideString]) or IsWide
          else
            IsUnicode := (VarType(ParamDesc.Value) = varOleStr) or (VarType(ParamDesc.Value) = varString);
          if not IsUnicode
            and (ParamDesc.Value <> nil)
            and (ParamDesc.Value is TBlob) then
            IsUnicode := TBlob(ParamDesc.Value).IsUnicode;
        {$ELSE}
          if not ValueAvaliable then
            IsUnicode := (ParamDesc.GetDataType in [dtWideString, dtExtWideString]) or IsWide
          else
            IsUnicode := VarType(ParamDesc.Value) = varOleStr;
          if not IsUnicode
            and (VarType(ParamDesc.Value) = varByRef)
            and (TVarData(ParamDesc.Value).VPointer <> nil) then begin
            // Assert(TObject(TVarData(ParamDesc.Value).VPointer) is TBlob); - trial
            IsUnicode := TBlob(TVarData(ParamDesc.Value).VPointer).IsUnicode;
          end;
        {$ENDIF}

          if IsUnicode then // WideString
            pBind.wType := DBTYPE_BYREF or DBTYPE_WSTR
          else // (VType = varString) String
            pBind.wType := DBTYPE_BYREF or DBTYPE_STR;
        end;
      end
      else
        if (ParamDesc.GetParamType in [pdOutput, pdInputOutput]) // Truncate params, if too long
          and (DataType in [dtString, dtWideString, dtBytes, dtVarBytes]) then begin
          if not ((Connection.DBMSPrimaryVer >= 9) and (Connection.ProviderPrimaryVer >=9) and (Result > MaxNonBlobFieldLen)) then
            if DataType = dtString then
              Result := MaxNonBlobFieldLen + 1
            else
              Result := MaxNonBlobFieldLen;
        end;

      Assert(Result >= 0);
//      Assert(Result <= MaxNonBlobFieldLen + 1);
    end;

  var
    ServerVersion: integer;
  begin
{    if pBind.iOrdinal = 1 then
      OFS('==========================================');
    OFS('--------------------------');
    OFS('ParamDesc.GetName ' + ParamDesc.GetName);
    OFS('VarType(ParamDesc.Value) = ' + IntToStr(VarType(ParamDesc.Value)));}

    pBind.iOrdinal := Ordinal;
    pBind.eParamIO := ConvertCRParamTypeToOLEDB(ParamDesc.GetParamType);
    if IsLargeDataTypeUsed(ParamDesc) and // "text", "ntext", "image"
      (pBind.eParamIO in [DBPARAMIO_OUTPUT, DBPARAMIO_INPUT + DBPARAMIO_OUTPUT]) then
      if not IsOutputLOB(ParamDesc, Connection.DBMSPrimaryVer, Connection.ProviderPrimaryVer) then // "varchar(max)"
        DatabaseErrorFmt(SBadOutputParam, [ParamDesc.GetName]);

    // int64 parameters conversion
    if ((Connection.ProviderPrimaryVer < 8) and not IsWindowsVista) and (Connection.FProvider <> prCompact) and
      (ParamDesc.GetDataType = dtInt64) then
      ParamDesc.SetDataType(dtFloat);

    // currency parameters conversion
    if (ParamDesc.GetDataType = dtCurrency) and (VarType(ParamDesc.Value) = varCurrency) then
      ParamDesc.SetDataType(dtBCD); // To prevent SQL Server exception on setting float value to smallmoney parameter

    if Connection <> nil then
      ServerVersion := Connection.DBMSPrimaryVer
    else
      ServerVersion := 0;
    pBind.wType := ConvertInternalTypeToOLEDB(ParamDesc.GetDataType, True, ServerVersion);
    (* Not used
    if pBind.wType = DBTYPE_IUNKNOWN then begin
      GetMem1(pBind.pObject, sizeof(DBOBJECT));
      pBind.pObject^.iid := IID_ISequentialStream;
      if pBind.eParamIO = DBPARAMIO_INPUT then
        pBind.pObject^.dwFlags := STGM_READ
      else
        pBind.pObject^.dwFlags := STGM_READWRITE;
    end;*)

    pBind.obStatus := BindMemorySize;
    pBind.obLength := BindMemorySize + sizeof(DWORD);
    pBind.obValue := BindMemorySize + sizeof(DWORD) + sizeof(UINT);

    pBind.cbMaxLen := GetMaxLen; // Also correct pBind.wType. Must be called AFTER ConvertInternalTypeToOLEDB
    if not ((ServerVersion = 3) and ((pBind.wType = DBTYPE_BYTES) or (pBind.wType = DBTYPE_WSTR))) then
      Assert(pBind.cbMaxLen > 0, Format('Unknown datatype for param %s[%d] = %X', [ParamDesc.GetName, Ordinal, ParamDesc.GetDataType]));
    BindMemorySize := pBind.obValue + pBind.cbMaxLen;

    {OFS('iOrdinal = ' + IntToStr(pBind.iOrdinal));
    OFS('obValue = ' + IntToStr(pBind.obValue));
    OFS('obLength = ' + IntToStr(pBind.obLength));
    OFS('obStatus = ' + IntToStr(pBind.obStatus));
    OFS('pTypeInfo = ' + IntToStr(Integer(pBind.pTypeInfo)));
    OFS('pObject = ' + IntToStr(Integer(pBind.pObject)));
    OFS('pBindExt = ' + IntToStr(Integer(pBind.pBindExt)));
    OFS('dwPart = ' + IntToStr(pBind.dwPart));
    OFS('dwMemOwner = ' + IntToStr(pBind.dwMemOwner));
    OFS('eParamIO = ' + IntToStr(pBind.eParamIO));
    OFS('cbMaxLen = ' + IntToStr(pBind.cbMaxLen));
    OFS('dwFlags = ' + IntToStr(pBind.dwFlags));
    OFS('wType = ' + IntToStr(pBind.wType));
    OFS('bPrecision = ' + IntToStr(pBind.bPrecision));
    OFS('bScale = ' + IntToStr(pBind.bScale));}
  end;

begin
  SetBindDefaults;
  SetBindData;
end;

procedure SaveParamValue(const ParamDesc: TParamDesc; const pBind: TDBBinding;
  var ParamsAccessorData: TParamsAccessorData{$IFDEF HAVE_COMPRESS};
  const CompressBlobMode: TCompressBlobMode{$ENDIF}
  {$IFDEF CLR}; var ParamsGC: TIntPtrDynArray{$ENDIF}; ServerVersion, ClientVersion: integer);
var
  pStatus: PDWORD;
  pLength: PUINT;
  pValue: IntPtr;
  // pParamData: PVarData;
  ParamVarType: TVarType;
  ParamVarPtr: IntPtr;

  s: string;
  l: UINT;
  ws: WideString;

  c: Currency;
  i64: Int64;

  Blob: TBlob;
{$IFDEF CLR}
  d: double;
  b: TBytes;
{$ENDIF}
{$IFDEF VER6P}
  DotPos: integer;
  Bcd: TBcd;
{$ENDIF}
{$IFDEF HAVE_COMPRESS}
  Compress: boolean;
{$ENDIF}
  dt: TDateTime;
  AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond: Word;
  DBTimeStamp: TDBTimeStamp;
  DBNumeric: TDBNumeric;
  Stream: ISequentialStream;
  OLEDBStream: TOLEDBStream;
begin
  {OFS('-------');
  OFS('[' + IntToStr(pBind.iOrdinal) + ']');
  OFS('pBind.eParamIO=' + IntToStr(pBind.eParamIO));
  OFS('pBind.obValue=' + IntToStr(pBind.obValue));
  OFS('pBind.obLength=' + IntToStr(pBind.obLength));
  OFS('pBind.obStatus=' + IntToStr(pBind.obStatus));
  OFS('pBind.wType=' + IntToStr(pBind.wType));
  OFS('pBind.dwFlags=' + IntToStr(pBind.dwFlags));
  OFS('pBind.bPrecision=' + IntToStr(pBind.bPrecision));
  OFS('pBind.bScale=' + IntToStr(pBind.bScale));
  OFS('pBind.cbMaxLen=' + IntToStr(pBind.cbMaxLen));
  OFS('ParamDesc.GetNull=' + BoolToStr(ParamDesc.GetNull, True));
  OFS('ParamDesc.GetParamType=' + IntToStr(Integer(ParamDesc.GetParamType)));
  OFS('ParamDesc.GetDataType=' + IntToStr(ParamDesc.GetDataType));}
  if pBind.eParamIO in [DBPARAMIO_INPUT, DBPARAMIO_INPUT + DBPARAMIO_OUTPUT] then begin
    pStatus := PDWORD(UINT(Integer(ParamsAccessorData.ExecuteParams.pData)) + pBind.obStatus);
    pValue := IntPtr(UINT(Integer(ParamsAccessorData.ExecuteParams.pData)) + pBind.obValue); // Destination

    if TOLEDBParamDesc(ParamDesc).UseDefaultValue then begin
      FillChar(pValue, pBind.cbMaxLen, 0);
      Marshal.WriteInt32(pStatus, Integer(DBSTATUS_S_DEFAULT));
    end
    else
    if ParamDesc.GetNull then begin
      FillChar(pValue, pBind.cbMaxLen, 0);
      Marshal.WriteInt32(pStatus, Integer(DBSTATUS_S_ISNULL));
    end
    else
    begin
      Marshal.WriteInt32(pStatus, Integer(DBSTATUS_S_OK));

      pLength := PUINT(UINT(Integer(ParamsAccessorData.ExecuteParams.pData)) + pBind.obLength);
      ParamVarType := VarType(ParamDesc.Value);
      // pParamData := @TVarData(ParamDesc.Value); // Source

      if (((ParamDesc.GetParamType = pdInput) and (ParamDesc.GetDataType in CharsByRef + BytesByRef){$IFDEF CLR}and (ParamDesc.GetDataType <> dtString){$ENDIF})
        or (ParamDesc.GetDataType in [dtBlob, dtMemo, dtWideMemo, dtMSXML])) and (ServerVersion <> 3) and (not IsOutputLOB(ParamDesc, ServerVersion, ClientVersion))
        then begin
        // Optimization for input-only parameters, store by ref
        if ParamVarType = varArray + varByte then begin
        {$IFDEF CLR}
          l := Length(ParamsGC);
          SetLength(ParamsGC, l + 1);
          ParamsGC[l] := AllocGCHandle(ParamDesc.Value, True);
          ParamVarPtr := GetAddrOfPinnedObject(ParamsGC[l]);
          l := VarArrayHighBound(ParamDesc.Value, 1) - VarArrayLowBound(ParamDesc.Value, 1) + 1;
        {$ELSE}
          ParamVarPtr := TVarData(ParamDesc.Value).VArray.Data;
          l := TVarData(ParamDesc.Value).VArray.Bounds[0].ElementCount;
        {$ENDIF}
          Marshal.WriteIntPtr(pValue, ParamVarPtr);
          Marshal.WriteInt32(pLength, l);
        end
        else
      {$IFDEF CLR}
        if ParamDesc.Value is TBlob then begin
          Assert(ParamDesc.Value <> nil);
          Blob := TBlob(ParamDesc.Value);
      {$ELSE}
        if ParamVarType = varByRef then begin
          Assert(TVarData(ParamDesc.Value).VPointer <> nil);
          // Assert(TObject(TVarData(ParamDesc.Value).VPointer) is TBlob); - trial
          Blob := TVarData(ParamDesc.Value).VPointer;
      {$ENDIF}
          Blob.Defrag;
        {$IFDEF HAVE_COMPRESS}
          Compress := Blob is TCompressedBlob;
          if Compress then
            TCompressedBlob(Blob).Compressed := (CompressBlobMode = cbServer) or (CompressBlobMode = cbClientServer);
          if Compress and TCompressedBlob(Blob).Compressed then
            Marshal.WriteInt32(pLength, Integer(TCompressedBlob(Blob).CompressedSize))
          else
        {$ENDIF}
            Marshal.WriteInt32(pLength, Integer(Blob.Size));
          if IntPtr(Blob.FirstPiece) = nil then
            Marshal.WriteIntPtr(pValue, nil)
          else
            Marshal.WriteIntPtr(pValue, IntPtr(Integer(Blob.FirstPiece) + sizeof(TPieceHeader)));
        end
        else
        begin // CharsByRef Input parameter
        {$IFDEF CLR}
          l := Length(ParamsGC);
          SetLength(ParamsGC, l + 1);
          ParamsGC[l] := AllocGCHandle(ParamDesc.Value, True);
          ParamVarPtr := GetAddrOfPinnedObject(ParamsGC[l]);
        {$ELSE}
          ParamVarPtr := TVarData(ParamDesc.Value).VPointer;
          if (ParamVarPtr = nil) and (ParamDesc.Value = '') then
            ParamVarPtr := PChar(EmptyString);
        {$ENDIF}
          Marshal.WriteIntPtr(pValue, ParamVarPtr);
          if (ParamDesc.GetDataType in CharsByRef) then
            if ParamVarPtr <> nil then begin
            {$IFDEF CLR}
              l := Length(ParamDesc.Value) * SizeOf(WideChar);
            {$ELSE}
              if ParamVarType = varOleStr then // WideString
                l := Integer(StrLenW(ParamVarPtr) * SizeOf(WideChar))
              else // Pascal string
                l := StrLen(ParamVarPtr)
            {$ENDIF}
            end
            else
              l := 0
          else
            l := Integer(ParamDesc.GetSize);
          Marshal.WriteInt32(pLength, l);
        end;
      end
      else
        case ParamDesc.GetDataType of
          dtUnknown:
            SetOleVariant(pValue, ParamDesc.Value);
          dtString:
          begin
            s := ParamDesc.Value;
            l := Length(s);
            if l > pBind.cbMaxLen - 1{#0} then // Truncate too long values
              l := pBind.cbMaxLen - 1{#0};
            Marshal.WriteInt32(pLength, l);

            if l > 0 then
              CopyBufferAnsi(s, pValue, l + 1{#0});
          end;
          dtWideString:
          begin
            ws := ParamDesc.Value;
            l := Length(ws) * sizeof(WideChar);
            if l > (pBind.cbMaxLen - 1{#0}) * sizeof(WideChar) then // Truncate too long values
              l := (pBind.cbMaxLen - 1{#0}) * sizeof(WideChar);
            Marshal.WriteInt32(pLength, l);

            if l > 0 then
              CopyBufferUni(ws, pValue, l + 2{#0#0});
          end;
          dtBytes, dtVarBytes:
          begin
            case ParamVarType of
              varArray + varByte:
              begin
              {$IFDEF CLR}
                b := ParamDesc.Value; 
                l :=  Length(b);
                if l > pBind.cbMaxLen then // Truncate too long values
                  l := pBind.cbMaxLen;
                Marshal.Copy(b, 0, pValue, l);
              {$ELSE}
                l :=  TVarData(ParamDesc.Value).VArray.Bounds[0].ElementCount;
                if l > pBind.cbMaxLen then // Truncate too long values
                  l := pBind.cbMaxLen;
                Move(TVarData(ParamDesc.Value).VArray.Data^, pValue^, l);
              {$ENDIF}
                Marshal.WriteInt32(pLength, l);
              end;
              varOleStr:
              begin
                s := ParamDesc.Value;
                l :=  Length(s);
                if l > pBind.cbMaxLen {without #0} then // Truncate too long values
                  l := pBind.cbMaxLen {without #0};
                Marshal.WriteInt32(pLength, l);

                if l > 0 then
                  CopyBufferAnsi(s, pValue, l);
              end;
              else
              {$IFDEF LITE}
                raise Exception.Create('Unknown BLOB field type (must be varArray + varByte, varOleStr)');
              {$ELSE}
                raise EDatabaseError.Create('Unknown BLOB field type (must be varArray + varByte, varOleStr)');
              {$ENDIF}
            end;
          end;
          dtInt8:
            Marshal.WriteInt16(pValue, Byte(ParamDesc.Value));
          dtInt16:
            Marshal.WriteInt16(pValue, SmallInt(ParamDesc.Value));
          dtWord:
            if (ServerVersion = 3) and (pBind.wType = DBTYPE_UI1) then
              Marshal.WriteByte(pValue, Byte(ParamDesc.Value))
            else
              Marshal.WriteInt16(pValue, SmallInt(Word(ParamDesc.Value)));
          dtInt32:
            Marshal.WriteInt32(pValue, Integer(ParamDesc.Value));
          dtFloat:
            if ServerVersion = 3 then begin
              if (pBind.wType = DBTYPE_R4) then
                Marshal.WriteInt32(pValue, BitConverter.ToInt32(BitConverter.GetBytes(Single(ParamDesc.Value)), 0))
              else
                if (pBind.wType = DBTYPE_NUMERIC) then begin
                  DBNumeric := DoubleToDBNumeric(Double(ParamDesc.Value), pBind.bPrecision, pBind.bScale);
                {$IFDEF CLR}
                  Marshal.StructureToPtr(TObject(DBNumeric), pValue, False);
                {$ELSE}
                  PDBNumeric(pValue)^ := DBNumeric;
                {$ENDIF}
                end
                else
                  Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(Double(ParamDesc.Value)));
            end
            else
              Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(Double(ParamDesc.Value)));
          dtCurrency: begin
            if (ServerVersion = 3) and (pBind.wType = DBTYPE_CY) then begin
            {$IFDEF CLR}
              c := ParamDesc.Value;
              d := c;
              d := d * 10000;
              i64 := Convert.ToInt64(d);
              Marshal.WriteInt64(pValue, i64);
            {$ELSE}
              PCurrency(pValue)^ := ParamDesc.Value;
            {$ENDIF}
            end
            else
              Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(Double(ParamDesc.Value)));
            // PCurrency(pValue)^ := ParamDesc.Value; Currency type cannot be used over TCurrencyField uses double to store
          end;
          dtDate:
            Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(TDateTime(ParamDesc.Value)));
          dtDateTime, dtTime:
          begin
            dt := TDateTime(ParamDesc.Value);
            DecodeDateTime(dt, AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond);
            DBTimeStamp.year := AYear;
            DBTimeStamp.month := AMonth;
            DBTimeStamp.day := ADay;
            DBTimeStamp.hour := AHour;
            DBTimeStamp.minute := AMinute;
            DBTimeStamp.second := ASecond;
            DBTimeStamp.fraction := AMilliSecond * 1000000 ; // milliseconds to billionths of a second
          {$IFDEF CLR}
            Marshal.StructureToPtr(TObject(DBTimeStamp), pValue, False);
          {$ELSE}
            PDBTimeStamp(pValue)^ := DBTimeStamp;
          {$ENDIF}
          end;
          dtBoolean:
            Marshal.WriteInt16(pValue, SmallInt(WordBool(Boolean(ParamDesc.Value)))); // Convert to boolean is useful to bypass Delphi bug
          dtInt64:
          begin
          {$IFDEF VER6P}
            i64 := ParamDesc.Value; // Explicit Convert!
            Marshal.WriteInt64(pValue, i64);
          {$ELSE}
            if ParamVarType in [$000E, $0014] then
              PInt64(pValue)^ := PInt64(@TVarData(ParamDesc.Value).VInteger)^
            else
              PInt64(pValue)^ := TVarData(ParamDesc.Value).VInteger;
          {$ENDIF}
          end;
        {$IFDEF VER5P}
          dtGuid:
          {$IFDEF CLR}
            Marshal.StructureToPtr(TObject(StringToGUID(ParamDesc.Value)), pValue, False);
          {$ELSE}
            PGuid(pValue)^ := StringToGUID(ParamDesc.Value);
          {$ENDIF}
          dtVariant:
            SetOleVariant(pValue, ParamDesc.Value);
        {$ENDIF}
          dtBCD:
            if (ServerVersion = 3) then begin
              Assert(pBind.wType = DBTYPE_NUMERIC);
              DBNumeric := DoubleToDBNumeric(ParamDesc.Value, 0, 0);
            {$IFDEF CLR}
              Marshal.StructureToPtr(TObject(DBNumeric), pValue, False);
            {$ELSE}
              PDBNumeric(pValue)^ := DBNumeric;
            {$ENDIF}
            end
            else begin
            {$IFNDEF VER6P}
              if ParamVarType in [$000E, $0014] then
                PCurrency(pValue)^ := PInt64(@TVarData(ParamDesc.Value).VInteger)^
              else
            {$ENDIF}
              begin
                c := ParamDesc.Value;
              {$IFDEF CLR}
                d := c;
                d := d * 10000;
                i64 := Convert.ToInt64(d);
              {$ELSE}
                i64 := PInt64(@c)^;
              {$ENDIF}
                Marshal.WriteInt64(pValue, i64);
              end;
            end;
        {$IFDEF VER6P}
          dtFmtBCD:
            if (ServerVersion = 3) then begin
              Assert(pBind.wType = DBTYPE_NUMERIC);
              if VarIsFMTBcd(ParamDesc.Value) then
              //Assert(VarIsFMTBcd(ParamDesc.Value));
                DBNumeric := BcdToDBNumeric(VarToBcd(ParamDesc.Value))
              else begin
                s := ParamDesc.Value;
                if DecimalSeparator <> '.' then begin
                  DotPos := Pos(DecimalSeparator, s);
                  if DotPos <> 0 then
                    s[DotPos] := '.';
                end;
                Bcd := StrToBcd(s);
                DBNumeric := BcdToDBNumeric(Bcd);
              end;
            {$IFDEF CLR}
              Marshal.StructureToPtr(TObject(DBNumeric), pValue, False);
            {$ELSE}
              PDBNumeric(pValue)^ := DBNumeric;
            {$ENDIF}
            end
            else
            begin
              s := ParamDesc.Value;
              if DecimalSeparator <> '.' then begin
                DotPos := Pos(DecimalSeparator, s);
                if DotPos <> 0 then
                  s[DotPos] := '.';
              end;
              l := Length(s);
              Marshal.WriteInt32(pLength, l);

              if l > 0 then
                CopyBufferAnsi(s, pValue, l + 1{#0});
            end;
        {$ENDIF}
          dtMemo, dtWideMemo, dtBlob:
            if pBind.wType = DBTYPE_IUNKNOWN then begin
              // Create stream
              OLEDBStream := TOLEDBStream.Create(ParamDesc.GetObject as TBlob, nil{FStreamList});
              Stream := OLEDBStream;
              Marshal.WriteIntPtr(pValue, Marshal.GetIUnknownForObject(OLEDBStream));
              // Set stream size
              Marshal.WriteInt32(pLength, Integer(OLEDBStream.Size));
            end
            else begin
              ws := (ParamDesc.GetObject as TBlob).AsWideString;
              l := Length(ws) * sizeof(WideChar);
              if l > (pBind.cbMaxLen - 1{#0}) * sizeof(WideChar) then // Truncate too long values
                l := (pBind.cbMaxLen - 1{#0}) * sizeof(WideChar);
              Marshal.WriteInt32(pLength, l);

              if l > 0 then
                CopyBufferUni(ws, pValue, l + 2{#0#0});
            end;
          else
            Assert(False, Format('ParamDesc - %s, Unknown DataType = %d', [ParamDesc.GetName, ParamDesc.GetDataType]));
        end;
      end;
  end;
end;

procedure TOLEDBCommand.CreateAndFillParamAccs;
var
  ParamCnt: integer;

  procedure PrepareParamAcc(out BindMemorySize: UINT); // Set preliminary binding data
  var
    i: integer;
  begin
    Assert(Length(FParamsAccessorData.rgBindings) <> 0);
    for i := 0 to ParamCnt - 1 do
      FParamsAccessorData.rgBindings[i].pObject := nil;

    BindMemorySize := 0;
    for i := 0 to ParamCnt - 1 do
      FillBindingForParam(i + 1, TOLEDBParamDesc(FParams[i]), FConnection, FParamsAccessorData.rgBindings[i], BindMemorySize, True, False);
  end;

var
{$IFDEF CLR}
  rgBindingsGC: GCHandle;
{$ENDIF}
  rgBindings: PDBBinding;
  rgStatus: PUINT;
  i: integer;
  BindMemorySize: UINT;

begin
  Assert(FICommandText <> nil, 'FICommandText must be setted to CreateAndFillParamAccs');
  Assert(not FParamsAccessorDataAvaible, 'procedure CreateAndFillParamAccs already called');
  ParamCnt := FParams.Count;
  rgStatus := Marshal.AllocHGlobal(ParamCnt * SizeOf(UINT));
{$IFNDEF CLR}
  IntPtr(FParamsAccessorData.Accessor) := nil;
{$ENDIF}

  FCanReadParams := False;

  try
    try
      FParamsAccessorData.ExecuteParams.HACCESSOR := 0;
      FParamsAccessorData.ExecuteParams.pData := nil;
      FParamsAccessorData.ExecuteParams.cParamSets := 1;

      SetLength(FParamsAccessorData.rgBindings, ParamCnt);
      PrepareParamAcc(BindMemorySize);

      FParamsAccessorData.ExecuteParams.pData := Marshal.AllocHGlobal(BindMemorySize);
      FillChar(FParamsAccessorData.ExecuteParams.pData, BindMemorySize, 0);

      for i := 0 to ParamCnt - 1 do
        SaveParamValue(FParams[i], FParamsAccessorData.rgBindings[i],
          FParamsAccessorData{$IFDEF HAVE_COMPRESS}, FCompressBlob{$ENDIF}
          {$IFDEF CLR}, FParamsGC{$ENDIF}, FConnection.DBMSPrimaryVer, FConnection.ProviderPrimaryVer);


      QueryIntf(FICommandText, {$IFDEF CLR}IAccessor{$ELSE}IID_IAccessor{$ENDIF}, FParamsAccessorData.Accessor);
    {$IFDEF CLR}
      rgBindingsGC := GCHandle.Alloc(FParamsAccessorData.rgBindings, GCHandleType.Pinned);
      rgBindings := Marshal.UnsafeAddrOfPinnedArrayElement(FParamsAccessorData.rgBindings, 0);
    {$ELSE}
      rgBindings := @FParamsAccessorData.rgBindings[0];
    {$ENDIF}
      Check(FParamsAccessorData.Accessor.CreateAccessor(
        DBACCESSOR_PARAMETERDATA, ParamCnt, rgBindings, BindMemorySize,
        FParamsAccessorData.ExecuteParams.HACCESSOR, rgStatus));
      FParamsAccessorDataAvaible := True;
    except
      if FParamsAccessorData.ExecuteParams.pData <> nil then
        Marshal.FreeHGlobal(FParamsAccessorData.ExecuteParams.pData);
      FParamsAccessorData.ExecuteParams.pData := nil;

      if Length(FParamsAccessorData.rgBindings) <> 0 then begin
        for i := 0 to ParamCnt - 1 do
        begin
          if FParamsAccessorData.rgBindings[i].pObject <> nil then
            Marshal.FreeHGlobal(FParamsAccessorData.rgBindings[i].pObject);
        end;
        SetLength(FParamsAccessorData.rgBindings, 0);
      end;

      FParamsAccessorDataAvaible := False;
      raise;
    end;
  finally
    Marshal.FreeHGlobal(rgStatus);
  {$IFDEF CLR}
    if IntPtr(rgBindingsGC) <> nil then
      rgBindingsGC.Free;
  {$ENDIF}
  end;
end;

procedure TOLEDBCommand.Execute(Iters: integer = 1);
  procedure DoExecute;
  var
    AsynchComplete: boolean;
    
    function OpenOrExec: IUnknown;
    var
      Params: DBPARAMS;
      RequestInt: TGUID;
      hr: HRESULT;

    begin
      if not GetPrepared then
        SetCommandProp;

      if FRequestIUnknown or (FNonBlocking and (not FRequestIUnknown)) then
        RequestInt := IID_IUnknown
      else
        RequestInt := IID_NULL;

      FWaitForBreak := False;
      try

        if FRPCCall then
          SetParameterInfo;
        if FParams.Count = 0 then begin
          Params.HACCESSOR := 0;
          Params.pData := nil;
          Params.cParamSets := 0;
          hr := ICommand(FICommandText).Execute(nil, RequestInt, Params, FRowsAffected, Result);
        end
        else
        begin
          CreateAndFillParamAccs;
          hr := ICommand(FICommandText).Execute(nil, RequestInt, FParamsAccessorData.ExecuteParams, FRowsAffected, Result);
        end;
        
        AsynchComplete := True;
        if FNonBlocking and (hr = DB_S_ASYNCHRONOUS) then
          AsynchComplete := False
        else
          CheckAndAnalyze(hr);

      {$IFDEF SDAC_TEST}
        Inc(__ServerExecuteCount);
      {$ENDIF}
      except
        on E: Exception do begin
          if (E is EOLEDBError) and (EOLEDBError(E).ErrorCode = DB_E_ABORTLIMITREACHED) then
            Unprepare;
          Result := nil;
          FIUnknown := nil;
          ClearIMultipleResults;
          FISSAsynchStatus := nil;

          raise;
        end;
      end;
    end;

  var
    IUnk: IUnknown;
  begin
    Assert(FIUnknown = nil);
    FLastExecWarning := False;
    if FIMultipleResults = nil then begin
      IUnk := OpenOrExec; // After call IUnk may be [nil, IUnknown (message), IUnknown (cursor)]
      Assert(FRequestIUnknown or FNonBlocking or (IUnk = nil));

      if FRequestIUnknown and (IUnk <> nil) then begin
        if FRequestMultipleResults then
          QueryIntf(IUnk, {$IFDEF CLR}IMultipleResults{$ELSE}IID_IMultipleResults{$ENDIF}, FIMultipleResults)
        else // This is a server cursor or DbxSda call
          FIUnknown := IUnk;
        IUnk := nil;
      end;
      
      if not FRequestIUnknown and FNonBlocking and not AsynchComplete then begin
        Assert(IUnk <> nil);
        QueryIntf(IUnk, {$IFNDEF CLR}IID_ISSAsynchStatus{$ELSE}ISSAsynchStatus{$ENDIF}, FISSAsynchStatus);
      end;
    end;

    if FRequestIUnknown and (FIMultipleResults <> nil) then
      if FIUnknownNext = nil then
        GetNextResult(FIUnknown, FRowsAffected)
      else
      begin
        FIUnknown := FIUnknownNext;
        FIUnknownNext := nil;
        FRowsAffected := FRowsAffectedNext;
      end;
  end;
begin
  if (FCursorState <> csInactive) and (FCursorState <> csPrepared) then
    Exit;

  FExecuting := True;
  SetCursorState(csExecuting);
  QueryInterfaces(False); // If QueryInterfaces already called then do nothing
  try
    DoExecute;
    if FNonBlocking and (FISSAsynchStatus <> nil) then begin
      Assert(FExecutor = nil);
      FExecutor := TOLEDBThreadWrapper.Create(TExecuteThread, True);
      FExecutor.OnException := DoExecuteException;
      FExecutor.OnTerminate := DoExecuteTerminate;
      TExecuteThread(FExecutor.FThread).FRunMethod := WaitAsynchCompletion;
      FExecutor.FreeOnTerminate := True;
      FExecutor.Resume;
    end;
  except
    on E: Exception do begin
      if (not FNonBlocking) or FRequestIUnknown or (FISSAsynchStatus = nil) then
        EndExecute(E);
      raise;
    end;
  end;
  if (not FNonBlocking) or FRequestIUnknown or (FISSAsynchStatus = nil) then
    EndExecute(nil);
end;

procedure TOLEDBCommand.DoExecuteTerminate(Sender: TObject); // MainThread context
begin
  EndExecute(FExecutor.FException);
  FExecutor := nil;
end;

procedure TOLEDBCommand.DoExecuteException(Sender: TObject; E: Exception; var Fail: boolean); // MainThread context
begin
  if (E is EOLEDBError) then
    FConnection.DoError(EOLEDBError(E), Fail);
end;

procedure TOLEDBCommand.WaitAsynchCompletion; // FExecuter.FThread context
var
  hr: HRESULT;
  Completed: boolean;
begin
  if (FISSAsynchStatus <> nil) then begin
    try
      Completed := False;
      while not TExecuteThread(FExecutor.FThread).Terminated do begin
        hr := FISSAsynchStatus.WaitForAsynchCompletion(100);
        Completed := hr <> DB_S_ASYNCHRONOUS;
        if Completed then
          break;
      end;
      if not Completed then
        FISSAsynchStatus.Abort(DB_NULL_HCHAPTER, DBASYNCHOP_OPEN);
    finally
      ClearISSAsynchStatus;
    end;
  end;
end;

procedure TOLEDBCommand.EndExecute(E: Exception); // MainThread context
begin
  try
    RequestParamsIfPossible;
    ReleaseInterfaces;

    
    if FIUnknown = nil then
      FCursorState := csInactive
    else
      FCursorState := csExecuted;

    if Assigned(FAfterExecute) then // Must be after RequestParamsIfPossible
      FAfterExecute(E = nil);
  finally
    FExecuting := False;
  end;
end;

function TOLEDBCommand.CreateProcCall(Name: string; const NeedDescribe: boolean; const WideStrings: boolean;
  const EnableBcd: boolean; const EnableFmtBcd: boolean): string;
  
  procedure DescribeParams(const MasterDatabase: boolean; out OriginalDatabase: string);
  var
    ParamsMetaInfo: TOLEDBRecordSet;

    procedure FillParams;
      function GetOffsetByName(const Name: string): integer;
      var
        i: integer;
      begin
        for i := 0 to ParamsMetaInfo.Fields.Count - 1 do
          if ParamsMetaInfo.Fields[i].Name = Name then begin
            Result := ParamsMetaInfo.Fields[i].Offset;
            Exit;
          end;
        Result := - maxint;
        Assert(False, 'Unknown field name ' + Name);
      end;

    var
      NameFld, TypeNameFld, TypeFld, HasDefaultFld, DefaultFld, DataTypeFld, OctetLengthFld, CharMaxLenFld: integer; // offsets

      Param: TParamDesc;
      ParamType: TParamDirection;

      s, ParamName: string;
      DataType: word;

      IsLong: boolean;

      RecBuf: IntPtr;
      ws: WideString;
      dt: integer;
    begin
      FParams.Clear;

      NameFld := GetOffsetByName('PARAMETER_NAME'); // DataSize = 129
      TypeNameFld := GetOffsetByName('TYPE_NAME'); // DataSize = 129
      TypeFld := GetOffsetByName('PARAMETER_TYPE'); // DataSize = 4
      HasDefaultFld := GetOffsetByName('PARAMETER_HASDEFAULT'); // DataSize = 2
      DefaultFld := GetOffsetByName('PARAMETER_DEFAULT'); // DataSize = 256
      DataTypeFld := GetOffsetByName('DATA_TYPE'); // DataSize = 4
      OctetLengthFld := GetOffsetByName('CHARACTER_OCTET_LENGTH'); // DataSize = 4
      CharMaxLenFld := GetOffsetByName('CHARACTER_MAXIMUM_LENGTH'); // DataSize = 4

      RecBuf := nil;
      ParamsMetaInfo.AllocRecBuf(IntPtr(RecBuf));
      try
        while True do begin
          ParamsMetaInfo.GetNextRecord(RecBuf);
          if ParamsMetaInfo.Eof then
            Break;

          IsLong := Marshal.ReadInt32(RecBuf, OctetLengthFld) >= maxInt - 1;
          dt := Marshal.ReadInt32(RecBuf, DataTypeFld);

          if ConvertOLEDBTypeToInternalFormat(dt, IsLong, EnableBcd, {$IFDEF VER6P}EnableFMTBCD{$ELSE}False{$ENDIF}, WideStrings, True, DataType, FConnection.DBMSPrimaryVer) then begin
            ParamType := ConvertOLEDBParamTypeToCR(Marshal.ReadInt32(RecBuf, TypeFld));
            Param := TOLEDBParamDesc.Create;
            Param.SetParamType(ParamType);
            FParams.Add(Param);

            //Choice "Bytes" and "VarBytes"
            s := Marshal.PtrToStringUni(IntPtr(Integer(RecBuf) + TypeNameFld));
            if (DataType = dtBytes) and (s = 'varbinary') then // WAR user-defined types?
              DataType := dtVarBytes;
            Param.SetDataType(DataType);

            ParamName := GetParamNameWODog(Marshal.PtrToStringUni(IntPtr(Integer(RecBuf) + NameFld)));
            Param.SetName(ParamName);

            if Word(Marshal.ReadInt16(RecBuf, HasDefaultFld)) <> 0 then begin
              ws := Marshal.PtrToStringUni(IntPtr(Integer(RecBuf) + DefaultFld));
              Param.SetValue(ws);
            end;

            if IsLargeDataTypeUsed(Param) and
              ((ParamType = pdOutput) or (ParamType = pdInputOutput)) then
              Param.SetParamType(pdInput);

            Param.SetSize(Marshal.ReadInt32(RecBuf, CharMaxLenFld));
          end
          else begin
            s := Marshal.PtrToStringUni(IntPtr(Integer(RecBuf) + TypeNameFld));
            DatabaseErrorFmt(SBadFieldType, [s, dt]);
          end;
        end;
      finally
        if RecBuf <> nil then
          ParamsMetaInfo.FreeRecBuf(RecBuf);
      end;
    end;

    procedure ParseFullName(out Database: string; out Owner: string; out ProcName: string);
    begin
      UnbracketIfPossible(Name, OriginalDatabase, Owner, ProcName);
      if OriginalDatabase <> '' then
        Database := OriginalDatabase
      else
        if not MasterDatabase then
          Database := FConnection.FDatabase
        else
          Database := DefaultSDACDatabase;
    end;
  var
    Database, Owner, ProcName: string;
    rgRestrictions: TRestrictions;

    Rowset: IRowset;

  begin
    ParseFullName(Database, Owner, ProcName);
    SetLength(rgRestrictions, 3);
    if (Length(ProcName) > 1) and (ProcName[1] = '#') then // for temporary stored procedures
      rgRestrictions[0] := 'tempdb'
    else
      rgRestrictions[0] := DataBase;
    rgRestrictions[1] := Owner;
    rgRestrictions[2] := ProcName;


    Rowset := FConnection.GetSchemaRowset(DBSCHEMA_PROCEDURE_PARAMETERS, rgRestrictions);

    ParamsMetaInfo := nil;
    try
      ParamsMetaInfo := TOLEDBRecordSet.Create;
      ParamsMetaInfo.SetConnection(FConnection);
      ParamsMetaInfo.SetProp(prFetchAll, True);

      ParamsMetaInfo.SetIRowset(Rowset, False);
      ParamsMetaInfo.Open;

      FillParams;
    finally
      if ParamsMetaInfo <> nil then begin
        ParamsMetaInfo.Close;
        ParamsMetaInfo.UnPrepare;
        ParamsMetaInfo.Free;
      end;
    end;
  end;

var
  i: integer;
  BracketAdded: boolean;
  Database, s, s1: string;
  ParamName: string;
begin
  if FConnection = nil then
    DatabaseError(SConnectionNotDefined);

  FConnection.Connect('');

  if NeedDescribe then begin
    DescribeParams(False, Database);
    if (FParams.Count = 0) and (Database = '') then begin
      s := LowerCase(Copy(Name, 1, 3));
      if (s = 'sp_') or (s = 'xp_') then
        DescribeParams(True, Database);
    end;
  end;

  i := Pos(';', Name);
  if i = 0 then
    s := 'CALL ' + BracketIfNeed(Name, Char('"'), Char('"'){May be bug in MS SQL Server}) + ' '
  else begin
    s := Name;
    s1 := Copy(Name, i, 1024);
    Delete(s, i, 1024);
    s := 'CALL ' + BracketIfNeed(s, Char('"'), Char('"'){May be bug in MS SQL Server}) + s1
  end;

  BracketAdded := False;
  for i := 0 to FParams.Count - 1 do begin
    ParamName := FParams[i].GetName;
    if ParamName = '' then
      ParamName := '?'
    else
      ParamName := ':' + ParamName;
    if FParams[i].GetParamType = pdResult then
      s := ParamName + ' = ' + s
    else begin
      if BracketAdded then
        s := s + ', ' + ParamName
      else begin
        BracketAdded := True;
        s := s + '(' + ParamName
      end;
    end;
  end;
  if BracketAdded then
    s := s + ')';
  FSQL := '{' + s + '}';
  Result := FSQL;
end;

procedure ConvertStreamToBlob(const pValue: IntPtr; const Length: integer; Blob: TBlob;
  {$IFDEF HAVE_COMPRESS}CompressBlobMode: TCompressBlobMode;{$ENDIF} OmitXMLPreamble: boolean = False);
var
  Stream: ISequentialStream;
  BytesReadedFromStream: Longint;
  p, pXML: IntPtr;

  Piece: PPieceHeader;
  PieceSize: integer;
{$IFDEF CLR}
  o: System.Object;
  otype: System.Type;
{$ENDIF}
  pBytesReadedFromStream: IntPtr;
  FirstRead: boolean;
  BlobSize: integer;
  SizeAvailable: boolean;
begin
  p := Marshal.ReadIntPtr(pValue);
  Assert(p <> nil);

  BlobSize := Length;
  SizeAvailable := BlobSize > 0;
{$IFDEF CLR}
  otype := System.Type.GetTypeFromCLSID(CLSID_SQLOLEDB);
  o := Marshal.GetObjectForIUnknown(p);
  Stream := ISequentialStream(Marshal.CreateWrapperOfType(o, otype));
  o := nil;
{$ELSE}
  IntPtr(Stream) := IntPtr(p);
{$ENDIF}
  pBytesReadedFromStream := Marshal.AllocHGlobal(SizeOf(Integer));
  try
    Assert(Stream <> nil);
    
    if OmitXMLPreamble then begin
      // Skip FF FE bytes, CR 16149
      pXML := Marshal.AllocHGlobal(SizeOf(WideChar));
      try
        Stream.Read(pXML, SizeOf(WideChar), pBytesReadedFromStream);
        if SizeAvailable then
          Dec(BlobSize, SizeOf(WideChar));
      finally
        Marshal.FreeHGlobal(pXML);
      end;
    end;

    PieceSize := 10 * 1024;
    FirstRead := True;
    repeat
      if (BlobSize >= DefaultPieceSize) or not SizeAvailable then begin
        if not FirstRead then
          PieceSize := DefaultPieceSize;
      end
      else
        PieceSize := BlobSize;

      Blob.AllocPiece(Piece, PieceSize);
      try
      {$IFDEF CLR}
        Stream.Read(IntPtr(Integer(Piece) + SizeOf(TPieceHeader)), PieceSize, pBytesReadedFromStream);
        BytesReadedFromStream := Marshal.ReadInt32(pBytesReadedFromStream);
      {$ELSE}
        Stream.Read(IntPtr(Integer(Piece) + SizeOf(TPieceHeader)), PieceSize, @BytesReadedFromStream);
      {$ENDIF}
        Piece.Used := BytesReadedFromStream;
        if SizeAvailable then
          Dec(BlobSize, BytesReadedFromStream);
      finally
        Blob.CompressPiece(Piece);
        if IntPtr(Piece) <> nil then
          Blob.AppendPiece(Piece);
        FirstRead := False;
      end;
    until ((BlobSize = 0) and SizeAvailable) or ((PieceSize <> BytesReadedFromStream) and not SizeAvailable);
  {$IFDEF HAVE_COMPRESS}
    if (Blob is TCompressedBlob) and ((CompressBlobMode = cbClient) or (CompressBlobMode = cbClientServer)) then
      TCompressedBlob(Blob).Compressed := True;
  {$ENDIF}
  finally
  {$IFDEF CLR}
    Marshal.ReleaseComObject(Stream);
    Marshal.Release(p);
  {$ENDIF}
    Marshal.FreeHGlobal(pBytesReadedFromStream);
  end;
end;

procedure TOLEDBCommand.RequestParamsIfPossible; // Call RequestAndFreeParamAccs if interfaces is cleared
  procedure RequestAndFreeParamAccs;
    procedure ProcessParam(ParamDesc: TParamDesc; var pBind: TDBBinding);
    var
      pStatus: PDWORD;
      Status: DWORD;
      pValue: IntPtr;
      pLength: PUINT;
      l: UINT;

      c: Currency;
      i64: Int64;

    {$IFDEF CLR}
      Bcd: TBcd;
      b: TBytes;
      d: double;
    {$ENDIF}
      DBTimeStamp: TDBTimeStamp;
      dt: TDateTime;
    {$IFDEF VER6P}
      DBNumeric: TDBNumeric;
    {$ENDIF}
      Blob: TSharedObject;
    begin
      if (ParamDesc.GetParamType in [pdUnknown, pdInputOutput, pdOutput, pdResult])
        and not ((ParamDesc.GetParamType = pdUnknown) and (ParamDesc.GetDataType in [dtBlob, dtMemo, dtWideMemo, dtMSXML])) then begin
        pStatus :=  PDWORD(UINT(Integer(FParamsAccessorData.ExecuteParams.pData)) + pBind.obStatus);
        Status := DWORD(Marshal.ReadInt32(pStatus));
        if not IsOutputLOB(ParamDesc, FConnection.DBMSPrimaryVer, FConnection.ProviderPrimaryVer) then
          ParamDesc.SetNull((Status = DBSTATUS_S_ISNULL) or (Status = DBSTATUS_E_UNAVAILABLE))
        else begin
          Blob := ParamDesc.GetObject;
          try
            ParamDesc.SetNull((Status = DBSTATUS_S_ISNULL) or (Status = DBSTATUS_E_UNAVAILABLE));
          finally
            ParamDesc.SetObject(Blob);
          end;
        end;

        if ParamDesc.GetNull then
          Marshal.WriteInt32(pStatus, Integer(DBSTATUS_S_ISNULL))
        else
        begin
          pValue := IntPtr(UINT(Integer(FParamsAccessorData.ExecuteParams.pData)) + pBind.obValue);
          pLength := PUINT(UINT(Integer(FParamsAccessorData.ExecuteParams.pData)) + pBind.obLength);
          case ParamDesc.GetDataType of
            dtUnknown:
              ParamDesc.SetValue(GetOleVariant(pValue));
            dtString:
              ParamDesc.SetValue(Marshal.PtrToStringAnsi(pValue));
            dtWideString:
              ParamDesc.SetValue(Marshal.PtrToStringUni(pValue));
            dtBytes, dtVarBytes:
            begin
              l := Marshal.ReadInt32(pLength);
              if l > 0 then begin
              {$IFDEF CLR}
                SetLength(b, l);
                Marshal.Copy(pValue, b, 0, l);
                ParamDesc.Value := b;
              {$ELSE}
                ParamDesc.SetValue(VarArrayCreate([0, l - 1], varByte));
                CopyBuffer(pValue, TVarData(ParamDesc.Value).VArray.Data, l);
              {$ENDIF}
              end;
            end;
            dtInt8:
              ParamDesc.SetValue(Byte(Marshal.ReadByte(pValue)));
            dtInt16:
              ParamDesc.SetValue(SmallInt(Marshal.ReadInt16(pValue)));
            dtWord:
              ParamDesc.SetValue(Word(Marshal.ReadInt16(pValue)));
            dtInt32:
              ParamDesc.SetValue(Integer(Marshal.ReadInt32(pValue)));
            dtFloat:
              ParamDesc.SetValue(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(pValue)));
            dtCurrency:
              ParamDesc.SetValue(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(pValue)));
              // ParamDesc.SetValue(PCurrency(pValue)^); Currency type cannot be used over TCurrencyField uses double to store
            dtDate:
              ParamDesc.SetValue(TDateTime(BitConverter.Int64BitsToDouble(Marshal.ReadInt64(pValue))));
            dtDateTime, dtTime:
            begin
            {$IFDEF CLR}
              DBTimeStamp := TDBTimeStamp(Marshal.PtrToStructure(pValue, TypeOf(TDBTimeStamp)));
            {$ELSE}
              DBTimeStamp := PDBTimeStamp(pValue)^;
            {$ENDIF}
              dt := EncodeDateTime(DBTimeStamp.year, DBTimeStamp.month, DBTimeStamp.day, DBTimeStamp.hour, DBTimeStamp.minute, DBTimeStamp.second, DBTimeStamp.fraction div 1000000{Billionths of a second to milliseconds});
              ParamDesc.SetValue(dt);
            end;
            dtBoolean:
              ParamDesc.SetValue(Boolean(WordBool(Marshal.ReadInt16(pValue))));
            dtInt64:
          {$IFDEF VER6P}
              ParamDesc.SetValue(Marshal.ReadInt64(pValue));
          {$ELSE}
            begin
              TVarData(_TParamDesc(ParamDesc).FData).VType := $000E;
              PInt64(@TVarData(ParamDesc.Value).VInteger)^ := PInt64(pValue)^;
            end;
          {$ENDIF}
          {$IFDEF VER5P}
            dtGuid:
            {$IFDEF CLR}
              ParamDesc.SetValue(GUIDToString(TGuid(Marshal.PtrToStructure(pValue, TypeOf(TGuid)))));
            {$ELSE}
              ParamDesc.SetValue(GUIDToString(PGuid(pValue)^));
            {$ENDIF}
            dtVariant:
              ParamDesc.SetValue(GetOleVariant(pValue));
          {$ENDIF}
            dtBCD:
            begin
              i64 := Marshal.ReadInt64(pValue);
            {$IFDEF CLR}
              d := i64;
              d := d / 10000;
              c := d;
            {$ELSE}
              c := PCurrency(@i64)^;
            {$ENDIF}
              ParamDesc.SetValue(c);
            end;
          {$IFDEF VER6P}
            dtFmtBCD:
              if FConnection.FProvider <> prCompact then
                ParamDesc.SetValue(Marshal.PtrToStringAnsi(pValue))
              else begin
              {$IFDEF CLR}
                DBNumeric := TDBNumeric(Marshal.PtrToStructure(pValue, TypeOf(TDBNumeric)));
              {$ELSE}
                DBNumeric := PDBNumeric(pValue)^;
              {$ENDIF}
                ParamDesc.SetValue(VarFMTBcdCreate(DBNumericToBcd(DBNumeric)));
              end;
          {$ENDIF}
            dtMemo, dtWideMemo: begin
              Blob := ParamDesc.GetObject;
              Assert(Blob <> nil);
              TBlob(Blob).Clear;
              ConvertStreamToBlob(pValue, 0, TBlob(Blob){$IFDEF HAVE_COMPRESS}, FCompressBlob{$ENDIF});
            end;
            else
              Assert(False, Format('ParamDesc - %s, Unknown DataType = %d', [ParamDesc.GetName, ParamDesc.GetDataType]));
          end;
        end;
      end;
    end;
  var
    i: integer;

    ParamCnt: integer;

  begin
    Assert(FIUnknown = nil, 'Before RequestAndFreeParamAccs interface FIUnknown must be released');
    Assert(FIMultipleResults = nil, 'Before RequestAndFreeParamAccs interface FIMultipleResults must be released');

    if FParamsAccessorDataAvaible then begin
      ParamCnt := FParams.Count;
      try
        if FConnection <> nil then begin
          if FParamsAccessorData.ExecuteParams.HACCESSOR <> 0 then
            Check(FParamsAccessorData.Accessor.ReleaseAccessor(FParamsAccessorData.ExecuteParams.HACCESSOR, nil));
          FParamsAccessorData.ExecuteParams.HACCESSOR := 0;
          FParamsAccessorData.Accessor := nil;

          for i := 0 to ParamCnt - 1 do
            ProcessParam(FParams[i], FParamsAccessorData.rgBindings[i]);
        end;

        if FParamsAccessorData.ExecuteParams.pData <> nil then
          Marshal.FreeHGlobal(FParamsAccessorData.ExecuteParams.pData);
        FParamsAccessorData.ExecuteParams.pData := nil;
        
      finally
        if Length(FParamsAccessorData.rgBindings) <> 0 then begin
          for i := 0 to ParamCnt - 1 do
            if FParamsAccessorData.rgBindings[i].pObject <> nil then
              Marshal.FreeHGlobal(FParamsAccessorData.rgBindings[i].pObject);
          SetLength(FParamsAccessorData.rgBindings, 0);
        end;

        FParamsAccessorDataAvaible := False;

        (*// Remove unused streams
        for i := FStreamList.Count - 1 downto 0 do
          TOLEDBStream(FStreamList[i])._Release;
        Assert(FStreamList.Count = 0);*)
      end;

      FCanReadParams := True;
      if Assigned(FReadParams) and (FConnection <> nil) then
        FReadParams;
    end;
  end;

{$IFDEF CLR}
var
  i: integer;
{$ENDIF}

begin
{$IFDEF CLR}
  for i := Low(FParamsGC) to High(FParamsGC) do
    FreeGCHandle(FParamsGC[i]);
  SetLength(FParamsGC, 0);
{$ENDIF}

  if (FIUnknown = nil) then begin
    if (FIMultipleResults <> nil) and not FNextResultRequested then begin
      GetNextResult(FIUnknownNext, FRowsAffectedNext);
      FNextResultRequested := True;
    end;

    if (FIMultipleResults = nil) and (FParams <> nil) and (FParams.Count > 0) then
      RequestAndFreeParamAccs;
  end;
end;

procedure TOLEDBCommand.CheckAndAnalyze(const Status: HRESULT);
const
  ParamHeader = 'Parameter[%d] %s - %s (Status = %Xh).';
var
  i: integer;
  ParamStatus: DWORD;
  ParamName: string;
  Msg: WideString;
begin
  if Status <> S_OK then
    try
      Check(Status);
    except
      on E: Exception do
      begin
        if (Status and $80000000) <> 0 then begin // Severity bit (see OLEDBC.pas line 5489) is not 0
          if Status = DB_E_OBJECTOPEN then
            DatabaseError(SObjectOpen)
          else
            if FParamsAccessorData.ExecuteParams.pData = nil then
              raise
            else
            begin
              Msg := '';
              with FParamsAccessorData do
                for i := 0 to FParams.Count - 1 do begin
                  ParamName := FParams[i].GetName;
                  if ParamName = '' then
                    ParamName := IntToStr(i)
                  else
                    ParamName := ':' + ParamName;
                  ParamStatus := DWORD(Marshal.ReadInt32(ExecuteParams.pData, rgBindings[i].obStatus));
                  case ParamStatus of
                    DBSTATUS_S_OK, DBSTATUS_S_ISNULL, DBSTATUS_S_DEFAULT:;
                    DBSTATUS_E_BADACCESSOR:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SInvalidParamType, ParamStatus]);
                    DBSTATUS_E_CANTCONVERTVALUE:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SInvalidValue, ParamStatus]);
                    DBSTATUS_S_TRUNCATED:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SDataTruncated, ParamStatus]);
                    DBSTATUS_E_SIGNMISMATCH:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SSignMismatch, ParamStatus]);
                    DBSTATUS_E_DATAOVERFLOW:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SDataOverflow, ParamStatus]);
                    DBSTATUS_E_CANTCREATE:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SOutOfMemory, ParamStatus]);
                    DBSTATUS_E_UNAVAILABLE:
                      {AddInfoToErr(Msg, ParamHeader, [i, ParamName, SUnavaible, ParamStatus])};
                    DBSTATUS_E_INTEGRITYVIOLATION:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SIntegrityViolation, ParamStatus]);
                    DBSTATUS_E_SCHEMAVIOLATION:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SShemaViolation, ParamStatus]);
                    DBSTATUS_E_BADSTATUS:
                      AddInfoToErr(Msg, ParamHeader, [i, ParamName, SBadStatus, ParamStatus]);
                  else
                    AddInfoToErr(Msg, ParamHeader, [i, ParamName, SUnknownStatus, ParamStatus]);
                  end;
                end; // for
              AddInfoToErr(E, Msg, []);
              raise E;
            end; // if Status = DB_E_OBJECTOPEN
        end
        else // if (Status and $80000000) <> 0 then begin
          if Status = DB_S_ERRORSOCCURRED then
            FLastExecWarning := True;
      end; // on E: Exception do begin
    end; // try..except..end;
end;

procedure TOLEDBCommand.GetNextResult(out ResultSet: IUnknown; out RowsAffected: integer);
var
  hr: HRESULT;
  OldRowsAffected: integer;
begin
  try
    repeat
      OldRowsAffected := FRowsAffected;
      hr := FIMultipleResults.GetResult(nil, 0, IID_IUnknown, RowsAffected, ResultSet);

      if (hr <> DB_S_NORESULT) then
        CheckAndAnalyze(hr);
      FConnection.OLEDBError(0, Component); // Check Info messages

      if ResultSet <> nil then begin
        OldRowsAffected := RowsAffected;
        Break;
      end;

      if FWaitForBreak then begin
        FWaitForBreak := False;
        FConnection.OLEDBError(DB_E_CANCELED, Component);
      end;

    until hr <> S_OK;
    RowsAffected := OldRowsAffected;
  finally
    if ResultSet = nil then
      ClearIMultipleResults; // Rowsets not also provided
  end;

end;

procedure TOLEDBCommand.SetConnection(Value: TCRConnection);
begin
  if Value <> FConnection then begin
    inherited;

    FConnection := TOLEDBConnection(Value);
  end;
end;

function TOLEDBCommand.GetCursorState: TCursorState;
begin
  Result := FCursorState;
end;

procedure TOLEDBCommand.SetCursorState(Value: TCursorState);
begin
  FCursorState := Value;
end;

function TOLEDBCommand.GetProp(Prop: integer; var Value: variant): boolean; 
begin
  Result := True;

  case Prop of
    prRowsProcessed: 
      if FRowsAffected = -1 then
        Value := 0
      else
        Value := FRowsAffected;
    prScanParams:
      Value := False;
    prCanReadParams:
      Value := FCanReadParams;
    prIsSProc:
      Value := FIsSProc;
    prNonBlocking:
      Value := FNonBlocking;
  else
    Result := inherited GetProp(Prop, Value);
  end;
end;

function TOLEDBCommand.SetProp(Prop: integer; const Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prScanParams:;
    prCommandTimeout:
      FCommandTimeout := Value;
    prIsSProc:
      FIsSProc := Boolean(Value);
    prCanReadParams:
      FCanReadParams := Value;
    prSmartRefresh:
      FSmartRefresh := Value;
    prSmartRefreshMsg:
      FSmartRefreshMsg := Value;
    prSmartRefreshService:
      FSmartRefreshService := Value;
    prNonBlocking:
      FNonBlocking := Value;
  else
    Result := inherited SetProp(Prop, Value);
  end;
end;

function TOLEDBCommand.IUnknownIsNull: boolean;
begin
  Result := FIUnknown = nil;
end;

function TOLEDBCommand.IUnknownNextIsNull: boolean;
begin
  Result := FIUnknownNext = nil;
end;

function TOLEDBCommand.IMultipleResultsIsNull: boolean;
begin
  Result := FIMultipleResults = nil;
end;

function TOLEDBCommand.ISSAsynchStatusIsNull: boolean;
begin
  Result := FISSAsynchStatus = nil;
end;

procedure TOLEDBCommand.ClearIUnknown;
begin
  FIUnknown := nil;
end;

procedure TOLEDBCommand.ClearIUnknownNext;
begin
  FIUnknownNext := nil;
end;

procedure TOLEDBCommand.ClearIMultipleResults;
begin
{$IFDEF CLR}
  if FIMultipleResults <> nil then
    Marshal.ReleaseComObject(FIMultipleResults);
{$ENDIF}
  FIMultipleResults := nil; // Rowsets not also provided
end;

procedure TOLEDBCommand.ClearISSAsynchStatus;
begin
  FISSAsynchStatus := nil;
end;

procedure TOLEDBCommand.BreakExec;
begin
  if FNonBlocking and not ISSAsynchStatusIsNull then
    FExecutor.Terminate
  else begin
    FBreakExecCS.Acquire;
    try
      if FICommandText <> nil then
        Check(ICommand(FICommandText).Cancel);
      FWaitForBreak := True;
    finally
      FBreakExecCS.Release;
    end;
  end;
end;

function TOLEDBCommand.GetParamDescType: TParamDescClass;
begin
  Result := TOLEDBParamDesc;
end;

function TOLEDBCommand.AddParam: TParamDesc;
begin
  Result := TOLEDBParamDesc.Create;
  FParams.Add(Result);
end;

function TOLEDBCommand.GetParam(Index: integer): TOLEDBParamDesc;
begin
  Result := TOLEDBParamDesc(FParams[Index]);
end;

{ TOLEDBFieldDesc }

function TOLEDBFieldDesc.ActualNameQuoted(RecordSet: TCRRecordSet; const QuoteNames: boolean): string; 
begin
  if FActualNameQuoted[QuoteNames] <> '' then
    Result := FActualNameQuoted[QuoteNames]
  else
  begin
    if FTableInfo <> nil then begin
      if QuoteNames then
        Result := FTableInfo.Quote(BaseColumnName, LeftQuote, RightQuote)
      else
        Result := BaseColumnName;
      FActualNameQuoted[QuoteNames] := Result;
    end
    else begin
      if QuoteNames then
        Result := TOLEDBRecordSet(RecordSet).GetTableInfoClass.Quote(BaseColumnName, LeftQuote, RightQuote)
      else
        Result := BaseColumnName;
      FActualNameQuoted[QuoteNames] := Result;
    end;
  end;
end;

{ TMSTableInfo }

class function TOLEDBTableInfo.LeftQuote: Char;
begin
  Result := {$IFDEF CLR}CoreLab.Sdac.{$ENDIF}OLEDBAccess.LeftQuote;
end;

class function TOLEDBTableInfo.RightQuote: Char;
begin
  Result := {$IFDEF CLR}CoreLab.Sdac.{$ENDIF}OLEDBAccess.RightQuote;
end;

{ TOLEDBRecorSet }

constructor TOLEDBRecordSet.Create;
begin
  FEnableBCD := False;
  FUniqueRecords := False;
  FRequestSQLObjects := False;
  FCursorUpdate := True;
  FCursorTypeChanged := nil;

  FLockClearMultipleResults := False;
  FroAfterUpdate := False;

  FWideStrings := True; // Native fields mapping

  inherited;
  FNativeRowset := True;
  FCursorType := ctDefaultResultSet;
  FFetchRows := 25;
end;

destructor TOLEDBRecordSet.Destroy;
begin
  Assert((not FNativeRowset) or (FIRowset = nil), 'TOLEDBRecordSet.Destroy - interfaces not released');
  inherited;
end;

function TOLEDBRecordSet.GetFieldDescType: TFieldDescClass;
begin
  Result := TOLEDBFieldDesc;
end;

procedure TOLEDBRecordSet.ClearHRowIfNeed;
{$IFDEF CLR}
var
  rghRows: PUintArray;
  GCHandle: IntPtr;
{$ENDIF}
begin
  if FHRowAccessible and (FCursorType in ServerCursorTypes) then begin
    Assert(FIRowset <> nil, 'TOLEDBRecordSet.ClearHRowIfNeed - FIRowset must be setted');

  {$IFDEF CLR}
    GCHandle := AllocGCHandle(TObject(FHRow), True);
    try
      rghRows := GetAddrOfPinnedObject(GCHandle);
      Check(FIRowset.ReleaseRows(1, rghRows, nil, nil, nil));
    finally
      FreeGCHandle(GCHandle);
    end;
  {$ELSE}
    Check(FIRowset.ReleaseRows(1, @FHRow, nil, nil, nil));
  {$ENDIF}
    FHRow := MaxLongint;
    FHRowAccessible := False;
  end;
end;

function TOLEDBRecordSet.GetIndicatorSize: word; 
begin
  Result := FieldCount * OLE_DB_INDICATOR_SIZE;
end;

function TOLEDBRecordSet.IsBlobFieldType(DataType: word): boolean;
begin
  Result := inherited IsBlobFieldType(DataType) or (DataType = dtMSXML); 
end;

procedure TOLEDBRecordSet.CreateComplexField(RecBuf: IntPtr; FieldIndex: integer; WithBlob: boolean);
var
  Blob: TSharedObject;
  FieldDesc: TFieldDesc;
begin
  FieldDesc := Fields[FieldIndex];
  case FieldDesc.DataType of
    dtBlob, dtMemo, dtWideMemo, dtMSXML:
      if WithBlob then begin
      {$IFDEF HAVE_COMPRESS}
        if FieldDesc.DataType = dtBlob then
          Blob := TCompressedBlob.Create
        else
      {$ENDIF}
          Blob := TBlob.Create;
        TBlob(Blob).EnableRollback;
        SetObject(FieldIndex + 1, RecBuf, Blob);
      end;
    else
      inherited;
  end;
end;

procedure TOLEDBRecordSet.CreateComplexFields(RecBuf: IntPtr; WithBlob: boolean);
var
  i: integer;
  Blob: TBlob;
  Field: TFieldDesc;
begin
  inherited;

  if WithBlob then
    for i := 0 to FieldCount - 1 do begin
      Field := Fields[i];
      if (Field.FieldDescKind <> fdkCalculated) and (((Field.DataType = dtMemo) or (Field.DataType = dtWideMemo)) and ((Field.SubDataType and dtWide) <> 0)) or (Field.DataType = dtMSXML) then begin
        Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(RecBuf, Field.Offset)));
        Blob.IsUnicode := True;
      end
    end;
end;

procedure TOLEDBRecordSet.FreeComplexFields(RecBuf: IntPtr; WithBlob: boolean);
var
  i: integer;
  Handle: IntPtr;
  so: TSharedObject;
  b: boolean;
  Field: TFieldDesc;
begin
  inherited;

  for i := 0 to FieldCount - 1 do begin
    Field := Fields[i];
    if (Field.FieldDescKind <> fdkCalculated) and (Field.DataType = dtMSXML) and WithBlob then begin
      Handle := Marshal.ReadIntPtr(RecBuf, Field.Offset);
      so := TSharedObject(GetGCHandleTarget(Handle));
      b := (so <> nil) and (so.RefCount = 1);
      so.Free;
      if b then
        Marshal.WriteIntPtr(RecBuf, Field.Offset, nil);
    end;
  end;
end;

function TOLEDBRecordSet.GetStatus(FieldNo: word; RecBuf: IntPtr): DWORD;
begin
  Result := DWORD(Marshal.ReadInt32(RecBuf, DataSize + FieldNo{numeration from 0?} * OLE_DB_INDICATOR_SIZE));
end;

procedure TOLEDBRecordSet.SetStatus(FieldNo: word; RecBuf: IntPtr; Value: DWORD);
begin
  Marshal.WriteInt32(RecBuf, DataSize + FieldNo{numeration from 0?} * OLE_DB_INDICATOR_SIZE, Integer(Value));
end;

function TOLEDBRecordSet.GetNull(FieldNo: word; RecBuf: IntPtr): boolean;
begin
  Result := GetStatus(FieldNo - 1 {numeration from 1?}, RecBuf) = DBSTATUS_S_ISNULL;
  if Result then
    Result := GetNullByBlob(FieldNo, RecBuf);
end;

procedure TOLEDBRecordSet.SetNull(FieldNo: word; RecBuf: IntPtr; Value: boolean);
begin
  if Value then
    SetStatus(FieldNo - 1 {numeration from 1?}, RecBuf, DBSTATUS_S_ISNULL)
  else
    SetStatus(FieldNo - 1 {numeration from 1?}, RecBuf, DBSTATUS_S_OK);
end;

procedure TOLEDBRecordSet.GetFieldData(Field: TFieldDesc; RecBuf: IntPtr; Dest: IntPtr);
begin
  case Field.DataType of
    dtString, dtWideString, dtMemo, dtWideMemo, dtMSXML:
    begin
      if Field.Length = 0 then
        CopyBuffer(IntPtr(Integer(RecBuf) + Field.Offset), Dest, sizeof(IntPtr) {ISeqStream})
      else
        inherited;
    end;
    dtBlob:
      CopyBuffer(IntPtr(Integer(RecBuf) + Field.Offset), Dest, sizeof(TBlob));
    dtBCD:
    begin
      CheckBCDOverflow(Field.FieldNo, RecBuf);
      inherited;
    end;
  else
    inherited;
  end;
end;

procedure TOLEDBRecordSet.GetFieldAsVariant(FieldNo: word; RecBuf: IntPtr; var Value: variant);
begin
  CheckBCDOverflow(FieldNo, RecBuf);

  inherited;
end;

procedure TOLEDBRecordSet.PutFieldData(Field: TFieldDesc; RecBuf: IntPtr; Source: IntPtr);
begin
  case Field.DataType of
    dtString, dtWideString, dtMemo, dtWideMemo, dtMSXML:
    begin
      if Field.Length = 0 then
        CopyBuffer(Source, IntPtr(Integer(RecBuf) + Field.Offset), sizeof(IntPtr) {ISeqStream})
      else
        inherited;
    end;
    dtBlob:
      CopyBuffer(Source, IntPtr(Integer(RecBuf) + Field.Offset), sizeof(TBlob));
  else
    inherited;
  end;
end;

{ Open/Close }

function TOLEDBRecordSet.NeedInitFields: boolean;
begin
  Result := FCommand.FIsSProc and Prepared;
end;

procedure TOLEDBRecordSet.InternalPrepare;
var
  ColumnsInfo: IColumnsInfo;

  cColumns: UINT;
  prgInfo: PDBCOLUMNINFO;
  pStringsBuffer: IntPtr;

  //Malloc: IMalloc;
begin
  QueryCommandInterfaces(True);
  try
    SetCommandProp;
    inherited;
  finally
    ReleaseCommandInterfaces; /// FCommand.QueryIntCnt counter is increased in inherited
  end;

  try
    // Detect CommandType
    if FNativeRowset and not FCommand.FRPCCall then begin
      // If statement is wrong in some cases exception may be occured now
      QueryIntf(FCommand.FICommandPrepare, {$IFDEF CLR}IColumnsInfo{$ELSE}IID_IColumnsInfo{$ENDIF}, ColumnsInfo);
      Assert(ColumnsInfo <> nil);

      pStringsBuffer := nil;
      try
        // If statement is wrong in some cases exception may be occured now
        Check(ColumnsInfo.GetColumnInfo(cColumns, PDBCOLUMNINFO(prgInfo), pStringsBuffer));

        if cColumns > 0 then
          CommandType := ctCursor
        else
          CommandType := ctStatement;
      finally
        FreeCoMem(prgInfo);
        FreeCoMem(pStringsBuffer);
      end;
    end;
  except
    InternalUnPrepare;
    raise;
  end;
end;

procedure TOLEDBRecordSet.InternalUnPrepare;
begin
  try
    inherited;
  finally
    if FNativeRowset then begin
      // ReleaseCommandInterfaces;
      if not FLockClearMultipleResults then begin
        FCommand.ClearIMultipleResults;
        FCommand.FIUnknownNext := nil;
      end;
      FCommand.FIUnknown := nil;
      ReleaseRecordSetInterfaces;
    end;
    CommandType := ctUnknown;
  end;
end;

procedure TOLEDBRecordSet.InsertRecord(RecBuf: IntPtr); 
  procedure AppItem;
  var
    Block: PBlockHeader;
    Item: PItemHeader;
  begin
    Assert(IntPtr(BlockMan.FirstBlock) = nil);

    // Nearly copied from TBlockManager.AddFreeBlock
    BlockMan.AllocBlock(Block, 1);
    Item := IntPtr(Integer(Block) + sizeof(TBlockHeader));
    Item.Prev := nil;
    Item.Next := nil;
    Item.Block := Block;
    Item.Flag := flFree;

    BlockMan.FirstFree := Item;
    Block.UsedItems := 0;
    //------------------

    CurrentItem := AppendItem;
  end;

var
  Appended: boolean;
  i: integer;
begin
//  ODS(Format('+ TOLEDBRecordSet.InsertRecord   FI = %d, CI = %d, LI = %d, BOF = %s, EOF = %s', [Integer(FirstItem), Integer(CurrentItem), Integer(LastItem), BoolToStr(BOF, True), BoolToStr(EOF, True)]));
  if (FIRowsetUpdate <> nil) then begin
    if IntPtr(CurrentItem) = nil then begin
      if IntPtr(FirstItem) <> nil then
        CurrentItem := FirstItem
      else
      if IntPtr(LastItem) <> nil then
        CurrentItem := LastItem;
    end;

    Appended := IntPtr(CurrentItem) = nil;
    if Appended then
      AppItem
    else
      if HasComplexFields then
        FreeComplexFields(IntPtr(Integer(CurrentItem.Block) + sizeof(TBlockHeader) + sizeof(TItemHeader)), True);

    InternalAppend(RecBuf);
    PutRecord(RecBuf);
    ReorderItems(CurrentItem, roInsert);

    if FCursorType = ctKeySet then begin
      FirstItem := nil;
      CurrentItem := nil;
      LastItem := nil;

      SetToEnd;
    end
    else
    begin
      Assert(FCursorType = ctDynamic);
      if HasBlobFields then
        for i := 0 to FieldCount - 1 do
          if IsBlobFieldType(Fields[i].DataType) then
            TBlob(GetObject(Fields[i].FieldNo, RecBuf)).Commit;

      FirstItem := nil;
      CurrentItem := nil;
      LastItem := nil;

      FLastFetchBack := False;
      SetToEnd;
    end;
  end
  else
  if ((FCursorType in ServerCursorTypes) and not FCursorUpdate) then  begin
    if FRecordCount = 0 then
    begin
      InternalAppend(RecBuf);

      AppItem;
      PutRecord(RecBuf);
      ReorderItems(CurrentItem, roInsert);

      CurrentItem := nil;
//      SetToEnd; - no sense for keyset cursors
      FBof := False;
{      SetToEnd;

      FBof := FirstItem = nil;
      FEof := LastItem = nil;}
    end
    else
    begin
      InternalAppend(RecBuf);

      if HasComplexFields then
        FreeComplexFields(RecBuf, True);
      SetToEnd;
      CurrentItem := LastItem;

      FBof := IntPtr(FirstItem) = nil;
      FEof := IntPtr(LastItem) = nil;
    end;
  end
  else
    inherited;
//  ODS(Format('+ TOLEDBRecordSet.InsertRecord   FI = %d, CI = %d, LI = %d, BOF = %s, EOF = %s', [Integer(FirstItem), Integer(CurrentItem), Integer(LastItem), BoolToStr(BOF, True), BoolToStr(EOF, True)]));
end;

procedure TOLEDBRecordSet.UpdateRecord(RecBuf: IntPtr);
begin
  inherited;
  if (FCursorType in ServerCursorTypes) and FroAfterUpdate then
    FetchToBookmarkValue;
end;

procedure TOLEDBRecordSet.DeleteRecord;
begin
  if (FIRowsetUpdate <> nil)
  or ((FCursorType in ServerCursorTypes) and not FCursorUpdate) then begin
    InternalDelete;
    RemoveRecord;
    if FCursorType = ctDynamic then
      FRecordCount := - 1;

    Fetch;
    CurrentItem := FirstItem;

    if IntPtr(CurrentItem) = nil then begin
      Fetch(True);
      CurrentItem := FirstItem;
    end;
  end
  else
    inherited;
end;

procedure TOLEDBRecordSet.SetIndexFieldNames(Value: string);
begin
  if FCursorType in ServerCursorTypes then
    DatabaseError(SLocalSortingServerCursor);
    
  inherited SetIndexFieldNames(Value);
end;

procedure TOLEDBRecordSet.SetToBegin;
begin
  case FCursorType of
    ctStatic, ctKeyset:
      try
        FFetchFromBookmark := True;
        FBookmarkSize := sizeof(byte);
        FBookmarkValue := DBBMK_FIRST;
        Fetch;
      finally
        FFetchFromBookmark := False;
        FBookmarkSize := sizeof(FBookmarkValue);
      end;
    ctDynamic:
      while Fetch(True) do;
  end;
  inherited;
end;

procedure TOLEDBRecordSet.SetToEnd; 
begin
  if FCursorType in [ctStatic, ctKeyset] then
    try
      FFetchFromBookmark := True;
      FBookmarkSize := sizeof(byte);
      FBookmarkValue := DBBMK_LAST;
      Fetch;
    finally
      FFetchFromBookmark := False;
      FBookmarkSize := sizeof(FBookmarkValue);
    end
  else
    FetchAll;
  inherited;
end;

function TOLEDBRecordSet.FetchToBookmarkValue(FetchBack: boolean = False): boolean; // Fetch to Bookmark. Bookmark value is stored in FBookmarkValue. Bookmark value used only for ctStatic and ctKeyset. For ctDynamic method refetched current record in specified direction
begin
  Assert(FCursorType in ServerCursorTypes);
  try
    FFetchFromBookmark := True;
    Result := Fetch(FetchBack);
  finally
    FFetchFromBookmark := False;
  end;
end;

procedure TOLEDBRecordSet.SetToBookmark(Bookmark: PRecBookmark);
begin
  if FCursorType in [ctStatic, ctKeyset] then begin
    // Cannot optimize - used to RefreshRecord
    // if (FBookmarkValue <> Bookmark.Order) or (CurrentItem = nil) then begin
    FBookmarkValue := Bookmark.Order;
    FetchToBookmarkValue;
  end;

  inherited;
end;

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

end;

function TOLEDBRecordSet.CanFetchBack: boolean;
begin
  Result := FCursorType in ServerCursorTypes;
end;

procedure TOLEDBRecordSet.DoFetchTerminate(Sender: TObject); // MainThread context
begin
  EndFetchAll(FFetchExecutor.FException);
  FFetchExecutor := nil;
end;

procedure TOLEDBRecordSet.DoFetchException(Sender: TObject; E: Exception; var Fail: boolean); // MainThread context
begin
  if (E is EOLEDBError) then
    FCommand.FConnection.DoError(EOLEDBError(E), Fail);
end;

const
  FE_AFTERFETCH = 1;

procedure TOLEDBRecordSet.DoFetchSendEvent(Sender: TObject; Event: TObject);
begin
  if Integer(Event) = FE_AFTERFETCH then
    DoAfterFetch;
end;

procedure TOLEDBRecordSet.FetchAll;
begin
  if (FCommand.GetCursorState < csFetchingAll) or (FCursorType = ctDynamic) then begin
    FCommand.SetCursorState(csFetchingAll);
    if FCommand.FNonBlocking then begin
      FFetchExecutor := TOLEDBThreadWrapper.Create(TExecuteThread, True);
      TExecuteThread(FFetchExecutor.FThread).FRunMethod := DoFetchAll;
      FFetchExecutor.OnException := DoFetchException;
      FFetchExecutor.OnTerminate := DoFetchTerminate;
      FFetchExecutor.OnSendEvent := DoFetchSendEvent;
      FFetchExecutor.FreeOnTerminate := True;
      StringHeap.ThreadSafety := True;
      // FFetchExecutor.Resume; // moved to TCustomMSDataSet.SetActive
    end
    else
      DoFetchAll;
  end;
end;

procedure TOLEDBRecordSet.DoFetchAll; // FFetchExecutor.FThread context
begin
  FFetching := True;
  try
    while Fetch do
      if (FFetchExecutor <> nil) and FWaitForFetchBreak then
        Break;
  finally
    FFetching := False;
  end;
end;

procedure TOLEDBRecordSet.EndFetchAll(E: Exception); // MainThread context
begin
  if FCommand.FNonBlocking then
    StringHeap.ThreadSafety := True;
end;

procedure TOLEDBRecordSet.BreakFetch;
begin
  inherited;

  if FCommand.FNonBlocking and (FFetchExecutor <> nil) and not FFetchExecutor.Thread.Suspended then
    FFetchExecutor.Terminate;
end;

function TOLEDBRecordSet.CanDisconnect: boolean;
begin
  Result := inherited CanDisconnect
    and (FCommand.FIUnknown = nil)
    and (FIRowset = nil)
    and (FCommand.FIUnknownNext = nil)
    and (FCommand.FIMultipleResults = nil);
end;

function TOLEDBRecordSet.RowsReturn: boolean;
begin
  if CommandType <> ctUnknown then
    Result := inherited RowsReturn
  else                              //we need to know this info even if CommandType is not set(TCustomDADataSet.DoAfterExecute)
    Result := (FCommand.FIUnknown <> nil) or (FIRowset <> nil);
end;

procedure TOLEDBRecordSet.RowsetUpdateCommit;
const
  RowHeader = '%s (Status = %Xh).';
var
{$IFDEF CLR}
  rghRows: PUintArray;
  GCHandle: IntPtr;
{$ENDIF}
  pRowStatus: PDBRowStatus;
  RowStatus: DBRowStatus;
  Msg: WideString;
begin
  try
  {$IFDEF CLR}
    GCHandle := AllocGCHandle(TObject(FHRow), True);
    try
      rghRows := GetAddrOfPinnedObject(GCHandle);
      Check(FIRowsetUpdate.Update(DB_NULL_HCHAPTER, 1, rghRows, nil, nil, pRowStatus));
    finally
      FreeGCHandle(GCHandle);
    end;
  {$ELSE}
    Check(FIRowsetUpdate.Update(DB_NULL_HCHAPTER, 1, @FHRow, nil, nil, pRowStatus));
  {$ENDIF}
  except
    on e: Exception do begin
      if pRowStatus <> nil then begin
        Msg := '';
        RowStatus := DBRowStatus(Marshal.ReadInt32(pRowStatus));
        case RowStatus of
          DBROWSTATUS_S_OK:;
          DBROWSTATUS_S_MULTIPLECHANGES:
            AddInfoToErr(Msg, RowHeader, [SRowMultipleChanges, RowStatus]);
          DBROWSTATUS_S_PENDINGCHANGES:
            AddInfoToErr(Msg, RowHeader, [SRowPendingChanges, RowStatus]);
          DBROWSTATUS_E_CANCELED:
            AddInfoToErr(Msg, RowHeader, [SRowCanceled, RowStatus]);
          //DBROWSTATUS_E_CANTRELEASE = $00000006;
          DBROWSTATUS_E_CONCURRENCYVIOLATION:
            AddInfoToErr(Msg, RowHeader, [SRowConcurrencyViolation, RowStatus]);
          DBROWSTATUS_E_DELETED:
            AddInfoToErr(Msg, RowHeader, [SRowDeleted, RowStatus]);
          //DBROWSTATUS_E_PENDINGINSERT = $00000009;
          //DBROWSTATUS_E_NEWLYINSERTED = $0000000A;
          DBROWSTATUS_E_INTEGRITYVIOLATION:
            AddInfoToErr(Msg, RowHeader, [SRowIntegrityViolation, RowStatus]);
          DBROWSTATUS_E_INVALID:
            Assert(False);
          //DBROWSTATUS_E_MAXPENDCHANGESEXCEEDED = $0000000D;
          //DBROWSTATUS_E_OBJECTOPEN = $0000000E;
          //DBROWSTATUS_E_OUTOFMEMORY = $0000000F;
          DBROWSTATUS_E_PERMISSIONDENIED:
            AddInfoToErr(Msg, RowHeader, [SRowPermissionDenied, RowStatus]);
          DBROWSTATUS_E_LIMITREACHED:
            AddInfoToErr(Msg, RowHeader, [SRowLimitReached, RowStatus]);
          DBROWSTATUS_E_SCHEMAVIOLATION:
            AddInfoToErr(Msg, RowHeader, [SRowSchemaViolation, RowStatus]);
          DBROWSTATUS_E_FAIL:
            AddInfoToErr(Msg, RowHeader, [SRowFail, RowStatus]);
        else
          AddInfoToErr(Msg, RowHeader, [SUnknownStatus, RowStatus]);
        end;

        FreeCoMem(pRowStatus);
        AddInfoToErr(E, Msg, []);
      end;
      
      // -----
      RowsetUpdateRollback;
      // -----}

      raise E;
    end;
  end;
end;

procedure TOLEDBRecordSet.RowsetUpdateRollback;
{$IFDEF CLR}
var
  rghRows: PUintArray;
  GCHandle: IntPtr;
{$ENDIF}
begin
{$IFDEF CLR}
  GCHandle := AllocGCHandle(TObject(FHRow), True);
  try
    rghRows := GetAddrOfPinnedObject(GCHandle);
    Check(FIRowsetUpdate.Undo(0, 1, rghRows, nil, nil, nil));
  finally
    FreeGCHandle(GCHandle);
  end;
{$ELSE}
  Check(FIRowsetUpdate.Undo(0, 1, @FHRow, nil, nil, nil));
{$ENDIF}
//Assert(cRows = 1);

{ Check(FIRowsetUpdate.GetRowStatus(0, 1, @FHRow, @pRowStatus));
  FHRowAccessible := True; ClearHRowIfNeed;

  Check(FIRowsetUpdate.GetRowStatus(0, 1, @FHRow, @pRowStatus));
  FHRowAccessible := True; ClearHRowIfNeed;
//Check(FIRowsetUpdate.GetRowStatus(0, 1, @FHRow, @pRowStatus));
}
end;

procedure TOLEDBRecordSet.InternalAppend(RecBuf: IntPtr);
begin
  if (FIRowsetUpdate <> nil) then begin
    ClearHRowIfNeed;
    InternalAppendOrUpdate(RecBuf, True);
  end
  else
    inherited;

  if FCursorType = ctKeySet then
    Inc(FRecordCount);
end;

procedure TOLEDBRecordSet.InternalDelete;
var
  hr: HResult;
{$IFDEF CLR}
  rghRows: PUintArray;
  GCHandle: IntPtr;
{$ENDIF}
begin
  if FIRowsetUpdate = nil then
    inherited
  else
  begin
    Assert(FIRowsetUpdate <> nil, 'FCommand.FIRowsetUpdate must be setted');
    Assert(FHRowAccessible, 'FHRow must be accessible');

  {$IFDEF CLR}
    GCHandle := AllocGCHandle(TObject(FHRow), True);
    try
      rghRows := GetAddrOfPinnedObject(GCHandle);
      hr := IRowsetChange(FIRowsetUpdate).DeleteRows(0, 1, rghRows, nil);
    finally
      FreeGCHandle(GCHandle);
    end;
  {$ELSE}
    hr := FIRowsetUpdate.DeleteRows(0, 1, @FHRow, nil);
  {$ENDIF}
    Check(hr);
    RowsetUpdateCommit;
  end;
end;

procedure TOLEDBRecordSet.InternalUpdate(RecBuf: IntPtr); 
begin
  if FIRowsetUpdate = nil then
    inherited
  else 
  begin
    Assert(FHRowAccessible, 'FHRow must be accessible');
    InternalAppendOrUpdate(RecBuf, False);
  end;
end;

procedure TOLEDBRecordSet.InternalAppendOrUpdate(RecBuf: IntPtr; const IsAppend: boolean);
var
  StreamList: TList;

  procedure SetDataToRow(const Row: hRow; const pRec: IntPtr);
    procedure PrepareConvertableFields; // Server Cursors. Before send data to OLEDB
    var
      i: integer;
      pValue: IntPtr;
      HeapBuf: IntPtr;
      Size: integer;
      Field: TFieldDesc;
    {$IFDEF VER5P}
      g: TGUID;
    {$ENDIF}
    {$IFDEF VER6P}
      Bcd: TBcd;
      s: string;
      DotPos, l: integer;
    {$ENDIF}
    {$IFDEF CLR}
      BcdBuf: TBytes;
    {$ENDIF}
    begin
    {$IFDEF VER8}
      SetLength(BcdBuf, 0); // To avoid compiler warning. Delphi8 bug.
    {$ENDIF}
      for i := 0 to Fields.Count - 1 do begin
        Field := Fields[i];
        if Field.FieldDescKind = fdkData then begin
          pValue := IntPtr(Integer(pRec) + Field.Offset);
          case Field.DataType of
            dtBytes:
              Marshal.WriteInt32(pValue, Field.Length, Field.Length);
            dtVarBytes:
              Marshal.WriteInt32(pValue, sizeof(word) + Field.Length, Marshal.ReadInt16(pRec, Field.Offset));
            dtExtVarBytes:
              if not GetNull(i + 1, pRec) then
              begin
                HeapBuf := Marshal.ReadIntPtr(pValue); //HeapBuf := IntPtr(IntPtr(pValue)^);
                Size := Marshal.ReadInt16(HeapBuf);
                Marshal.WriteInt32(pValue, sizeof(IntPtr {IntPtr to OLEDBBuf/StringHeap}), Size);
                Marshal.WriteIntPtr(pValue, IntPtr(Integer(HeapBuf) + sizeof(word)));
              end;
          {$IFDEF VER6P}
            dtFmtBCD:
              begin
              {$IFDEF CLR}
                Marshal.Copy(pValue, BcdBuf, 0, 34);
                Bcd := TBcd.FromBytes(BcdBuf);
              {$ELSE}
                Bcd := PBcd(pValue)^;
              {$ENDIF}
                // DBNumeric
                s := BcdToStr(Bcd);
                if DecimalSeparator <> '.' then begin
                  DotPos := Pos(DecimalSeparator, s);
                  if DotPos <> 0 then
                    s[DotPos] := '.';
                end;
                l := Length(s);
                // Marshal.WriteInt32(pLength, l);

                if l > 0 then
                  CopyBufferAnsi(s, pValue, l + 1{#0});
              end;
          {$ENDIF}
          {$IFDEF VER5P}
            dtGuid:
              if not GetNull(i + 1, pRec) then
              begin
                g := StringToGUID(Marshal.PtrToStringAnsi(pValue));
              {$IFDEF CLR}
                Marshal.StructureToPtr(TObject(g), pValue, False);
              {$ELSE}
                CopyBuffer(@g, pValue, sizeof(g));
              {$ENDIF}
              end;
          {$ENDIF}
          end;
        end;
      end;
    end;

    procedure PostPlainAccessorBlock(
      const AccessorBlock: TAccessorBlock;
      out NeedToPost: boolean // Used to skip unchanged BLOB fields
    );
      procedure ConvertBlobToStream(BlobField: TFieldDesc; const pValue: IntPtr);
      var
        OLEDBStream: TOLEDBStream;
        Blob: TBlob;
        pUnk: IntPtr;

      begin
        Blob := TBlob(GetGCHandleTarget(Marshal.ReadIntPtr(pValue)));

        // Assert(FCursorType in [ctKeyset, ctDynamic] and FCursorUpdate);
        NeedToPost := Blob.CanRollback;

        if NeedToPost then begin
          // Create stream
          OLEDBStream := TOLEDBStream.Create(Blob, StreamList);
          pUnk := Marshal.GetIUnknownForObject(OLEDBStream);
          Marshal.AddRef(pUnk);
          Marshal.WriteIntPtr(pValue, pUnk);

          // Set stream size
          Marshal.WriteInt32(pRec, BlobField.Offset + sizeof(IntPtr{ISeqStream}), OLEDBStream.Size);
        end;
      end;

    var
      pValue: IntPtr;
      BlobField: TFieldDesc;

    begin
      NeedToPost := True;
      if AccessorBlock.BlobFieldNum = -1 then
        Exit;

      BlobField := Fields[AccessorBlock.BlobFieldNum];
      pValue := IntPtr(Integer(pRec) + BlobField.Offset);
      if not GetNull(AccessorBlock.BlobFieldNum + 1, pRec) then
        ConvertBlobToStream(BlobField, pValue);
    end;

  var
    FetchBlockOffset: integer;

    procedure PostExternalAccessorBlock(const AccessorBlock: TAccessorBlock);
    var
      i, l, p: integer;
      Blob: TSharedObject;
      pValue, pc: IntPtr;
      pov: POleVariant;

      Field: TFieldDesc;
      FieldNum: integer;
      ServerVersion: integer;

    begin
      Assert(AccessorBlock.BlobFieldNum = - 1);
      ServerVersion := ProviderPrimaryVer;
      
      for i := 0 to Length(AccessorBlock.FieldNums) - 1 do begin
        FieldNum := AccessorBlock.FieldNums[i];
        Field := Fields[FieldNum];
        if IsNeedFetchBlock(Field, ServerVersion) then begin
          pValue := IntPtr(Integer(pRec) + Field.Offset);
          case Field.DataType of
            dtMemo, dtWideMemo, dtMSXML: begin
              Blob := TSharedObject(GetGCHandleTarget(Marshal.ReadIntPtr(pValue)));
              pc := IntPtr(Integer(FFetchBlock) + FetchBlockOffset + OLE_DB_INDICATOR_SIZE);

              p := 0;
              if not GetNull(FieldNum + 1, pRec) then begin
                l := TBlob(Blob).Size;
                if l > MaxNonBlobFieldLen then // see IncFetchBlockOffset
                  l := MaxNonBlobFieldLen;

                if l > 0 then
                  p := TBlob(Blob).Read(0, l, pc);
              end;
              Marshal.WriteByte(pc, p, 0{#0});
              if TBlob(Blob).IsUnicode then
                Marshal.WriteByte(pc, p + 1, 0{#0});
            end;
            dtVariant: begin
              Blob := TSharedObject(GetGCHandleTarget(Marshal.ReadIntPtr(pValue)));
              pov := POleVariant(Integer(FFetchBlock) + FetchBlockOffset + OLE_DB_INDICATOR_SIZE); 
              Marshal.WriteInt16(pov, varEmpty); // TVarData(pov^).VType := varEmpty;
              SetOleVariant(pov, TVariantObject(Blob).Value);
            end;
            else
              Assert(False);
          end;
          IncFetchBlockOffset(FetchBlockOffset, Field.DataType);
        end;
      end;
    end;

    procedure CopyStatusFBlock(const ToFBlock: boolean); // if ToFBlock is True then status is copied from pRec to FetchBlock. Otherwise - from FetchBlock to pRec
    var
      i: integer;
      FetchBlockOffset: integer;
      Status: DWORD;
      
    begin
      FetchBlockOffset := 0;
      for i := 0 to Fields.Count - 1 do
        if (Fields[i].FieldDescKind = fdkData) and IsNeedFetchBlock(Fields[i], ProviderPrimaryVer) then begin
          if ToFBlock then begin
            Status := GetStatus(i, pRec);
            Marshal.WriteInt32(FFetchBlock, FetchBlockOffset, Integer(Status));
          end
          else
          begin
            Status := DWORD(Marshal.ReadInt32(FFetchBlock, FetchBlockOffset));
            SetStatus(i, pRec, Status);
          end;
          IncFetchBlockOffset(FetchBlockOffset, Fields[i].DataType);
        end;
    end;

  var
    AccNum: integer;
    hr: HResult;
    NeedToPost: boolean;
  begin
    PrepareConvertableFields;

    FetchBlockOffset := 0;
    try
      for AccNum := 0 to Length(FFetchAccessorData.AccessorBlocks) - 1 do begin
        Assert(Length(FFetchAccessorData.AccessorBlocks[AccNum].FieldNums) = 1); // CR 4082
        if not IsAppend // Edit
          or not GetNull(FFetchAccessorData.AccessorBlocks[AccNum].FieldNums[0] + 1, pRec)
          or (FFetchAccessorData.AccessorBlocks[AccNum].BlockType = abBlob) then begin
          case FFetchAccessorData.AccessorBlocks[AccNum].BlockType of
            abFetchBlock: begin
              // Prepare data
              CopyStatusFBlock(True);
              PostExternalAccessorBlock(FFetchAccessorData.AccessorBlocks[AccNum]);
              // Set data to IRowset
              if FHRowAccessible then
                hr := FIRowsetUpdate.SetData(FHRow, FFetchAccessorData.AccessorBlocks[AccNum].hAcc, FFetchBlock)
              else
                hr := FIRowsetUpdate.InsertRow(0, FFetchAccessorData.AccessorBlocks[AccNum].hAcc, FFetchBlock, FHRow);
              CopyStatusFBlock(False);

              // Analyze OLE DB result
              CheckAndAnalyzeFieldsStatus(hr, pRec);
              FHRowAccessible := True;
            end;
            abOrdinary, abBLOB: begin
              // Prepare data
              PostPlainAccessorBlock(FFetchAccessorData.AccessorBlocks[AccNum], NeedToPost);
              if NeedToPost then begin
                // Set data to IRowset
                if FHRowAccessible then
                  hr := FIRowsetUpdate.SetData(FHRow, FFetchAccessorData.AccessorBlocks[AccNum].hAcc, pRec)
                else
                  hr := FIRowsetUpdate.InsertRow(0, FFetchAccessorData.AccessorBlocks[AccNum].hAcc, pRec, FHRow);

                // Analyze OLE DB result
                CheckAndAnalyzeFieldsStatus(hr, pRec);
                FHRowAccessible := True;
              end;
            end;
            abReadOnly:;
          end;
        end;
      end;
    except
      if FHRowAccessible then
        RowsetUpdateRollback;
      raise;
    end;

    if FHRowAccessible then
      RowsetUpdateCommit;
  end;

var
  pRec, pRecOld: IntPtr;
  i: integer;
  OLEDBStream: TOLEDBStream;

begin
  Assert(FIRowsetUpdate <> nil, 'FCommand.FIRowsetUpdate must be setted');

  StreamList := nil;
  pRecOld := RecBuf;

  /// Store old values to prevent conversion. Blob fields is not stored
  pRec := Marshal.AllocHGlobal(RecordSize);
  try
    StreamList := TList.Create;
    CopyBuffer(pRecOld, pRec, RecordSize);
    SetDataToRow(FHRow, pRec);
  finally
    Marshal.FreeHGlobal(pRec);

    // Remove streams
    for i := StreamList.Count - 1 downto 0 do begin
      OLEDBStream := TOLEDBStream(StreamList[i]);
    {$IFDEF CLR}
      OLEDBStream.Free;
    {$ELSE}
      OLEDBStream._Release;
    {$ENDIF}
      if GUIDToString(ProviderId) = GUIDToString(CLSID_SQLNCLI) then
        OLEDBStream.FStreamList := nil;
    end;
    Assert((StreamList.Count = 0) or (GUIDToString(ProviderId) = GUIDToString(CLSID_SQLNCLI)), 'StreamList.Count = ' + IntToStr(StreamList.Count));
    StreamList.Free;
  end;
end;

procedure TOLEDBRecordSet.SetCommandProp;
var
  OLEDBProperties: TOLEDBPropertiesSet;
  IRowsetUpdateRequired: boolean;
  IsSQLEverywhere: boolean;
begin
{$IFDEF SDAC_TEST}
  Inc(__SetRecordSetCommandPropCount);
{$ENDIF}

  Assert(FNativeRowset, 'FNativeRowset must be True');
  Assert(FCommand.FIMultipleResults = nil, 'FCommand.FIMultipleResults must be nil');

  IsSQLEverywhere := Provider = prCompact;
  if FCursorType in ServerCursorTypes then begin
    // Server cursor has no sence in disconnected mode
    if DisconnectedMode then
      DatabaseError(SDMandServerCursors);
    FFetchRows := 1; /// !!!
    FFetchAll := False;
    FCommand.FRequestMultipleResults := False;
  end
  else
    FCommand.FRequestMultipleResults := not IsSQLEverywhere;

  IRowsetUpdateRequired := (ProviderPrimaryVer = 7);

  OLEDBProperties := TOLEDBPropertiesSet.Create(FCommand.FConnection, DBPROPSET_ROWSET);
  try
    with OLEDBProperties do begin
      // AddPropInt(DBPROP_ACCESSORDER, DBPROPVAL_AO_SEQUENTIALSTORAGEOBJECTS); - no performance improvement
      AddPropInt(DBPROP_ACCESSORDER, DBPROPVAL_AO_RANDOM);

      case FCursorType of
        ctDefaultResultSet:
        begin
          if not IsSQLEverywhere then
            AddPropBool(DBPROP_UNIQUEROWS, FUniqueRecords or not FReadOnly);
          AddPropBool(DBPROP_IColumnsRowset, FRequestSQLObjects);
          if not IsSQLEverywhere then begin
            AddPropBool(DBPROP_IMultipleResults, FCommand.FRequestMultipleResults);
            AddPropBool(DBPROP_SERVERCURSOR, False);
          end;
          AddPropBool(DBPROP_OWNINSERT, False);
          AddPropBool(DBPROP_OTHERINSERT, False);
          AddPropBool(DBPROP_OTHERUPDATEDELETE, False);
          AddPropBool(DBPROP_OWNUPDATEDELETE, False);
          AddPropBool(DBPROP_IRowsetChange, False);
          AddPropBool(DBPROP_IRowsetUpdate, False);
        end;
        ctStatic:
        begin
          {Static RO}
          AddPropBool(DBPROP_SERVERCURSOR, True);
          AddPropBool(DBPROP_IRowsetChange, False);
          AddPropBool(DBPROP_IRowsetUpdate, False);
          AddPropBool(DBPROP_OWNINSERT, False);
          AddPropBool(DBPROP_OTHERINSERT, False);
          AddPropBool(DBPROP_OTHERUPDATEDELETE, False);
          AddPropBool(DBPROP_OWNUPDATEDELETE, False);
          AddPropBool(DBPROP_REMOVEDELETED, False);
          AddPropBool(DBPROP_IRowsetResynch, False);
          AddPropBool(DBPROP_CHANGEINSERTEDROWS, False);
          AddPropBool(DBPROP_SERVERDATAONINSERT, False);
          AddPropBool(DBPROP_UNIQUEROWS, False);
          AddPropBool(DBPROP_CANFETCHBACKWARDS, not FUniDirectional);

          // Bookmarks
          AddPropBool(DBPROP_IRowsetLocate, True);
          AddPropBool(DBPROP_BOOKMARKS, True);
        end;
        ctKeyset:
        begin
          {Keyset}
          AddPropBool(DBPROP_SERVERCURSOR, True);
          AddPropBool(DBPROP_OTHERINSERT, False);
          AddPropBool(DBPROP_OTHERUPDATEDELETE, True);
          AddPropBool(DBPROP_OWNINSERT, True);
          AddPropBool(DBPROP_OWNUPDATEDELETE, True);
          AddPropBool(DBPROP_UNIQUEROWS, False);
          AddPropBool(DBPROP_IMMOBILEROWS, True);
          AddPropBool(DBPROP_CANFETCHBACKWARDS, not FUniDirectional);

          // Bookmarks
          AddPropBool(DBPROP_IRowsetLocate, True);
          AddPropBool(DBPROP_BOOKMARKS, True);
          AddPropBool(DBPROP_REMOVEDELETED, True);

          // RO or RW cursor
          AddPropBool(DBPROP_IRowsetUpdate, not FReadOnly and FCursorUpdate, IRowsetUpdateRequired);
          AddPropBool(DBPROP_CHANGEINSERTEDROWS, True);

          // Transactions support
          AddPropBool(DBPROP_COMMITPRESERVE, True);
          AddPropBool(DBPROP_ABORTPRESERVE, True);

       end;
        ctDynamic:
        begin
          {Dynamic}
          AddPropBool(DBPROP_SERVERCURSOR, True);
          AddPropBool(DBPROP_OTHERINSERT, True);
          AddPropBool(DBPROP_OTHERUPDATEDELETE, True);
          AddPropBool(DBPROP_OWNINSERT, True);
          AddPropBool(DBPROP_OWNUPDATEDELETE, True);
          AddPropBool(DBPROP_UNIQUEROWS, False);
          AddPropBool(DBPROP_IMMOBILEROWS, False);
          AddPropBool(DBPROP_CANFETCHBACKWARDS, not FUniDirectional);

          // Bookmarks
          AddPropBool(DBPROP_IRowsetLocate, False);
          AddPropBool(DBPROP_BOOKMARKS, False);
          AddPropBool(DBPROP_REMOVEDELETED, True);

          // RO or RW cursor        
          AddPropBool(DBPROP_IRowsetUpdate, not FReadOnly and FCursorUpdate, IRowsetUpdateRequired);
          AddPropBool(DBPROP_CHANGEINSERTEDROWS, False);

          AddPropBool(DBPROP_IRowsetScroll, False);
          AddPropBool(DBPROP_CANHOLDROWS, False);
          AddPropBool(DBPROP_LITERALBOOKMARKS, False);
          AddPropBool(DBPROP_SERVERDATAONINSERT, False);     

          // Transactions support
          AddPropBool(DBPROP_COMMITPRESERVE, True);
          AddPropBool(DBPROP_ABORTPRESERVE, True);

        end;
      end;
    end;                                               
    OLEDBProperties.SetProperties(FCommand.FICommandProperties);
  finally
    OLEDBProperties.Free;
  end;
end;

procedure TOLEDBRecordSet.QueryCommandInterfaces(const QueryPrepare: boolean); // Create ConnectionSwap if need. Call FCommand.QueryInterfaces.
begin
  FCommand.QueryInterfaces(QueryPrepare);
end;

procedure TOLEDBRecordSet.ReleaseCommandInterfaces;
begin
  FCommand.ReleaseInterfaces;
end;
    
procedure TOLEDBRecordSet.QueryRecordSetInterfaces;
{  procedure GetProperties;
  const
    PropSetCnt = 1;
    PropCnt = 12;
  var
    RowsetInfo: IRowsetInfo;

    rgPropertyIDSets: PDBPropIDSetArray;
    DBPropIDArray: array[0..PropCnt - 1] of DBPROPID;

    rgPropertySets: PDBPropSet;
    cPropertySets: UINT;
    b: boolean;
    i: integer;
    st: DBPROPSTATUS;
  begin
    FIRowset.QueryInterface(IID_IRowsetInfo, RowsetInfo);

    DBPropIDArray[0] := DBPROP_SERVERCURSOR;
    DBPropIDArray[1] := DBPROP_IRowsetChange;
    DBPropIDArray[2] := DBPROP_IRowsetUpdate;
    DBPropIDArray[3] := DBPROP_OWNINSERT;
    DBPropIDArray[4] := DBPROP_OTHERINSERT;
    DBPropIDArray[5] := DBPROP_OTHERUPDATEDELETE;
    DBPropIDArray[6] := DBPROP_OWNUPDATEDELETE;
    DBPropIDArray[7] := DBPROP_REMOVEDELETED;
    DBPropIDArray[8] := DBPROP_IRowsetResynch;
    DBPropIDArray[9] := DBPROP_CHANGEINSERTEDROWS;
    DBPropIDArray[10] := DBPROP_SERVERDATAONINSERT;
    DBPropIDArray[11] := DBPROP_UNIQUEROWS;

    GetMem1(rgPropertyIDSets, PropSetCnt * sizeof(DBPropIDSet));
    try
      rgPropertyIDSets[0].guidPropertySet := DBPROPSET_ROWSET;
      rgPropertyIDSets[0].cPropertyIDs := PropCnt;
      rgPropertyIDSets[0].rgPropertyIDs := @DBPropIDArray;

      Check(RowsetInfo.GetProperties(PropSetCnt, rgPropertyIDSets,
        cPropertySets, PDBPropSet(rgPropertySets)));

      Assert(rgPropertySets <> nil, 'Cannot get properties');

      for i := 0 to PropCnt - 1 do
      begin
        b := rgPropertySets.rgProperties[i].vValue;
        st := rgPropertySets.rgProperties[i].dwStatus;
        b := not b;
      end;

    finally
      FreeMem1(rgPropertyIDSets);
      FCommand.FConnection.Malloc.Free(rgPropertySets.rgProperties);
      FCommand.FConnection.Malloc.Free(rgPropertySets);
    end;
  end;
}
begin
  try
    if FNativeRowset then begin
      Assert(FIRowset = nil, 'Duplicate call to TOLEDBRecordSet.QueryRecordSetInterfaces');
      Assert(FCommand.FIUnknown <> nil, 'FCommand.FIUnknown must be setted');

      QueryIntf(FCommand.FIUnknown, {$IFDEF CLR}IRowset{$ELSE}IID_IRowset{$ENDIF}, FIRowset);

      if FCursorType in [ctKeyset, ctStatic] then begin
        QueryIntf(FIRowset, {$IFDEF CLR}IRowsetLocate{$ELSE}IID_IRowsetLocate{$ENDIF}, FIRowsetLocate);
        Assert(FIRowsetLocate <> nil);
      end;

      {if (FCursorType in [ctKeyset, ctDynamic]) then
        GetProperties;}
      if (FCursorType in [ctKeyset, ctDynamic])
        and not FReadOnly
        and FCursorUpdate then
        QueryIntf(FIRowset, {$IFDEF CLR}IRowsetUpdate{$ELSE}IID_IRowsetUpdate{$ENDIF}, FIRowsetUpdate);

      FCommand.FIUnknown := nil;
    end;
  except
    ReleaseRecordSetInterfaces;
  end;
end;

procedure TOLEDBRecordSet.ReleaseRecordSetInterfaces;
begin
  FIRowsetUpdate := nil;

  FIRowset := nil;
  FIRowsetLocate := nil;

  if FNativeRowset then
    RequestParamsIfPossible;
end;

procedure TOLEDBRecordSet.ReleaseAllInterfaces(const ReleaseMultipleResults: boolean);
begin
  FreeFetchBlock;
  FCommand.FIUnknown := nil;
  ReleaseRecordSetInterfaces;
end;

procedure TOLEDBRecordSet.InternalOpen;
begin
  FLastFetchBack := False;
  FLastFetchOK := True;
  FLastFetchEnd := False;
  FHRowAccessible := False;
  if FCursorType = ctDynamic then 
    FRecordCount := - 1;
    
  FProcessDynBofEof := False;

  try
    if FNativeRowset then 
      QueryCommandInterfaces(False); // If QueryInterfaces already called then do nothing. Need to prevent clear FICommandText & FICommandProperties after command.execute before CreateFieldDescByRowset

    inherited;
  except
    if FNativeRowset then 
      ReleaseCommandInterfaces;

    if FCommand.GetCursorState = csFetched then
      FCommand.SetCursorState(csInactive);
      
    raise;
  end;
end;

procedure TOLEDBRecordSet.InternalClose;
begin
  if FCommand.FNonBlocking and (FFetchExecutor <> nil) then
    BreakFetch;

  ClearHRowIfNeed;
  FreeFetchBlock;
  if FNativeRowset then begin
    if not FLockClearMultipleResults then begin
      FCommand.ClearIMultipleResults;
      FCommand.FIUnknownNext := nil;
    end;
    FCommand.FIUnknown := nil;
    RequestParamsIfPossible;
  end;
  if FIRowset <> nil then // If FIRowset is not closed on Fetch by any reason
    ReleaseRecordSetInterfaces;

  FreeData; // To destroy Blobs

  if Assigned(FAfterExecFetch) then
    FAfterExecFetch(True);

  if FNativeRowset then
    ReleaseCommandInterfaces;

  FCommand.FCursorState := csInactive;
  if not Prepared then
    CommandType := ctUnknown;

  FCommand.FNextResultRequested := False;
  FLastFetchEnd := False;

  inherited;
end;

procedure TOLEDBRecordSet.ExecCommand;
  procedure ProcessCursorType;// Analyze CursorType changes
  var
    ActualCursorType: TMSCursorType; // Cursor type after OLEDB execute. 
    ActualSCReadOnly: boolean; // ReadOnly after OLEDB execute. Only for server cursors and CursorUpdate = True
  
    procedure AnalyzeCursorType; // Analyzed by FCommand.FIUnknown
    var
      RowsetInfo: IRowsetInfo;
      PropValues: TPropValues;

    begin
      with TOLEDBPropertiesGet.Create(FCommand.FConnection, DBPROPSET_ROWSET) do
        try
          AddPropId(DBPROP_SERVERCURSOR);
          AddPropId(DBPROP_OTHERUPDATEDELETE);
          AddPropId(DBPROP_IRowsetLocate);
          AddPropId(DBPROP_IRowsetUpdate);

          // Getting info interface
          if FCommand.FIUnknown <> nil then begin
            QueryIntf(FCommand.FIUnknown, {$IFDEF CLR}IRowsetInfo{$ELSE}IID_IRowsetInfo{$ENDIF}, RowsetInfo);
            GetProperties(RowsetInfo, PropValues);
          end
          else
          begin
            Assert(FCommand.FICommandProperties <> nil);
            GetProperties(FCommand.FICommandProperties, PropValues);
          end;
        finally
          Free;
        end;

      if {DBPROP_SERVERCURSOR} PropValues[0] = False then
        ActualCursorType := ctDefaultResultSet
      else
      begin // Static, KeySet, Dynamic
        if {DBPROP_OTHERUPDATEDELETE} PropValues[1] = False then begin // Static
          ActualCursorType := ctStatic;
          ActualSCReadOnly := True;
        end
        else
        begin // KeySet, Dynamic
          if {DBPROP_IRowsetLocate} PropValues[2] = False then // Dynamic
            ActualCursorType := ctDynamic
          else
            ActualCursorType := ctKeySet; // KeySet

          ActualSCReadOnly := {DBPROP_IRowsetUpdate} PropValues[3] = False;
        end;
      end;
    end;
    
  var
    IsChanged, IsROChanged: boolean;
  begin
    ActualCursorType := FCursorType;
    ActualSCReadOnly := FReadOnly;
    AnalyzeCursorType;

    IsROChanged := (FCursorType in ServerCursorTypes) and FCursorUpdate and (FReadOnly <> ActualSCReadOnly);
    IsChanged := (FCursorType <> ActualCursorType) 
      or IsROChanged;

    if IsChanged then begin
      FCursorType := ActualCursorType;
      if IsROChanged then
        FReadOnly := ActualSCReadOnly;

      Assert(Assigned(FCursorTypeChanged));
      try
        FCursorTypeChanged; // may be exception
      except
        FCommand.FIUnknown := nil;
        FCommand.ClearIMultipleResults;
        raise;
      end;
    end;
  end;
  
begin
  if not FNativeRowset then
    Exit; // CommandExec is not need for non-Native rowsets

  QueryCommandInterfaces(False);
  try
    if not Prepared
      and (FCommand.FIMultipleResults = nil)
      and FNativeRowset then // This is a first call to non-prepared DataSet.Command.Execute
      SetCommandProp;
    inherited;

    if (FCommand.FIUnknown <> nil)
      and FCommand.FLastExecWarning then
        ProcessCursorType;

    /// Must be after ProcessCursorType to prevent wrong setting CommandType
    if (FCommand.FIUnknown <> nil)
      or (FIRowset <> nil) then
      CommandType := ctCursor
    else
      CommandType := ctStatement;

  finally
    ReleaseCommandInterfaces;
    if CommandType <> ctCursor then
      FCommand.SetCursorState(csInactive);
  end;
end;

procedure TOLEDBRecordSet.Open;
begin
  try
    inherited;
  except
    if FCommand.FQueryIntCnt > 0 then
      FCommand.ReleaseInterfaces;
    raise;
  end;
end;

procedure TOLEDBRecordSet.Reopen;
begin
  if not FNativeRowset then begin
    Close;
    SetIRowset(FCommand.FConnection.GetSchemaRowset(FSchema, FRestrictions), False);
  end;

  inherited;
end;

function TOLEDBRecordSet.GetSchemaRowset(const Schema: TGUID; rgRestrictions: TRestrictions): IRowset;
begin
  FSchema := Schema;
  FRestrictions := rgRestrictions;

  Assert(FCommand.FConnection <> nil);
  Result := FCommand.FConnection.GetSchemaRowset(FSchema, FRestrictions);
end;

procedure TOLEDBRecordSet.Disconnect;
begin
  Assert(FCommand <> nil);
  FCommand.FIUnknown := nil;
  // FCommand.FIMultipleRes
  
  //Cache connection depenednt information
  GetProviderPrimaryVer;
  GetDBMSPrimaryVer;
  GetProviderId;
  GetProvider;
  GetDisconnectedMode;
  GetDatabase;

  ReleaseAllInterfaces(True);
  inherited;
end;

procedure TOLEDBRecordSet.CreateCommand;
var
  Cmd: TOLEDBCommand;
begin
  Cmd := TOLEDBCommand.Create;
  Cmd.FRequestIUnknown := True;
  SetCommand(Cmd);
end;

procedure TOLEDBRecordSet.SetCommand(Value: TCRCommand);
begin
  inherited;

  FCommand := TOLEDBCommand(Value);
end;

function TOLEDBRecordSet.GetIRowset: IRowset;
begin
  Result := FIRowset;
end;

function TOLEDBRecordSet.GetICommandText: ICommandText;
begin
  Result := FCommand.FICommandText;
end;

procedure TOLEDBRecordSet.SetIRowset(
      Rowset: IRowset; 
      const IsColumnsRowset: boolean); // If True then FieldDescs was stored in FColumnsRowsetFieldDescs
begin
  Close;
  Unprepare;

  FIRowset := Rowset;
  FNativeRowset := False;
  FReadOnly := True; // Non-native rowset cannot be modified
  FCommand.FCursorState := csExecuted;
  FFlatBuffers := True;
  CommandType := ctUnknown;
  FIsColumnsRowset := IsColumnsRowset;
end;

procedure TOLEDBRecordSet.Check(const Status: HRESULT);
begin
  if FNativeRowset then
    FCommand.FConnection.Check(Status, Component)
  else
    if Status <> S_OK then
      raise EOLEDBError.Create(Status, Format(SOLEDBError, [Status]));
end;

procedure TOLEDBRecordSet.RequestParamsIfPossible;
begin
  if (FIRowsetLocate = nil) and (FIRowsetUpdate = nil) and (FIRowset = nil) then begin
    FCommand.RequestParamsIfPossible;
    if (FCommand.FIUnknown = nil) 
      and ((FCommand.GetCursorState = csFetching) or (FCommand.GetCursorState = csFetchingAll)) then begin
      FCommand.SetCursorState(csFetched);
      if not Prepared then
        ReleaseCommandInterfaces;
    end;
  end;
end;

class function TOLEDBRecordSet.GetTableInfoClass: TTableInfoClass;
begin
  Result := TOLEDBTableInfo;
end;

function TOLEDBRecordSet.GetProviderPrimaryVer: integer;
begin
  if (FCommand <> nil) and (FCommand.FConnection <> nil) then
    FProviderPrimaryVer := FCommand.FConnection.ProviderPrimaryVer;
  Result := FProviderPrimaryVer;
end;

function TOLEDBRecordSet.GetDBMSPrimaryVer: integer;
begin
  if (FCommand <> nil) and (FCommand.FConnection <> nil) then
    FDBMSPrimaryVer := FCommand.FConnection.DBMSPrimaryVer;
  Result := FDBMSPrimaryVer;
end;

function TOLEDBRecordSet.GetProviderId: TGuid;
begin
  if (FCommand <> nil) and (FCommand.FConnection <> nil) then
    FProviderId := FCommand.FConnection.FProviderId;
  Result := FProviderId;
end;

function TOLEDBRecordSet.GetProvider: TOLEDBProvider;
begin
  if (FCommand <> nil) and (FCommand.FConnection <> nil) then
    FProvider := FCommand.FConnection.FProvider;
  Result := FProvider;
end;

function TOLEDBRecordSet.GetDisconnectedMode: boolean;
begin
  if (FCommand <> nil) and (FCommand.FConnection <> nil) then
    FDisconnectedMode := FCommand.FConnection.DisconnectedMode;
  Result := FDisconnectedMode;
end;

function TOLEDBRecordSet.GetDatabase: string;
begin
  if (FCommand <> nil) and (FCommand.FConnection <> nil) then
    FDatabase := FCommand.FConnection.FDatabase;
  Result := FDatabase;
end;

procedure TOLEDBRecordSet.CheckBCDOverflow(const FieldNo: integer {from 1}; RecBuf: IntPtr);
begin
  if (Fields[FieldNo - 1].DataType = dtBCD)
    and (GetStatus(FieldNo - 1, RecBuf) = DBSTATUS_E_DATAOVERFLOW) then
    raise Exception.Create(SBCDOverflow);
end;

procedure TOLEDBRecordSet.CheckAndAnalyzeFieldsStatus(const Status: HRESULT; const pRec: IntPtr);
const
  FieldHeader = 'Field[%d] %s - %s (Status = %Xh).';
var
  i: integer;
  FieldStatus: DWORD;
  FieldName: string;
  Msg: WideString;
begin
  if (Status <> S_OK) and (Status <> DB_S_ERRORSOCCURRED) then
    try
      Check(Status);
    except
      on E: Exception do begin
        Msg := '';
        for i := 0 to FFields.Count - 1 do
          if FFields[i].FieldDescKind = fdkData then begin
            FieldName := FFields[i].Name;
            if FieldName = '' then
              FieldName := IntToStr(i)
            else
              FieldName := ':' + FieldName;
            FieldStatus := GetStatus(i, pRec);
            case FieldStatus of
              DBSTATUS_S_OK, DBSTATUS_S_ISNULL, DBSTATUS_S_DEFAULT:;
              DBSTATUS_E_BADACCESSOR:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SBadAccessor, FieldStatus]);
              DBSTATUS_E_CANTCONVERTVALUE:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SInvalidValue, FieldStatus]);
              DBSTATUS_S_TRUNCATED:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SDataTruncated, FieldStatus]);
              DBSTATUS_E_SIGNMISMATCH:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SSignMismatch, FieldStatus]);
              DBSTATUS_E_DATAOVERFLOW:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SDataOverflow, FieldStatus]);
              DBSTATUS_E_CANTCREATE:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SCantCreate, FieldStatus]);
              DBSTATUS_E_UNAVAILABLE:
                {AddInfoToErr(Msg, FieldHeader, [i, FieldName, SUnavaible, FieldStatus])};
              DBSTATUS_E_PERMISSIONDENIED:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SPermissionDenied, FieldStatus]);
              DBSTATUS_E_INTEGRITYVIOLATION:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SIntegrityViolation, FieldStatus]);
              DBSTATUS_E_SCHEMAVIOLATION:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SShemaViolation, FieldStatus]);
              DBSTATUS_E_BADSTATUS:
                AddInfoToErr(Msg, FieldHeader, [i, FieldName, SBadStatus, FieldStatus]);
            else
              AddInfoToErr(Msg, FieldHeader, [i, FieldName, SUnknownStatus, FieldStatus]);
            end;
          end;
        AddInfoToErr(E, Msg, []);
        raise E;
      end;
    end;
end;

procedure TOLEDBRecordSet.InternalInitFields;
  function ConvertDBCOLUMNINFOToFieldDesc(
    // const prgInfoEl: PDBCOLUMNINFO;
    const FieldName: string; // prgInfoEl.pwszName
    const ActualFieldName: string; // if prgInfoEl.columnid.eKind = DBKIND_NAME then ActualFieldName := prgInfoEl.columnid.uName.pwszName else ActualFieldName := prgInfoEl.pwszName;
    const FieldNo: integer; // prgInfoEl.iOrdinal
    const OLEDBType: DBTYPE; // prgInfoEl.wType
    const dwFlags: DBCOLUMNFLAGS; // prgInfoEl.dwFlags
    const IsAutoIncrement: boolean;
    const Precision: integer; //prgInfoEl.bPrecision
    const Scale: integer; //prgInfoEl.bScale
    const ColumnSize: UINT; // prgInfoEl.ulColumnSize

    // Options
    const LongStrings: boolean;
    // Result
    Field: TOLEDBFieldDesc
    ): boolean;// return False, if this field type not supported

    function IsFlagSetted(const Flag: UINT): boolean;
    begin
      Result := (dwFlags and Flag) <> 0;
    end;

    procedure ConvertFlags;
    begin
      // DBCOLUMNFLAGS_ISFIXEDLENGTH, DBCOLUMNFLAGS_ISLONG,
      // DBCOLUMNFLAGS_ISNULLABLE, DBCOLUMNFLAGS_MAYBENULL
      // DBCOLUMNFLAGS_WRITE, DBCOLUMNFLAGS_WRITEUNKNOWN
      // DBCOLUMNFLAGS_ISROWID, DBCOLUMNFLAGS_ISROWVER
      // DBCOLUMNFLAGS_SCALEISNEGATIVE

      Field.Fixed := IsFlagSetted(DBCOLUMNFLAGS_ISFIXEDLENGTH);
      Field.IsKey := IsFlagSetted(DBCOLUMNFLAGS_KEYCOLUMN);
      Field.Required := not IsFlagSetted(DBCOLUMNFLAGS_ISNULLABLE);
      Field.FIsTimestamp := IsFlagSetted(DBCOLUMNFLAGS_ISROWVER);

      Field.ReadOnly :=
        (not IsFlagSetted(DBCOLUMNFLAGS_WRITE) and
         not IsFlagSetted(DBCOLUMNFLAGS_WRITEUNKNOWN));

      if FNativeRowset
        and not (FCursorType in ServerCursorTypes)
        and Field.ReadOnly then
        Field.ReadOnly := Field.FIsTimestamp or IsAutoIncrement;

      Field.FIsAutoIncrement := IsAutoIncrement;
    end;
    
    function CreateUniqueFieldName(const FieldName: string): string;
    var
      AliasNum: integer;
    begin
      if FieldName = '' then begin
        AliasNum := 1;
        repeat
          Result := 'COLUMN' + IntToStr(AliasNum);
          Inc(AliasNum);
        until FindField(Result) = nil;
      end
      else
        Result := FieldName;
    end;

  var                                
    InternalType: word;

  begin
    Field.SubDataType := dtUnknown;
    
    Result := ConvertOLEDBTypeToInternalFormat(OLEDBType, IsFlagSetted(DBCOLUMNFLAGS_ISLONG), FEnableBCD, {$IFDEF VER6P}FEnableFMTBCD{$ELSE}False{$ENDIF}, FWideStrings, False, InternalType, DBMSPrimaryVer);
    if not Result then
      Exit;

    // --- Correct access to SQL 2000 server from 7.0 Client
    if FNativeRowset
      and ((ProviderPrimaryVer < 8) and not IsWindowsVista)
      and (Provider <> prCompact)
      and (OLEDBType = DBTYPE_NUMERIC)
      and (Precision = 19) then
      InternalType := dtInt64;
    // ---

  {$IFDEF LITE}
    if (InternalType = dtFmtBCD)
      and not ((Scale > 4) or (Precision > 14)) then
      InternalType := dtBCD;
  {$ENDIF}

    Field.DataType := InternalType;

    if FNativeRowset then
      Field.Name := CreateUniqueFieldName(FieldName)
    else
      Field.Name := FieldName;
    Field.ActualName := ActualFieldName;
    Field.FieldNo := FieldNo;

    ConvertFlags;
    // Correct access to 'bigint' fields from Delphi4
  {$IFDEF VER4}
    if Field.DataType = dtInt64 then
      Field.ReadOnly := True;
  {$ENDIF}

    {if FNativeRowset then
      Field.Hidden := integer(prgInfoEl.columnid.uGuid.pguid) <> 1;
    // :( This is impossible becouse we needs TField for update SQLs and for ColumnsMetaInfo}

    // WAR Field.Size must be syncronized with actual data size in PutFieldData, GetFieldData, and possible PutFieldAsVariant, GetFieldAsVariant

    case InternalType of
      // Integer fields
      dtBoolean:
        Field.Size := sizeof(WordBool);
      dtInt8: begin
        Field.Size := sizeof(byte);
        if DBMSPrimaryVer = 3 then
          Field.SubDataType := Field.SubDataType or dtUInt8;
      end;
      dtWord: begin
        if (DBMSPrimaryVer = 3) and (OLEDBType = DBTYPE_UI1) then
          Field.SubDataType := Field.SubDataType or dtUInt8;
        Field.Size := sizeof(word);
      end;
      dtInt16:
        Field.Size := sizeof(smallint);
      dtInt32, dtUInt32:
        Field.Size := sizeof(integer);
      dtInt64:
        Field.Size := sizeof(int64);

      // Float fields
      dtFloat:
      begin
        Field.Size := sizeof(double);
        Field.Scale := Scale;
        Field.Length := Precision; // Precision cannot be greater then 15

        if OLEDBType = DBTYPE_R4 then begin
          Field.Length := 7;
          if DBMSPrimaryVer = 3 then
            Field.SubDataType := Field.SubDataType or dtSingle;
        end
        else
          if OLEDBType = DBTYPE_R8 then
            Field.Length := 15;
      end;
      dtCurrency:
      begin
        Field.Size := sizeof(double);
        //Field.Size := sizeof(currency); //Currency type cannot be used over TCurrencyField uses double to store
        Field.Scale := Scale;
        Field.Length := Precision; // Precision cannot be greater then 15
      end;

      // Multibyte fields
      dtBCD:
      begin
        Field.Size := sizeof(Currency);
        Field.Scale := Scale;
        Field.Length := Precision;
      end;

    {$IFDEF VER6P}
      dtFmtBCD:
      begin
        if Precision < SizeOfTBcd then
          Field.Size := SizeOfTBcd + 1{'+/-'} + 1{'.'} + 1{#0}
        else
          Field.Size := Precision + 1{'.'} + 1 {#0}; // To right notation of large NUMERIC values
        Field.Scale := Scale;
        Field.Length := Precision;
      end;
   {$ENDIF}

      dtDateTime:
        if DBMSPrimaryVer <> 3 then
          Field.Size := sizeof(TDateTime)
        else
          Field.Size := sizeof(TDBTimeStamp);
      dtDate, dtTime:
        Field.Size := sizeof(TDateTime);
    {$IFDEF VER5P}
      dtGuid:
      begin
        Field.Length := 38; { Length(GuidString) }
        Field.Size := Field.Length + 1;
      end;
   {$ENDIF}
      dtString, dtWideString, dtMemo, dtWideMemo, dtMSXML:
      begin
        Field.Length := 0; // WAR Field.Size must be syncronized with actual data size in PutFieldData, GetFieldData, and possible PutFieldAsVariant, GetFieldAsVariant
      {$IFNDEF VER5P}
        if prgInfoEl.wType = DBTYPE_GUID then
        begin
          Field.Length := 38; { Length(GuidString) }
          Field.Size := Word(Field.Length + 1);
        end
        else
      {$ENDIF}
        if IsFlagSetted(DBCOLUMNFLAGS_ISLONG) then begin
          // This is a MS SQL 'text' or 'ntext' field
          Field.SubDataType := dtText;
          Field.Size := Word(sizeof(IntPtr)) {ISeqStream};
          if not FReadOnly then
            Field.Size := Word(Field.Size + Word(sizeof(integer))) {DBLENGTH};
        end
        else  // This is a MS SQL 'char', 'varchar', 'nchar' or 'nvarchar' field
        begin
          if not LongStrings and (ColumnSize > 255) then begin
            if InternalType = dtWideString then
              Field.DataType := dtWideMemo
            else
              Field.DataType := dtMemo;
            Field.Size := Word(sizeof(IntPtr)) {ISeqStream};
            if not FReadOnly then
              Field.Size := Word(Field.Size + Word(sizeof(integer))) {DBLENGTH};
          end
          else
          begin
            Field.Length := ColumnSize;
            if InternalType = dtWideString then
              Field.Size := Word((ColumnSize + 1) * sizeof(WideChar))
            else
              Field.Size := Word(ColumnSize + 1);

            if not FFlatBuffers and (Field.Size >= FlatBufferLimit) and (FCursorType = ctDefaultResultSet) then
            begin
              Field.Size := Word(sizeof(IntPtr));
              if InternalType = dtString then
                Field.DataType := dtExtString
              else
                Field.DataType := dtExtWideString;
            end;
          end;
        end;

        if (OLEDBType = DBTYPE_WSTR) or (OLEDBType = DBTYPE_XML) then
          Field.SubDataType := Field.SubDataType or dtWide;
      end;
      dtBytes, dtVarBytes, dtBlob:
      begin
        if IsFlagSetted(DBCOLUMNFLAGS_ISLONG) then begin
          // This is a MS SQL 'image' field
          // WAR Field.Size must be syncronized with actual data size in PutFieldData, GetFieldData, and possible PutFieldAsVariant, GetFieldAsVariant
          Field.Size := Word(sizeof(IntPtr)) {ISeqStream};
          if not FReadOnly then
            Field.Size := Word(Field.Size + Word(sizeof(integer))) {DBLENGTH};
        end
        else
        begin
          Field.Length := Word(ColumnSize);

          if Field.Fixed then begin
            if FReadOnly then
              Field.Size := Word(Field.Length)
            else
              Field.Size := Word(Field.Length + Word(sizeof(UINT))) {OLE DB readed bytes};
          end
          else
            if FFlatBuffers or (ColumnSize < FlatBufferLimit) or (FCursorType <> ctDefaultResultSet) then
            begin
              Field.DataType := dtVarBytes;
              Field.Size := Word(Word(sizeof(word)) {Readed bytes} + Field.Length + Word(sizeof(UINT))) {OLE DB readed bytes};
            end
            else
            begin
              Field.Length := Word(ColumnSize);
              Field.DataType := dtExtVarBytes;
              Field.Size := Word(sizeof(IntPtr) {IntPtr to OLEDBBuf/StringHeap} + sizeof(UINT)) {OLE DB readed bytes};
            end;
        end;
      end;
    {$IFDEF VER5P}
      dtVariant:
        Field.Size := sizeof(TVariantObject);
    {$ENDIF}
      else
        Result := False;
    end;
  end;

  procedure CreateFieldDescs(
    const IUnk: IUnknown;
    const ByInfo: boolean // if True, then FieldDescs creates with using ColumnsInfo, otherwise - using ColumnsRowset
  );

    procedure CreateFieldDescsByInfo;
      function GetHiddenColumnsCount: UINT;
      var
        PropValues: TPropValues;
      begin
        with TOLEDBPropertiesGet.Create(FCommand.FConnection, DBPROPSET_ROWSET) do
          try
            AddPropId(DBPROP_HIDDENCOLUMNS);
            GetProperties(FCommand.FICommandProperties, PropValues);
          finally
            Free;
          end;
        Result := PropValues[0];
      end;

    var
      ColumnsInfo: IColumnsInfo;
      i: integer;
      Field, BookmarkField: TOLEDBFieldDesc;

      cColumns: UINT;
      prgInfo: PDBCOLUMNINFO;
      prgInfoEl: PDBCOLUMNINFO;
      rgInfoEl: {$IFDEF CLR}DBCOLUMNINFO{$ELSE}PDBCOLUMNINFO{$ENDIF};
      pStringsBuffer: IntPtr;

      //Malloc: IMalloc;

      ActualFieldName: string;

    begin
      if IUnk = nil then
        Exit; // This query does not return rowset

      QueryIntf(IUnk, {$IFDEF CLR}IColumnsInfo{$ELSE}IID_IColumnsInfo{$ENDIF}, ColumnsInfo);
      Assert(ColumnsInfo <> nil);

      pStringsBuffer := nil;
      Check(ColumnsInfo.GetColumnInfo(cColumns, PDBCOLUMNINFO(prgInfo), pStringsBuffer));

    {$IFDEF LITE}
      Assert(not FUniqueRecords);
    {$ENDIF}
      // Add hidden columns count for SQL 2000
      if FNativeRowset and ((ProviderPrimaryVer >= 8) or IsWindowsVista) and FUniqueRecords then
        cColumns := cColumns + GetHiddenColumnsCount;

      if cColumns > 0 then
        try
          BookmarkField := nil;
          prgInfoEl := prgInfo;
          for i := 0 to cColumns - 1 do begin
          {$IFDEF CLR}
            Assert(prgInfoEl <> nil);
            rgInfoEl := DBCOLUMNINFO(Marshal.PtrToStructure(prgInfoEl, TypeOf(DBCOLUMNINFO)));
          {$ELSE}
            rgInfoEl := prgInfoEl;
          {$ENDIF}
            Field := TOLEDBFieldDesc.Create;
            try
              if rgInfoEl.columnid.eKind = DBKIND_NAME then
                ActualFieldName := Marshal.PtrToStringUni(rgInfoEl.columnid.uName{$IFNDEF CLR}.pwszName{$ENDIF})
              else
                ActualFieldName := rgInfoEl.pwszName;

              if ConvertDBCOLUMNINFOToFieldDesc(
                // rgInfoEl
                rgInfoEl.pwszName,
                ActualFieldName,
                rgInfoEl.iOrdinal,
                rgInfoEl.wType,
                rgInfoEl.dwFlags,
                False,
                rgInfoEl.bPrecision,
                rgInfoEl.bScale,
                rgInfoEl.ulColumnSize,
                FLongStrings, Field) then begin

                Field.FOLEDBType := rgInfoEl.wType;
                if Field.FieldNo > 0 then
                  FFields.Add(Field)
                else // Bookmark column have FieldNo = 0
                begin
                  Field.Hidden := True;
                  BookmarkField := Field;
                  FBookmarkOffset := - 1;
                end;
              end
              else
                if FNativeRowset then
                  DatabaseErrorFmt(SBadFieldType, [rgInfoEl.pwszName, rgInfoEl.wType])
                else
                  Field.Free;
            except
              Field.Free;
              BookmarkField.Free;
              raise;
            end;
            prgInfoEl := IntPtr(Integer(prgInfoEl) +
            {$IFDEF CLR}
              Marshal.SizeOf(TypeOf(DBCOLUMNINFO))
            {$ELSE}
              sizeof(DBCOLUMNINFO)
            {$ENDIF});
          end;                   
          if BookmarkField <> nil then
            FFields.Add(BookmarkField);
            
        finally
          {if FCommand.FConnection = nil then
            CoGetMalloc(1, Malloc)
          else
            Malloc := FCommand.FConnection.Malloc;}

          FreeCoMem(prgInfo);
          FreeCoMem(pStringsBuffer);
          //Malloc.Free(prgInfo);
          //Malloc.Free(pStringsBuffer);

          //Malloc := nil;
        end;
    end;

  {$IFNDEF LITE}
    procedure CreateFieldDescsByRowset;
    var
      RecBuf: IntPtr;
      ColumnsMetaInfo: TOLEDBRecordSet;

      procedure CheckColumnsMetaInfo;
      begin
        if FCommand.FConnection.FColumnsMetaInfo <> nil then
          ColumnsMetaInfo := FCommand.FConnection.FColumnsMetaInfo
        else
        begin
          ColumnsMetaInfo := TOLEDBRecordSet.Create;
          FCommand.FConnection.FColumnsMetaInfo := ColumnsMetaInfo;
        
          ColumnsMetaInfo.SetConnection(FCommand.FConnection);
          ColumnsMetaInfo.SetProp(prFetchAll, False);
          ColumnsMetaInfo.SetProp(prFetchRows, 1);
          ColumnsMetaInfo.SetProp(prUniDirectional, True);
          FCommand.FConnection.FFldCatalogNameIdx := -1;
        end;
      end;

      procedure CheckColumnsMetaInfoIdx;
      begin
        Assert(ColumnsMetaInfo <> nil);
        if FCommand.FConnection.FFldCatalogNameIdx <> - 1 then
          Exit;

        with FCommand.FConnection do begin
          FFldCatalogNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_BASECATALOGNAME')); // SQL Everywhere
          FFldSchemaNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_BASESCHEMANAME')); // SQL Everywhere

          FFldTableNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_BASETABLENAME'));
          FFldColumnNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_BASECOLUMNNAME'));

          FFldPrecisionIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_PRECISION'));
          FFldScaleIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_SCALE'));
          FFldGuidIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_GUID'));

          /// ??? May be swap?
          FFldFieldNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_IDNAME'));
          FFldActualFieldNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_NAME'));

          FFldColumnNumberIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_NUMBER'));

          FFldIsAutoIncIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_ISAUTOINCREMENT')); // SQL Everywhere
          FFldTypeIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_TYPE'));
          FFldFlagsIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_FLAGS'));
          FFldColumnSizeIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FieldByName('DBCOLUMN_COLUMNSIZE'));
          FFldComputeModeIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_COMPUTEMODE'));

          // xml schema support
          FFldXMLSchemaCollCatalogNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_XML_SCHEMACOLLECTION_CATALOGNAME'));
          FFldXMLSchemaCollSchemaNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_XML_SCHEMACOLLECTION_SCHEMANAME'));
          FFldXMLSchemaCollNameIdx := FColumnsMetaInfo.Fields.IndexOf(FColumnsMetaInfo.FindField('DBCOLUMN_XML_SCHEMACOLLECTIONNAME'));
        end;

      end;

      procedure FillTablesAliases;
      var
        Parser: TMSParser;
        TableName: string;// Table or view name
        StLex, Alias: string;
        CodeLexem: integer;
        TableInfo: TCRTableInfo;
      begin
        TablesInfo.BeginUpdate;
        Parser := TMSParser.Create(FCommand.SQL);
        Parser.OmitBlank := False;
        Parser.OmitComment := True;
        try
          if Parser.ToLexem(lxSELECT) <> lcEnd then
            if Parser.ToLexem(lxFROM) <> lcEnd then
              repeat
                repeat
                 CodeLexem := Parser.GetNext(StLex);// Omit blank
                until CodeLexem <> lcBlank;

                // TableName
                TableName := StLex;
                while True do begin
                  CodeLexem := Parser.GetNext(StLex);

                  if (Length(StLex) > 0) and (StLex[1] = ',') then
                    Break;

                  if {(Length(StLex) > 0) and (StLex[1] in ['[, ' + '], ' + '", ' + '.']) and} (CodeLexem <> 0) and (CodeLexem <> lcBlank) then
                    TableName := TableName + StLex
                  else
                    Break;
                end;

                // 'AS' clause
                if Parser.GetNext(Alias) = lxAS then
                  Parser.GetNext(Alias)
                else
                  Parser.Back;

                // Alias
                if Parser.GetNext(Alias) = lcIdent then
                  Parser.GetNext(StLex)
                else begin
                  Alias := '';
                  Parser.Back;
                end;

                TableName := UnBracketIfPossible(TableName);
                Assert(TableName <> '', 'TableName cannot be empty'); // ++++

                TableInfo := TablesInfo.FindByName(TableName);
                if TableInfo = nil then begin
                  TableInfo := TablesInfo.Add;
                  TableInfo.TableName := TableName;
                  TableInfo.TableAlias := '';
                  TableInfo.IsView := True;
                end;

                if Alias <> '' then
                  TableInfo.TableAlias := TableInfo.NormalizeName(Alias);
              until (StLex <> ',');
        finally
          Parser.Free;
          TablesInfo.EndUpdate;
        end;
      end;
  
      function GetStrValue(Idx: integer): string;
      var
        Field: TOLEDBFieldDesc;
      begin
        if ColumnsMetaInfo.GetNull(Idx + 1, RecBuf) then
          Result := ''
        else
        begin
          Field := TOLEDBFieldDesc(ColumnsMetaInfo.Fields[Idx]);
          if (Field.SubDataType and dtWide) = 0 then
            Result := Marshal.PtrToStringAnsi(IntPtr(Integer(RecBuf) + Field.Offset))
          else
            Result := Marshal.PtrToStringUni(IntPtr(Integer(RecBuf) + Field.Offset));
        end;
      end;

      function GetWordValue(Idx: integer): Word;
      begin
        if ColumnsMetaInfo.GetNull(Idx + 1, RecBuf) then
          Result := 0
        else
          Result := Word(Marshal.ReadInt16(RecBuf, ColumnsMetaInfo.Fields[Idx].Offset));
      end;

      function GetLongWordValue(Idx: integer): Word;
      begin
        if ColumnsMetaInfo.GetNull(Idx + 1, RecBuf) then
          Result := 0
        else
          Result := LongWord(Marshal.ReadInt32(RecBuf, ColumnsMetaInfo.Fields[Idx].Offset));
      end;

      function GetUINTValue(Idx: integer): UINT;
      begin
        if ColumnsMetaInfo.GetNull(Idx + 1, RecBuf) then
          Result := 0
        else
          Result := UINT(Marshal.ReadInt32(RecBuf, ColumnsMetaInfo.Fields[Idx].Offset));
      end;

      function GetSmallIntValue(Idx: integer): SmallInt;
      begin
        if ColumnsMetaInfo.GetNull(Idx + 1, RecBuf) then
          Result := 0
        else
          Result := SmallInt(Marshal.ReadInt16(RecBuf, ColumnsMetaInfo.Fields[Idx].Offset));
      end;

    var
      ColumnsRowset: IColumnsRowset;
      CMIRowset: IRowset;

      Field, BookmarkField: TOLEDBFieldDesc;
      FieldNo: integer;
      TableName: string;

      FieldName, ActualFieldName: string;
      iu: IUnknown;
      FldGuidValue: string;

      TableInfo: TCRTableInfo;
      IsAutoIncrement: boolean;

      ColumnsRecordSet: TOLEDBRecordSet;
      Value: variant;
    begin
      if IUnk = nil then
        Exit; // This query does not return rowset

      Assert (FRequestSQLObjects and FNativeRowset);

      QueryIntf(IUnk, {$IFDEF CLR}IColumnsRowset{$ELSE}IID_IColumnsRowset{$ENDIF}, ColumnsRowset);
      Check(ColumnsRowset.GetColumnsRowset(nil, 0, nil, IID_IRowset, 0, nil, iu));{Default properties - default result set}
      CMIRowset := IRowset(iu);
      Assert(CMIRowset <> nil);
      try
        FBookmarkOffset := - 2;
        BookmarkField := nil;
        CheckColumnsMetaInfo;

        ColumnsMetaInfo.SetIRowset(CMIRowset, True);
        //ColumnsMetaInfo.Prepare;
        ColumnsMetaInfo.Open;

        RecBuf := nil;
        try
          CheckColumnsMetaInfoIdx;

          ColumnsMetaInfo.AllocRecBuf(IntPtr(RecBuf));

          while True do begin
            ColumnsMetaInfo.GetNextRecord(RecBuf);
            if ColumnsMetaInfo.Eof then
              Break;

            FieldNo := GetLongWordValue(FCommand.FConnection.FFldColumnNumberIdx);
            Assert(FieldNo >= 0); // Bookmark column have FieldNo = 0

            Field := TOLEDBFieldDesc.Create;
            try
              {
              SELECT BaseColumnName AS ColumnAlias ...

              FIConnection.ProviderVer = [07.01.0623, 07.01.0690, 07.01.0819, 07.01.0961]
                FieldName = BaseColumnName
                ActualName = ColumnAlias
                BaseColumnName = BaseColumnName

              FIConnection.ProviderVer = [08.00.0194, 08.00.0528, 08.10.7430, 08.10.9001]
                FieldName = ColumnAlias
                ActualName = ColumnAlias
                BaseColumnName = BaseColumnName
              }

              ActualFieldName := GetStrValue(FCommand.FConnection.FFldActualFieldNameIdx);
              if (ProviderPrimaryVer < 8) and not IsWindowsVista then
                FieldName := ActualFieldName
              else
                FieldName := GetStrValue(FCommand.FConnection.FFldFieldNameIdx);

              IsAutoIncrement := False;
              if FCommand.FConnection.FFldIsAutoIncIdx <> - 1 then
                IsAutoIncrement := WordBool(GetWordValue(FCommand.FConnection.FFldIsAutoIncIdx));

              if ConvertDBCOLUMNINFOToFieldDesc(
                // prgInfoEl
                FieldName,
                ActualFieldName,
                FieldNo,
                GetWordValue(FCommand.FConnection.FFldTypeIdx),
                GetUINTValue(FCommand.FConnection.FFldFlagsIdx),
                IsAutoIncrement,
                GetWordValue(FCommand.FConnection.FFldPrecisionIdx),
                GetSmallIntValue(FCommand.FConnection.FFldScaleIdx),
                GetUINTValue(FCommand.FConnection.FFldColumnSizeIdx),

                // Options
                FLongStrings, Field) then begin

                Field.FOLEDBType := GetWordValue(FCommand.FConnection.FFldTypeIdx);
                if FCommand.FConnection.FFldCatalogNameIdx <> -1 then
                  Field.FBaseCatalogName := GetStrValue(FCommand.FConnection.FFldCatalogNameIdx);
                if FCommand.FConnection.FFldSchemaNameIdx <> -1 then
                  Field.FBaseSchemaName := GetStrValue(FCommand.FConnection.FFldSchemaNameIdx);
                Field.FBaseTableName := GetStrValue(FCommand.FConnection.FFldTableNameIdx);
                Field.FBaseColumnName := GetStrValue(FCommand.FConnection.FFldColumnNameIdx);

                if FCommand.FConnection.FFldXMLSchemaCollCatalogNameIdx <> -1 then
                  Field.FXMLSchemaCollectionCatalogName := GetStrValue(FCommand.FConnection.FFldXMLSchemaCollCatalogNameIdx);
                if FCommand.FConnection.FFldXMLSchemaCollSchemaNameIdx <> -1 then
                  Field.FXMLSchemaCollectionSchemaName := GetStrValue(FCommand.FConnection.FFldXMLSchemaCollSchemaNameIdx);
                if FCommand.FConnection.FFldXMLSchemaCollNameIdx <> -1 then
                  Field.FXMLSchemaCollectionName := GetStrValue(FCommand.FConnection.FFldXMLSchemaCollNameIdx);
                Field.FXMLTyped := (Field.XMLSchemaCollectionCatalogName <> '') or (Field.XMLSchemaCollectionSchemaName <> '') or
                  (Field.FXMLSchemaCollectionName <> '');

                Field.ReadOnly := Field.ReadOnly or (Field.FBaseColumnName = '');

                if FCommand.FConnection.FFldComputeModeIdx <> -1 then
                  Field.ReadOnly := Field.ReadOnly or (GetUINTValue(FCommand.FConnection.FFldComputeModeIdx) <> DBCOMPUTEMODE_NOTCOMPUTED);

                if (not FUniqueRecords) or (FCursorType in ServerCursorTypes) then // Hide implicitly requested columns
                  if (ProviderPrimaryVer >= 8) or IsWindowsVista then begin
                    FldGuidValue := LowerCase(GetStrValue(FCommand.FConnection.FFldGuidIdx));
                    Field.Hidden := (FldGuidValue <> '') and (FldGuidValue <> '{' + LowerCase(IntToHex(FieldNo, 8))+'-0000-0000-0000-000000000000' + '}')
                  end
                  else
                    Field.Hidden := not ColumnsMetaInfo.GetNull(FCommand.FConnection.FFldGuidIdx + 1, RecBuf);

                // Fill TablesInfo structure
                TableName := GenerateTableName(Field.BaseCatalogName, Field.BaseSchemaName, Field.BaseTableName, Database);
                if TableName <> '' then begin
                  TableInfo := TablesInfo.FindByName(TableName);
                  TablesInfo.BeginUpdate;
                  try
                    if TableInfo = nil then begin
                      TableInfo := TablesInfo.Add;
                      TableInfo.TableName := TableName;
                      TableInfo.TableAlias := '';
                    end;
                  finally
                    TablesInfo.EndUpdate;
                  end;
                  Field.TableInfo := TableInfo;
                end
                else
                  Field.TableInfo := nil;

                if FieldNo > 0 then
                  FFields.Add(Field)
                else // Bookmark column have FieldNo = 0
                begin
                  Field.Hidden := True;
                  BookmarkField := Field;
                  FBookmarkOffset := - 1;
                end;

              end
              else
                if FNativeRowset then
                  DatabaseErrorFmt(SBadFieldType, [FieldName, GetWordValue(FCommand.FConnection.FFldTypeIdx)])
                else
                  Field.Free;
            except
              Field.Free;
              BookmarkField.Free;
              raise;
            end;
          end;

          if BookmarkField <> nil then
            FFields.Add(BookmarkField);

          if (Provider = prCompact) and (not FPopulatingKeyInfo) and (FUniqueRecords or not FReadOnly) then begin
            if TablesInfo.Count > 0 then begin
              TableInfo := TablesInfo[0];
              ColumnsRecordSet := TOLEDBRecordSet.Create;
              FPopulatingKeyInfo := True;
              try
                ColumnsRecordSet.SetConnection(FCommand.FConnection);
                ColumnsRecordSet.SetSQL(Format(
                  'SELECT' + LineSeparator +
                  '  A.COLUMN_NAME, B.AUTOINC_INCREMENT' + LineSeparator +
                  'FROM' + LineSeparator +
                  '  INFORMATION_SCHEMA.KEY_COLUMN_USAGE A, INFORMATION_SCHEMA.COLUMNS B' + LineSeparator +
                  'WHERE' + LineSeparator +
                  '  A.TABLE_NAME = B.TABLE_NAME and A.COLUMN_NAME = B.COLUMN_NAME and A.TABLE_NAME = %s', [QuotedStr(TableInfo.TableName)]));
                ColumnsRecordSet.Open;
                if RecBuf <> nil then
                  ColumnsMetaInfo.FreeRecBuf(RecBuf);
                ColumnsRecordSet.AllocRecBuf(RecBuf);
                while True do begin
                  ColumnsRecordSet.GetNextRecord(RecBuf);
                  if ColumnsRecordSet.Eof then
                    Break;
                  if not ColumnsRecordSet.GetNull(ColumnsRecordSet.FieldByName('COLUMN_NAME').FieldNo, RecBuf) then begin
                    ColumnsRecordSet.GetFieldAsVariant(ColumnsRecordSet.FieldByName('COLUMN_NAME').FieldNo, RecBuf, Value);
                    Field := TOLEDBFieldDesc(FFields.FindField(String(Value)));
                    if Field <> nil then begin
                      Field.IsKey := True;
                      Field.FIsAutoIncrement := not ColumnsRecordSet.GetNull(ColumnsRecordSet.FieldByName('AUTOINC_INCREMENT').FieldNo, RecBuf);
                      if FNativeRowset and not (FCursorType in ServerCursorTypes) then
                        Field.ReadOnly := Field.FIsTimestamp or Field.IsAutoIncrement;
                    end;
                  end;
                end;
              finally
                ColumnsRecordSet.FreeRecBuf(RecBuf);
                RecBuf := nil;
                ColumnsRecordSet.Close;
                ColumnsRecordSet.Free;
                FPopulatingKeyInfo := False;
              end;
            end;
          end;

        finally
          if RecBuf <> nil then
            ColumnsMetaInfo.FreeRecBuf(RecBuf);
          if ColumnsMetaInfo <> nil then begin
            ColumnsMetaInfo.Close;
            ColumnsMetaInfo.UnPrepare;
          end;
          if IsLibrary then
            FreeAndNil(FCommand.FConnection.FColumnsMetaInfo);
        end;
      finally
      {$IFDEF CLR}
        if CMIRowset <> nil then
          Marshal.ReleaseComObject(CMIRowset);
      {$ENDIF}
      end;
      Assert(not (FCursorType in [ctStatic, ctKeySet]) or (FBookmarkOffset <> - 2));

      TablesInfo.Normalize;
      FillTablesAliases;
    // For
    //   SELECT FieldName FieldAlias FROM TableName TableAlias
    // must be
    //   Field.Name = 'FieldAlias'
    //   Field.ActualName = 'FieldAlias'
    //   Field.TableName = '' ???
    //   Field.BaseColumnName = 'c_int'
    //   Field.BaseTableName = 'ALL_TYPES'

    end;
  {$ENDIF}

  var
    Connection: TOLEDBConnection;

  begin
    if ByInfo then begin
      if FNativeRowset or not FIsColumnsRowset then
        CreateFieldDescsByInfo
      else
      begin
        // Detect true connection
        Connection := FCommand.FConnection;

        if Connection.FColumnsRowsetFieldDescs <> nil then
          Connection.AssignFieldDescs(Connection.FColumnsRowsetFieldDescs, FFields)
        else
        begin
          CreateFieldDescsByInfo;
          Connection.FColumnsRowsetFieldDescs := TFieldDescs.Create;
          try
            try
              Connection.AssignFieldDescs(FFields, Connection.FColumnsRowsetFieldDescs) // Save fields for columnsrowset
            except
              FreeAndNil(Connection.FColumnsRowsetFieldDescs);
              raise;
            end;
          finally
            if IsLibrary then
              FreeAndNil(Connection.FColumnsRowsetFieldDescs);
          end;
        end;
      end;
    end
    else
    {$IFNDEF LITE}
      CreateFieldDescsByRowset;
    {$ELSE}
      Assert(False);
    {$ENDIF}
  end;

begin
  inherited; // Empty proc call

  if not FNativeRowset then
    CommandType := ctCursor;

  if CommandType = ctUnknown then begin // This is a FieldDefs.Update call

    QueryCommandInterfaces(False);
    try
      SetCommandProp;
      FCommand.Execute;
    finally
      ReleaseCommandInterfaces; /// FCommand.QueryIntCnt counter is increased in inherited
    end;
    CreateFieldDescs(FCommand.FIUnknown, not FRequestSQLObjects);

    // Free interfaces
  {$IFDEF CLR}
    if FCommand.FIMultipleResults <> nil then
      Marshal.ReleaseComObject(FCommand.FIMultipleResults);
    if FCommand.FIUnknown <> nil then
      Marshal.ReleaseComObject(FCommand.FIUnknown);
  {$ENDIF}
    FCommand.FIUnknown := nil;
    FCommand.ClearIMultipleResults;

    // Free param accessors (must be after clearing interfaces)
    RequestParamsIfPossible;

    // We does not need to process non-Native rowsets or ServerCursors
    // QueryRecordSetInterfaces not required too
    Exit;
  end;

  if not FNativeRowset then 
    CreateFieldDescs(FIRowset, True)
  else
  begin
    Assert(FIRowset = nil);
    if Prepared and not FCommand.FRPCCall then
      CreateFieldDescs(FCommand.FICommandPrepare, not FRequestSQLObjects)
    else
      CreateFieldDescs(FCommand.FIUnknown, not FRequestSQLObjects);
  end;
end;

procedure TOLEDBRecordSet.AllocFetchBlock;
var
  UseIRowsetUpdate: boolean;

  function AddFieldToABList(const FieldNum: integer): integer; // Add new AB, if need. If AB already present then return its index
    function AddAB(const BlockType: TAccessorBlockType): integer;
    var
      l: integer;
    begin
      l := Length(FFetchAccessorData.AccessorBlocks);
      SetLength(FFetchAccessorData.AccessorBlocks, l + 1);

      FFetchAccessorData.AccessorBlocks[l].BlockType := BlockType;
      FFetchAccessorData.AccessorBlocks[l].hAcc := 0;
      FFetchAccessorData.AccessorBlocks[l].BlobFieldNum := -1;

      Result := l;
    end;

  var
    BlockType: TAccessorBlockType;
    FieldDesc: TOLEDBFieldDesc;
    i: integer;
    IsLarge: boolean;
    IsDBNumeric: boolean;
  begin
    Result := -1;
    try
      FieldDesc := Fields[FieldNum] as TOLEDBFieldDesc;
      IsLarge := IsLargeDataTypeUsed(FieldDesc);
      IsDBNumeric := (ProviderPrimaryVer = 3) and (FieldDesc.OLEDBType = DBTYPE_NUMERIC);
      if IsNeedFetchBlock(FieldDesc, ProviderPrimaryVer) then
        BlockType := abFetchBlock // This is a string by ref - long string conversion used
      else
        if FieldDesc.ReadOnly and UseIRowsetUpdate then
          BlockType := abReadOnly
        else
          if IsLarge and UseIRowsetUpdate then
            BlockType := abBLOB
          else
            BlockType := abOrdinary;

      if not (FCursorType in [ctKeySet, ctDynamic]) // CR 4082
        and (BlockType <> abBLOB) then
        for i := 0 to Length(FFetchAccessorData.AccessorBlocks) - 1 do
          if FFetchAccessorData.AccessorBlocks[i].BlockType = BlockType then begin
            // Test BLOB fields
            if IsLarge then begin
              if FFetchAccessorData.AccessorBlocks[i].BlobFieldNum = - 1 then
                FFetchAccessorData.AccessorBlocks[i].BlobFieldNum := FieldNum
              else
              begin
                BlockType := abBLOB;
                Result := AddAB(BlockType);
                FFetchAccessorData.AccessorBlocks[Result].BlobFieldNum := FieldNum;
                Exit;
              end;
            end;
            // Test numeric fields in Everywhere
            if IsDBNumeric then begin
              Result := AddAB(BlockType);
              Exit;
            end;

            Result := i;
            Exit;
          end;

      // Accessor block not found! Create new
      Result := AddAB(BlockType);
      if IsLarge then
        FFetchAccessorData.AccessorBlocks[Result].BlobFieldNum := FieldNum;
    finally
      Assert(Result <> - 1);

      i := Length(FFetchAccessorData.AccessorBlocks[Result].FieldNums);
      SetLength(FFetchAccessorData.AccessorBlocks[Result].FieldNums, i + 1);
      FFetchAccessorData.AccessorBlocks[Result].FieldNums[i] := FieldNum;
    end;

  end;

  // Fill internal structures in accessor block
  procedure FillBindingStructInAccBlock(
    rgBindings: TDBBindingArray;
    var AccessorBlock: TAccessorBlock);
  var
    Cnt, i, l: integer;
    FieldNum: integer;
    Field: TFieldDesc;

    Obj: OLEDBIntf.DBOBJECT;
  begin
    Cnt := Length(AccessorBlock.FieldNums);
    Assert(Cnt > 0);
    //OFS('+FillBindingStructInAccBlock');
    for i := 0 to Cnt - 1 do
      with rgBindings[i] do begin
        FieldNum := AccessorBlock.FieldNums[i];
        Field := FFields[FieldNum];
        iOrdinal := Field.FieldNo;

        obValue := Field.Offset;
        obLength := 0;
        obStatus := DataSize + FieldNum * OLE_DB_INDICATOR_SIZE;

        dwPart := DBPART_VALUE or DBPART_STATUS;
        dwMemOwner := DBMEMOWNER_CLIENTOWNED;
        eParamIO := DBPARAMIO_NOTPARAM;
        cbMaxLen := Field.Size;

        if (FCursorType in [ctStatic, ctKeyset]) and (iOrdinal = 0) then
          dwFlags := DBCOLUMNFLAGS_ISBOOKMARK
        else
          dwFlags := 0;

        wType := ConvertInternalTypeToOLEDB(Field.dataType, False, DBMSPrimaryVer);
        if (DBMSPrimaryVer = 3) then begin
          if (TOLEDBFieldDesc(Field).OLEDBType = DBTYPE_R4) then
            wType := DBTYPE_R4;
          if (TOLEDBFieldDesc(Field).OLEDBType = DBTYPE_UI1) then begin
            wType := DBTYPE_UI1;
            cbMaxLen := SizeOf(Word);
          end;
          if (TOLEDBFieldDesc(Field).OLEDBType = DBTYPE_NUMERIC) then begin
            wType := DBTYPE_NUMERIC;
            cbMaxLen := SizeOfTDBNumeric;
          end;
        end;

        if not IsLargeDataTypeUsed(Field) then begin //???
          case Field.DataType of
            dtExtString, dtExtWideString:
              begin
                if Field.DataType = dtExtString then
                  l := 1
                else
                if Field.DataType = dtExtWideString then
                  l := 2
                else
                begin
                  l := 0;
                  Assert(False);
                end;

                Assert(AccessorBlock.BlockType = abFetchBlock);
                dwPart := DBPART_VALUE or DBPART_STATUS or DBPART_LENGTH;
                cbMaxLen := MaxNonBlobFieldLen + l;
                obStatus := FFetchBlockSize;
                obLength := obStatus + OLE_DB_INDICATOR_SIZE;
                obValue := obLength + 4;
                IncFetchBlockOffset(FFetchBlockSize, Field.DataType);
              end;
            dtMemo, dtWideMemo, dtMSXML: // Long string conversion used
              begin
                Assert(AccessorBlock.BlockType = abFetchBlock);
                if (Field.SubDataType and dtWide) = 0 then begin
                  wType := DBTYPE_STR;
                  l := 1;
                end
                else
                begin
                  wType := DBTYPE_WSTR;
                  l := 2;
                end;
                cbMaxLen := MaxNonBlobFieldLen + l;
                obStatus := FFetchBlockSize;
                obValue := obStatus + OLE_DB_INDICATOR_SIZE;
                IncFetchBlockOffset(FFetchBlockSize, dtMemo);
              end;
            dtVariant:
              begin
                Assert(AccessorBlock.BlockType = abFetchBlock);
                wType := DBTYPE_VARIANT;
                cbMaxLen := sizeof(OleVariant);
                obStatus := FFetchBlockSize;
                obValue := obStatus + OLE_DB_INDICATOR_SIZE;
                IncFetchBlockOffset(FFetchBlockSize, dtVariant);
              end;
            dtBytes:
              if not FReadOnly then begin
                dwPart := DBPART_VALUE or DBPART_STATUS or DBPART_LENGTH;
                obValue := Field.Offset;
                obLength := obValue + Field.Length;
                cbMaxLen := Field.Length;
              end;
            dtVarBytes:
              begin
                dwPart := DBPART_VALUE or DBPART_STATUS or DBPART_LENGTH;
                obValue := Field.Offset + sizeof(word);
                obLength := obValue + Field.Length;
                cbMaxLen := Field.Length;
              end;
            dtExtVarBytes:
              begin
                Assert(AccessorBlock.BlockType = abFetchBlock);

                cbMaxLen := MaxNonBlobFieldLen; // WAR on changing must change FetchExternalAccessorBlock FetchExternalAccessorBlock
                dwPart := DBPART_VALUE or DBPART_STATUS or DBPART_LENGTH;
                obStatus := FFetchBlockSize;
                obValue := obStatus + OLE_DB_INDICATOR_SIZE;
                obLength := obValue + cbMaxLen;
                IncFetchBlockOffset(FFetchBlockSize, Field.DataType);
              end;
            dtFloat, dtBcd:
              if (DBMSPrimaryVer = 3) and (TOLEDBFieldDesc(Field).OLEDBType = DBTYPE_NUMERIC) then begin
                Assert(AccessorBlock.BlockType = abFetchBlock);
                wType := DBTYPE_NUMERIC;
                cbMaxLen := SizeOfTDBNumeric;
                obStatus := FFetchBlockSize;
                obValue := obStatus + OLE_DB_INDICATOR_SIZE;
                IncFetchBlockOffset(FFetchBlockSize, Field.DataType);
              end;
          end;
        end
        else
        begin
          Assert(Field.DataType in [dtMemo, dtWideMemo, dtBlob, dtMSXML], 'Non-compartible values of Field.DataType and IsStreamUsed');

          Obj.iid := IID_ISequentialStream;
          Obj.dwFlags := STGM_READ;
          l := sizeof(DBOBJECT);
          pObject := Marshal.AllocHGlobal(l);
        {$IFDEF CLR}
          Marshal.StructureToPtr(TObject(Obj), pObject, False);
        {$ELSE}
          DBOBJECT(pObject^) := Obj;
        {$ENDIF}

          if not FReadOnly then begin
            dwPart := DBPART_VALUE or DBPART_STATUS or DBPART_LENGTH;
            obLength := obValue + sizeof(IntPtr);
          end;

        end;

        if wType in [DBTYPE_NUMERIC, DBTYPE_VARNUMERIC] then begin
          bPrecision := Field.Length;
          bScale := Field.Scale;
        end;

        {OFS('---');
        OFS('Field.Name = ' + Field.Name);
        OFS('iOrdinal = ' + IntToStr(iOrdinal));
        OFS('obValue = ' + IntToStr(obValue));
        OFS('obLength = ' + IntToStr(obLength));
        OFS('obStatus = ' + IntToStr(obStatus));
        OFS('pTypeInfo = ' + IntToStr(Integer(pTypeInfo)));
        //OFS('pObject = ' + IntToStr(Integer(pObject)));
        OFS('pBindExt = ' + IntToStr(Integer(pBindExt)));
        OFS('dwPart = ' + IntToStr(Integer(dwPart)));
        //dwMemOwner: DBMEMOWNER;
        //eParamIO: DBPARAMIO;
        OFS('cbMaxLen = ' + IntToStr(cbMaxLen));
        OFS('dwFlags = ' + IntToStr(dwFlags));
        OFS('wType = ' + IntToStr(Integer(wType)));
        OFS('bPrecision = ' + IntToStr(bPrecision));
        OFS('bScale = ' + IntToStr(bScale));}
      end;
    //OFS('-FillBindingStructInAccBlock');
  end;

var
  i, j: integer;

  rgStatus: PUINT;
  rgBindings: TDBBindingArray;
{$IFDEF CLR}
  rgBindingsGC: GCHandle;
{$ENDIF}
  hr: HResult;

  FieldCntAB: integer;

begin
  rgStatus := nil;

  FFetchBlock := nil;
  FFetchBlockSize := 0;
  UseIRowsetUpdate := (FCursorType in [ctKeyset, ctDynamic]) and FCursorUpdate;

  QueryIntf(FIRowset, {$IFDEF CLR}IAccessor{$ELSE}IID_IAccessor{$ENDIF}, FFetchAccessorData.Accessor);

  // Separate fields to AccessorBlocks
  for i := 0 to Fields.Count - 1 do
    if Fields[i].FieldDescKind = fdkData then
      AddFieldToABList(i);

  // CreateAccessors
  for i := 0 to Length(FFetchAccessorData.AccessorBlocks) - 1 do begin
    FieldCntAB := Length(FFetchAccessorData.AccessorBlocks[i].FieldNums);
    try
      rgStatus := Marshal.AllocHGlobal(FieldCntAB * SizeOf(UINT));
      SetLength(rgBindings, FieldCntAB);

      for j := 0 to FieldCntAB - 1 do
        with rgBindings[j] do begin
        {$IFDEF CLR}
          if pTypeInfo <> nil then
            Marshal.Release(pTypeInfo);
        {$ELSE}
          pTypeInfo := nil;
        {$ENDIF}
          pObject := nil;
          pBindExt := nil;
        end;

      FillBindingStructInAccBlock(rgBindings, FFetchAccessorData.AccessorBlocks[i]);

      // Create accessor
    {$IFDEF CLR}
      rgBindingsGC := GCHandle.Alloc(rgBindings, GCHandleType.Pinned);
      try
        hr := FFetchAccessorData.Accessor.CreateAccessor(DBACCESSOR_ROWDATA, FieldCntAB, Marshal.UnsafeAddrOfPinnedArrayElement(rgBindings, 0), 0, FFetchAccessorData.AccessorBlocks[i].hAcc, rgStatus);
      finally
        rgBindingsGC.Free;
      end;
    {$ELSE}
      hr := FFetchAccessorData.Accessor.CreateAccessor(DBACCESSOR_ROWDATA, FieldCntAB, rgBindings, 0, FFetchAccessorData.AccessorBlocks[i].hAcc, rgStatus);
    {$ENDIF}
      Check(hr);
    finally
      if Length(rgBindings) <> 0 then begin
        for j := 0 to FieldCntAB - 1 do
          if rgBindings[j].pObject <> nil then
            Marshal.FreeHGlobal(rgBindings[j].pObject);
        SetLength(rgBindings, 0);
      end;
      if rgStatus <> nil then begin
        Marshal.FreeHGlobal(rgStatus);
        rgStatus := nil;
      end;
    end;
  end;

  if FFetchBlockSize <> 0 then
    FFetchBlock := Marshal.AllocHGlobal(FFetchBlockSize);
end;

procedure TOLEDBRecordSet.FreeFetchBlock;
var
  AccNum: integer;
begin
  if FFetchAccessorData.Accessor = nil then
    Exit;

  with FFetchAccessorData do begin
    for AccNum := 0 to Length(AccessorBlocks) - 1 do
      Check(Accessor.ReleaseAccessor(AccessorBlocks[AccNum].hAcc, nil));
  {$IFDEF CLR}
    Marshal.ReleaseComObject(Accessor);
  {$ENDIF}
    Accessor := nil;
    SetLength(AccessorBlocks, 0);
  end;

  if FFetchBlock <> nil then
    Marshal.FreeHGlobal(FFetchBlock);
end;

function TOLEDBRecordSet.Fetch(FetchBack: boolean = False): boolean;
var
  OldFetchFromBookmark: boolean;

  procedure GetDataFromRow(const Row: hRow; const pRec: IntPtr);
    procedure PrepareConvertableFields; // After get data from OLEDB
    var
      i: integer;
      pValue: IntPtr;

      OleDbBuf: IntPtr;
      Field: TOLEDBFieldDesc;

    {$IFDEF VER6P}
    {$IFOPT C+}
      FieldStatus: DWORD;
    {$ENDIF}
    {$IFDEF CLR}
      BcdOut: TBcd;
      g: TGuid;
      p: IntPtr;
      j: integer;
      BcdBuf: TBytes;
      s: string;
      DBNum: TDBNumeric;
      Data: TBytes;
    {$ELSE}
      FieldLength, FieldScale: word;
    {$IFDEF VER9}
      Delta: word;
    {$ENDIF}
    {$ENDIF}
      Bcd: TBcd;
    {$ENDIF}
      DBTimeStamp: TDBTimeStamp;
      dt: TDateTime;
      d: double;
      CurrTimestamp: Int64;
    begin
      for i := 0 to Fields.Count - 1 do begin
        Field := TOLEDBFieldDesc(Fields[i]);
        if Field.FieldDescKind <> fdkData then
          SetNull(i + 1, pRec, True)
        else
        begin
          pValue := IntPtr(Integer(pRec) + Field.Offset);

          // Get max Timestamp value for RefreshQuick
          if Field.IsTimestamp and (Field.TableInfo <> nil) then begin
          {$IFDEF CLR}
            SetLength(Data, SizeOf(Int64));
            Marshal.Copy(pValue, Data, 0, SizeOf(Int64));
            System.Array.Reverse(Data, 0, SizeOf(Int64));
            CurrTimestamp := BitConverter.ToInt64(Data, 0);
          {$ELSE}
            CurrTimestamp := Marshal.ReadInt64(pValue);
            Reverse8(@CurrTimestamp);
          {$ENDIF}
            if {$IFDEF VER7P}UInt64{$ENDIF}(TOLEDBTableInfo(Field.TableInfo).FMaxTimestamp) < {$IFDEF VER7P}UInt64{$ENDIF}(CurrTimestamp) then
              TOLEDBTableInfo(Field.TableInfo).FMaxTimestamp := CurrTimestamp;
          end;

          OleDbBuf := nil;
          try
            case Field.DataType of
              dtVarBytes:
                Marshal.WriteInt16(pValue, SmallInt(UINT(Marshal.ReadInt32(pValue, sizeof(word) + Field.Length))));
            {$IFDEF VER6P}
              dtFmtBCD:
                if not GetNull(i + 1, pRec) then begin
                {$IFOPT C+}
                  FieldStatus := GetStatus(i, pRec);
                  Assert(FieldStatus = DBSTATUS_S_OK, Field.Name +  ': FieldStatus = $' + IntToHex(FieldStatus, 8){ + ', Value = ' + Marshal.PtrToStringAnsi(pValue)});
                {$ENDIF}
                {$IFDEF CLR}
                  DBNum := TDBNumeric(Marshal.PtrToStructure(pValue, TypeOf(TDBNumeric)));
                  Bcd := DBNumericToBCD(DBNum);
                  NormalizeBcd(Bcd, BcdOut, Field.Length, Field.Scale);

                  // Copied from TBcd.ToBytes
                  SetLength(BcdBuf, 34);
                  BcdBuf[0] := BcdOut.Precision;
                  BcdBuf[1] := BcdOut.SignSpecialPlaces;
                  for j := 0 to 31 do
                    BcdBuf[j + 2] := BcdOut.Fraction[j];
                  Marshal.Copy(BcdBuf, 0, pValue, 34);
                {$ELSE}
                  Bcd := DBNumericToBCD(TDBNumeric(pValue^));

                  FieldLength := Field.Length;
                  FieldScale := Field.Scale;
                {$IFDEF VER9} // Delphi 9 NormalizeBcd Bug
                  Delta := FieldLength - FieldScale;
                  if Delta > 34 then begin
                    Delta := 34;
                    FieldLength := FieldScale + Delta;
                  end;
                {$ENDIF}
                  NormalizeBcd(Bcd, PBcd(pValue)^, FieldLength, FieldScale);
                {$ENDIF}
                end;
            {$ENDIF}
            {$IFDEF VER5P}
              dtGuid:
                if not GetNull(i + 1, pRec) then
                {$IFDEF CLR}
                begin
                  g := TGUID(Marshal.PtrToStructure(pValue, TypeOf(TGUID)));
                  s := '{' + GUIDToString(g) + '}';
                  p := Marshal.StringToHGlobalAnsi(s);
                  try
                    StrLCopy(pValue, p, 38);
                  finally
                    Marshal.FreeHGlobal(p);
                  end;
                end;
                {$ELSE}
                  StrLCopy(pValue, @GUIDToString(PGUID(pValue)^)[1], 38);
                {$ENDIF}
            {$ENDIF}
              dtDateTime:
                if (Provider = prCompact) and (not GetNull(i + 1, pRec)) then begin
                {$IFDEF CLR}
                  DBTimeStamp := TDBTimeStamp(Marshal.PtrToStructure(pValue, TypeOf(TDBTimeStamp)));
                {$ELSE}
                  DBTimeStamp := PDBTimeStamp(pValue)^;
                {$ENDIF}
                  dt := {$IFNDEF CLR}MemUtils.{$ENDIF}EncodeDateTime(DBTimeStamp.year, DBTimeStamp.month, DBTimeStamp.day, DBTimeStamp.hour, DBTimeStamp.minute, DBTimeStamp.second, DBTimeStamp.fraction div 1000000{Billionths of a second to milliseconds});
                  Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(Double(dt)));
                end;
              dtCurrency:
                if Provider = prCompact then begin
                {$IFDEF CLR}
                  d := Marshal.ReadInt64(pValue);
                  d := d / 10000;
                {$ELSE}
                  d := Currency(pValue^);
                {$ENDIF}
                  Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(d));
                end;
              dtFloat:
                if (not GetNull(i + 1, pRec)) and (Provider = prCompact) then begin
                  if (Field.SubDataType and dtSingle) <> 0 then begin
                  {$IFDEF CLR}
                    BcdBuf := BitConverter.GetBytes(Marshal.ReadInt64(pValue));
                    d := BitConverter.ToSingle(BcdBuf, 0);
                  {$ELSE}
                    d := Single(pValue^);
                  {$ENDIF}
                    Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(d));
                  end
                end;
              dtWord:
                if (not GetNull(i + 1, pRec)) and (Provider = prCompact) and ((Field.SubDataType and dtUInt8) <> 0) then
                  Marshal.WriteByte(IntPtr(Integer(pValue) + 1), 0);
            end;
          finally
            if OleDbBuf <> nil then
              FreeCoMem(OleDbBuf);
          end;
        end;
      end;
    end;

    procedure FetchPlainAccessorBlock(const AccessorBlock: TAccessorBlock);
    var
      hr: HResult;
      Blob: TBlob;
      Field: TFieldDesc;
      pValue: IntPtr;
      Length: integer;

    begin
      // Get data from IRowset
      hr := FIRowset.GetData(Row, AccessorBlock.hAcc, pRec);
      CheckAndAnalyzeFieldsStatus(hr, pRec);

      // ConvertMemoToBlob;
      if AccessorBlock.BlobFieldNum <> -1 then begin
        Field := Fields[AccessorBlock.BlobFieldNum];
        pValue := IntPtr(Integer(pRec) + Field.Offset);
        Length := Marshal.ReadInt32(pRec, Field.Offset + SizeOf(IntPtr));
        if FReadOnly then
          Length := 0;
      {$IFDEF HAVE_COMPRESS}
        if Field.DataType = dtBlob then
          Blob := TCompressedBlob.Create((Field.SubDataType and dtWide) <> 0)
        else
      {$ENDIF}
          Blob := TBlob.Create((Field.SubDataType and dtWide) <> 0);

        try
          if (GetStatus(AccessorBlock.BlobFieldNum, pRec) <> DBSTATUS_S_ISNULL) then // Can't use GetNull->GetNullByBlob
            ConvertStreamToBlob(pValue, Length, Blob{$IFDEF HAVE_COMPRESS}, FCommand.FCompressBlob{$ENDIF},
              (Field is TOLEDBFieldDesc) and (TOLEDBFieldDesc(Field).OLEDBType = DBTYPE_XML));
        finally
          Marshal.WriteInt32(pValue, Integer(Blob.GCHandle));
        end;
      end;
    end;

  var
    FetchBlockOffset: integer;

    procedure FetchExternalAccessorBlock(const AccessorBlock: TAccessorBlock);
    var
      hr: HResult;
      i, FieldNum: integer;
      Status: DWORD;
      Blob: TSharedObject;
      pc: IntPtr;
      pValue, pFetchBlockValue: IntPtr;

      Field: TFieldDesc;
      l: integer;

      Size: word;
      HeapBuf: IntPtr;
      t: boolean;
      DBNumeric: TDBNumeric;
      d: double;
    {$IFNDEF CLR}
      c: currency;
    {$ENDIF}
      i64: Int64;

    begin
      Assert(AccessorBlock.BlobFieldNum = - 1);
      // Get data from IRowset
      hr := FIRowset.GetData(Row, AccessorBlock.hAcc, FFetchBlock);

      // Copy status from external buf to pRec. Need to correct work CheckAndAnalyzeFieldsStatus
      for i := 0 to Length(AccessorBlock.FieldNums) - 1 do begin
        FieldNum := AccessorBlock.FieldNums[i];
        Field := Fields[FieldNum];
        if IsNeedFetchBlock(Field, ProviderPrimaryVer) and (Field.FieldDescKind = fdkData) then begin
          Status := DWORD(Marshal.ReadInt32(FFetchBlock, FetchBlockOffset));
          SetStatus(FieldNum, pRec, Status);

          pFetchBlockValue := IntPtr(Integer(FFetchBlock) + FetchBlockOffset + OLE_DB_INDICATOR_SIZE);
          pValue := IntPtr(Integer(pRec) + Field.Offset);

          case Field.DataType of
            dtExtString, dtExtWideString: begin
              if GetNull(FieldNum + 1, pRec) then
                Marshal.WriteIntPtr(pValue, nil)
              else
              begin
                if Field.Fixed then
                  t := TrimFixedChar
                else
                  t := TrimVarChar;
                l := Marshal.ReadInt32(pFetchBlockValue);
                pFetchBlockValue := IntPtr(Integer(pFetchBlockValue) + 4);
                if Field.DataType = dtExtString then
                  Marshal.WriteIntPtr(pValue, StringHeap.AllocStr(pFetchBlockValue, t, l))
                else
                  Marshal.WriteIntPtr(pValue, StringHeap.AllocWideStr(pFetchBlockValue, t, l div 2));
              end;
            end;
            dtExtVarBytes:
              if GetNull(FieldNum + 1, pRec) then
                Marshal.WriteIntPtr(pValue, nil)
              else
              begin
                Size := UINT(Marshal.ReadInt32(pFetchBlockValue, MaxNonBlobFieldLen));
                HeapBuf := StringHeap.NewBuf(Size + sizeof(Word));
                CopyBuffer(pFetchBlockValue, IntPtr(Integer(HeapBuf) + sizeof(Word)), Size);
                Marshal.WriteIntPtr(pValue, HeapBuf);
                Marshal.WriteInt16(HeapBuf, SmallInt(Word(Size)));
              end;
            dtMemo, dtWideMemo, dtMSXML: begin
              Blob := TBlob.Create((Field.SubDataType and dtWide) <> 0);
              if Status <> DBSTATUS_S_ISNULL then begin
                pc := pFetchBlockValue;
                if TBlob(Blob).IsUnicode then
                  l := integer(StrLenW(pc)) * integer(sizeof(WideChar)) // D2005 CLR bug
                else
                  l := StrLen(pc);
                if l > 0 then begin
                  TBlob(Blob).Write(0, l, pc);
                  TBlobUtils.SetModified(TBlob(Blob), False);
                end;
              end;
              Marshal.WriteInt32(pValue, Integer(Blob.GCHandle));
            end;
            dtVariant: begin
              Blob := TVariantObject.Create;

              TVariantObject(Blob).Value := GetOleVariant(pFetchBlockValue);
              OleVarClear(pFetchBlockValue);

              Marshal.WriteInt32(pValue, Integer(Blob.GCHandle));
            end;
            dtFloat, dtBcd:
              if not GetNull(FieldNum + 1, pRec) then begin
              {$IFDEF CLR}
                DBNumeric := TDBNumeric(Marshal.PtrToStructure(pFetchBlockValue, TypeOf(TDBNumeric)));
              {$ELSE}
                DBNumeric := TDBNumeric(pFetchBlockValue^);
              {$ENDIF}
                d := DBNumericToDouble(DBNumeric);
                if Field.DataType = dtFloat then
                  Marshal.WriteInt64(pValue, BitConverter.DoubleToInt64Bits(d))
                else begin
                {$IFDEF CLR}
                  d := d * 10000;
                  i64 := Round(d);
                {$ELSE}
                  c := d;
                  i64 := Int64((@c)^);
                {$ENDIF}
                  Marshal.WriteInt64(pValue, i64);
                end;
              end;
            else
              Assert(False);
          end;
          IncFetchBlockOffset(FetchBlockOffset, Field.DataType);
          Assert(FetchBlockOffset <= FFetchBlockSize);

        end;
      end;
      CheckAndAnalyzeFieldsStatus(hr, pRec);
    end;

  var
    AccNum: integer;

  begin
    FetchBlockOffset := 0;
    for AccNum := 0 to Length(FFetchAccessorData.AccessorBlocks) - 1 do
      if FFetchAccessorData.AccessorBlocks[AccNum].BlockType = abFetchBlock then
        FetchExternalAccessorBlock(FFetchAccessorData.AccessorBlocks[AccNum])
      else
        FetchPlainAccessorBlock(FFetchAccessorData.AccessorBlocks[AccNum]);

    PrepareConvertableFields;
  end;

  procedure CreateBlockStruct(const pHBlock: PBlockHeader; const RowsObtained: UINT);
  var
    pHItem: PItemHeader;
    i: integer;
    ui: UINT;

  begin
  // Create Items
    pHItem := IntPtr(Integer(pHBlock) + sizeof(TBlockHeader));
    if IntPtr(FirstItem) = nil then
      FirstItem := pHItem;

    if IntPtr(LastItem) = nil then begin
      LastItem := pHItem;
      pHItem.Order := 0;
    end;

    for i := 0 to RowsObtained - 1 do begin
      pHItem.Prev := LastItem;
      pHItem.Next := nil;
      pHItem.Block := pHBlock;
      pHItem.Flag := flUsed;
      pHItem.Rollback := nil;
      pHItem.Status := isUnmodified;
      pHItem.UpdateResult := urNone;
      pHItem.FilterResult := fsNotChecked;

      LastItem.Next := pHItem;

      if not (FCursorType in ServerCursorTypes) then
        pHItem.Order := LastItem.Order + 1;

      LastItem := pHItem;

      UpdateCachedBuffer(pHItem, pHItem);

      pHItem := IntPtr(Integer(pHItem) + sizeof(TItemHeader) + RecordSize);
    end;

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

    case FCursorType of
      ctDefaultResultSet:
        if Filtered and not FFetchAll then
          InitFetchedItems(IntPtr(Integer(pHBlock) + sizeof(TBlockHeader)), False, FetchBack)
        else
          Inc(FRecordCount, RowsObtained);
      ctStatic, ctKeySet:
      begin
        if FBookmarkOffset = - 1 then
          FBookmarkOffset := sizeof(TBlockHeader) + sizeof(TItemHeader) + Fields[Fields.Count - 1].Offset;
        LastItem.Order := Marshal.ReadInt32(IntPtr(pHBlock), FBookmarkOffset);
        FBookmarkValue := LastItem.Order;
      end;
      ctDynamic:;
    end;
      
  // Free items
    ui := UINT(FFetchRows) - RowsObtained;
    if ui > 0 then
      for i := 0 to ui - 1 do begin
        pHItem.Prev := nil;
        pHItem.Next := BlockMan.FirstFree;
        pHItem.Block := pHBlock;
        pHItem.Flag := flFree;
        pHItem.Rollback := nil;

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

        pHItem := IntPtr(Integer(pHItem) + sizeof(TItemHeader) + RecordSize);
      end;

    pHBlock.UsedItems := RowsObtained;
  end;

  procedure InitBlock(pHBlock: PBlockHeader);
  var
    i, j: integer;
    Ptr: IntPtr;
    Field: TFieldDesc;
  begin
    if not HasComplexFields then
      Exit;

  // Create complex filds
    for i := 0 to FFetchRows - 1 do begin
      Ptr := IntPtr(Integer(pHBlock) + sizeof(TBlockHeader) + i * (RecordSize + sizeof(TItemHeader)) + sizeof(TItemHeader));

      /// We does not need to call CreateComplexFields(Ptr, True) because (in difference with ODAC) we fetch BLOB(IStream) IntPtrs directly to RecBuf
      for j := 0 to FieldCount - 1 do begin
        Field := Fields[j];
        if Field.FieldDescKind <> fdkCalculated then
          case Field.DataType of
            dtBlob, dtMemo, dtWideMemo, dtMSXML,  dtVariant, dtExtString, dtExtWideString, dtExtVarBytes:
              Marshal.WriteIntPtr(Ptr, Field.Offset, nil);
          end;
      end;
    end;
  end;

  procedure ClearBlock(pHBlock: PBlockHeader);
  var
    i: integer;
    Free: PItemHeader;
  begin
    if IntPtr(pHBlock) = nil then
      Exit;

    // Free complex filds
    Free := IntPtr(Integer(pHBlock) + sizeof(TBlockHeader));
    for i := 1 to pHBlock.ItemCount do begin
      if HasComplexFields and (Free.Flag <> flFree) then
        FreeComplexFields(IntPtr(Integer(Free) + sizeof(TItemHeader)), True);
      Free := IntPtr(Integer(Free) + sizeof(TItemHeader) + RecordSize);
    end;
  end;

  function GetRowsFromOLEDB(var RowsObtained: UINT; prghRows: PUintArray): HResult;
  var
    RowsOffset, RowsRequested: integer;
  {$IFDEF CLR}
    p: IntPtr;
  {$ENDIF}
  begin
    // Backward fetch processing
    if FetchBack then
      RowsRequested := - FFetchRows
    else
      RowsRequested := FFetchRows;

    RowsOffset := 0;

    // Get data from OLEDB
    if (FCursorType in [ctKeyset, ctStatic]) then begin
      if not FFetchFromBookmark then
        if FetchBack then
          RowsOffset := - 1
        else
          RowsOffset := + 1;

      Assert(FIRowsetLocate <> nil);
      /// FIRowsetLocate.GetRowsAt does not change current IRowset fetch position

    {$IFDEF CLR}
      p := Marshal.AllocHGlobal(SizeOf(Integer));
      try
        Marshal.WriteInt32(p, FBookmarkValue);
        Result := FIRowsetLocate.GetRowsAt(0, DB_NULL_HCHAPTER, FBookmarkSize, p, RowsOffset, RowsRequested, RowsObtained, prghRows);
      finally
        Marshal.FreeHGlobal(p);
      end;
    {$ELSE}
      Result := FIRowsetLocate.GetRowsAt(0, DB_NULL_HCHAPTER, FBookmarkSize, @FBookmarkValue, RowsOffset, RowsRequested, RowsObtained, prghRows);
    {$ENDIF}
    end
    else
    begin
      if (FCursorType = ctDynamic) then begin
        if FLastFetchOK then begin
          if FFetchFromBookmark then begin // Reread previous readed row
            if FLastFetchBack = FetchBack then begin
              if FetchBack then
                RowsOffset := + 1
              else
                RowsOffset := - 1;
            end;
          end
          else
            if FLastFetchBack <> FetchBack then
              if FetchBack then
                RowsOffset := - 1
              else
                RowsOffset := + 1;
        end
        else
          if FLastFetchBack = FetchBack then
            if FetchBack then
              RowsOffset := + 1
            else
              RowsOffset := - 1;
      end;

      Result := FIRowset.GetNextRows(DB_NULL_HCHAPTER, RowsOffset, RowsRequested, RowsObtained, prghRows);
    end;
    FLastFetchBack := FetchBack;
    FFetchFromBookmark := False; // Clear flag, setted on InternalOpen
  end;

  procedure ProcessNoResult;
  begin
    case FCursorType of
      ctDefaultResultSet:
        ReleaseAllInterfaces(False); // Process parameters
      ctStatic:
      begin
        Assert(not FFetchFromBookmark, 'Cannot fetch to bookmark with Static cursor type');
        if not OldFetchFromBookmark {to prevent recursion on empty resultset} then
          FetchToBookmarkValue;
      end;
      ctKeySet:
      begin
        if FCursorUpdate then
          Assert(not FFetchFromBookmark, 'Cannot fetch to bookmark with KeySet cursor type');
{        else
          FBookmarkValue := - 1;}
      end;
      ctDynamic:
      begin
        CurrentItem := nil; // FHRow is not accessible and we need to refetch data from server

        if not FProcessDynBofEof then begin
          try
            FProcessDynBofEof := True;
            if FetchBack then begin
              FBof := True;

              // Server cursor position is under first row, FHRow is not accessible
              // Need to call GetNextRows with params (RowsOffset = 1, RowsRequested = - 1)
              FLastFetchOK := True;
              if not FetchToBookmarkValue(True) then
                FEof := True;
            end
            else
            begin
              FEof := True;

              // Server cursor position is below last row, FHRow is not accessible
              // Need to call GetNextRows with params (RowsOffset = - 1, RowsRequested = 1)
              FLastFetchOK := True;              
              if not FetchToBookmarkValue then 
                FBof := True;
            end;

            if not FLastFetchOK then begin
              FLastFetchOK := True;
              FLastFetchBack := False;
            end;
          finally
            FProcessDynBofEof := False;
          end;

          if FBof or FEof then
            CurrentItem := nil;
        end;
      end;
    end;
  end;

  procedure FirstFetch;
  var
    Field: TOLEDBFieldDesc;
    i: integer;
  begin
    // This is a first call to Fetch.
    // Query interfaces, create accessors etc

    Assert(FCommand.GetCursorState >= csExecuted);
    if FCommand.GetCursorState = csExecuted then
      FCommand.SetCursorState(csFetching);

    QueryRecordSetInterfaces;
    AllocFetchBlock;

    if FCursorType in [ctStatic, ctKeySet] then begin
      // Setting FRecordCount for ctStatic, ctKeySet
      SetToEnd;
      if IntPtr(LastItem) <> nil then begin
        FRecordCount := LastItem.Order;
        SetToBegin;
      end;
      FFetchFromBookmark := FCursorType in [ctKeyset, ctStatic]; /// First record reading without offsetting
    end;
    FBookmarkValue := DBBMK_FIRST;
    FBookmarkSize := sizeof(FBookmarkValue);

    // Clear MaxTimestamp for RefreshQuick
    for i := 0 to Fields.Count - 1 do begin
      Field := TOLEDBFieldDesc(Fields[i]);
      if Field.IsTimestamp and (Field.TableInfo <> nil) then
        TOLEDBTableInfo(Field.TableInfo).FMaxTimestamp := 0;
    end;
  end;

var
  pHBlock: PBlockHeader;
  NewBlock: boolean;

  hr: HResult;
  RowsObtained: UINT;
  rghRows: TUintArray;
  prghRows: PUintArray;
  GCHandle: IntPtr;

  i: integer;
  pRec, pData: IntPtr;

  Cancel: boolean;

  IsThisFirstFetch: boolean;
  InThread: boolean;
begin
  Result := False;
  Assert(FCommand <> nil);
{$IFDEF CLR}
  InThread := FCommand.FNonBlocking and (FFetchExecutor <> nil) and (Thread.CurrentThread = FFetchExecutor.Thread.Handle);
{$ELSE}
  InThread := FCommand.FNonBlocking and (FFetchExecutor <> nil) and (GetCurrentThreadId = FFetchExecutor.Thread.ThreadID);
{$ENDIF}
  try
    if Fields.Count = 0 then
      DatabaseError(SNoResultSet, nil); /// Warning - constant SNoResultSet used for detecting in TCustomMSDataSet.OpenNext

    IsThisFirstFetch :=
      ((FCommand.FIUnknown <> nil) or not FNativeRowset)
       and (Length(FFetchAccessorData.AccessorBlocks) = 0);
    try
      if IsThisFirstFetch then
        FirstFetch; // This is a first call to Fetch. FIUnknown tested for prevent recreating accessors after fetching all strings

      DoBeforeFetch(Cancel);

      if Cancel or FWaitForFetchBreak then
        Exit;

      if FIRowset = nil then
      begin
        Result := False;
        Exit;
      end;

      if (FCursorType = ctKeySet) and not FCursorUpdate and (FBookmarkValue = - 1) then
      begin
        Result := False;
        Exit;
      end;

      OldFetchFromBookmark := FFetchFromBookmark;

       // Clear previous obtained rows
      ClearHRowIfNeed;
      Assert(not FHRowAccessible);

      pHBlock := nil;
      GCHandle := nil;
      prghRows := nil;
      RowsObtained := 0;
      SetLength(rghRows, FFetchRows);
      try
        // Get next rows
        GCHandle := AllocGCHandle(rghRows, True);
        prghRows := GetAddrOfPinnedObject(GCHandle);
        // prghRows := @rghRows[0];
        if FLastFetchEnd then
          RowsObtained := 0
        else
        begin
          hr := GetRowsFromOLEDB(RowsObtained, prghRows);
          FLastFetchEnd := FNativeRowset and (FCursorType = ctDefaultResultSet) and (hr = DB_S_ENDOFROWSET); // CR10007

          // Process rows
          if (hr <> DB_S_ENDOFROWSET)
            and (hr <> DB_S_ROWLIMITEXCEEDED)
            and not ((hr = DB_E_BADBOOKMARK) and (FBookmarkValue = DBBMK_FIRST))
            and not ((hr = DB_E_BADSTARTPOSITION) and (ProviderPrimaryVer = 7))then
            Check(hr);
        end;
        if RowsObtained > 0 then begin
          NewBlock := (IntPtr(BlockMan.FirstBlock) = nil) or (not FUniDirectional and not (FCursorType in ServerCursorTypes));
          if NewBlock then
            BlockMan.AllocBlock(pHBlock, FFetchRows)
          else begin
            pHBlock := BlockMan.FirstBlock;
            // Refresh block: drop values of blobs
            ClearBlock(pHBlock);
          end;
          InitBlock(pHBlock);
          pRec := IntPtr(Integer(pHBlock) + sizeof(TBlockHeader) + sizeof(TItemHeader));
          for i := 0 to RowsObtained - 1 do
          begin
            pData := IntPtr(Integer(pRec) + i * (RecordSize + sizeof(TItemHeader)));
            GetDataFromRow(rghRows[i], pData);
          end;
        end;
      finally
        FreeGCHandle(GCHandle);
        if RowsObtained > 0 then begin
          // Release row handle(s) if need
          if FCursorType in ServerCursorTypes then begin
            Assert(RowsObtained = 1);
            FHRow := rghRows[0];
            FHRowAccessible := True;
          end
          else
            Check(FIRowset.ReleaseRows(RowsObtained, prghRows, nil, nil, nil));
        end;

        if Length(rghRows) <> 0 then
          SetLength(rghRows, 0);
      end;
      Result := RowsObtained > 0;
      FLastFetchOK := Result;

      if IntPtr(pHBlock) <> nil then begin
        if Result then
          CreateBlockStruct(pHBlock, RowsObtained)
        else
          BlockMan.FreeBlock(pHBlock);
      end;

      if FNativeRowset and not Result then
        ProcessNoResult;

      Assert(not (FCursorType in [ctDynamic]{ServerCursorTypes}) or FProcessDynBofEof or (FHRowAccessible or (FBOF and FEOF)), 'Row must be accessible after Fetch for non-empty dataset with server cursor');
    except
      on e: exception do begin
        if FetchBack then
          FBOF := True
        else
          FEOF := True;

        ClearBlock(pHBlock);
        FreeFetchBlock;
        ReleaseRecordSetInterfaces;

        raise;
      end;
    end;
  finally
    if Assigned(FOnAfterFetch) then
      if InThread and FAfterFetch then
        FFetchExecutor.Thread.SendEvent(TObject(FE_AFTERFETCH))
      else
        DoAfterFetch;
  end;
end;

function TOLEDBRecordSet.GetProp(Prop: integer; var Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prReadOnly:
      Value := FReadOnly;
    prEnableBCD:
      Value := FEnableBCD;
  {$IFDEF VER6P}
    prEnableFMTBCD:
      Value := FEnableFMTBCD;
  {$ENDIF}
    prUniqueRecords:
      Value := FUniqueRecords;
    prCursorType:
      Value := Integer(TMSCursorType(FCursorType));
    prCursorUpdate:
      Value := FCursorUpdate;
    prLockClearMultipleResults:
      Value := FLockClearMultipleResults;
    prIsSProc:
      FCommand.GetProp(prIsSProc, Value);
    prNonBlocking:
      FCommand.GetProp(prNonBlocking, Value);
{    prBeforeFetch:
      Value := FBeforeFetch;
    prAfterFetch:
      Value := FAfterFetch;}
    else
      Result := inherited GetProp(Prop, Value);
  end;
end;

function TOLEDBRecordSet.SetProp(Prop: integer; const Value: variant): boolean; 
begin
  Result := True;
  case Prop of
    prReadOnly: 
      FReadOnly := Value;
    prCommandTimeout:
    begin
      Assert(FCommand <> nil);
      FCommand.FCommandTimeout := Value;
    end;
    prEnableBCD:
      FEnableBCD := Value;
  {$IFDEF VER6P}
    prEnableFMTBCD:
      FEnableFMTBCD := Value;
  {$ENDIF}
    prUniqueRecords:
      FUniqueRecords := Value;
    prCursorType:
    begin
      FCursorType := TMSCursorType(Integer(Value));
      if FCursorType in ServerCursorTypes then
        FFetchAll := False;
    end;
    prRequestSQLObjects:
      FRequestSQLObjects := Value;
    prCursorUpdate:
      FCursorUpdate := Value;
    prLockClearMultipleResults:
      FLockClearMultipleResults := Value;
    prRoAfterUpdate:
      FroAfterUpdate := Value;
    prWideStrings:
      FWideStrings := Value;
    prIsSProc:
      FCommand.SetProp(prIsSProc, Value);
    prSmartRefresh:
      FCommand.SetProp(prSmartRefresh, Value);
    prSmartRefreshMsg:
      FCommand.SetProp(prSmartRefreshMsg, Value);
    prSmartRefreshService:
      FCommand.SetProp(prSmartRefreshService, Value);
    prNonBlocking:
      FCommand.SetProp(prNonBlocking, Value);
{    prBeforeFetch:
      FBeforeFetch := Boolean(Value);
    prAfterFetch:
      FAfterFetch := Boolean(Value);}
    else
      Result := inherited SetProp(Prop, Value);
  end;
end;

{ TOLEDBProperties }

{ TOLEDBPropertiesSet }

const
  MaxPropCount = 20;
  
constructor TOLEDBPropertiesSet.Create(Connection: TOLEDBConnection; const GuidPropertySet: TGUID);
begin
  inherited Create;

  FConnection := Connection;

  FInitPropSet := Marshal.AllocHGlobal(SizeOf(DBPROPSET));
  FInitPropSet.cProperties := 0;
  FInitPropSet.guidPropertySet := GuidPropertySet;
  FInitPropSet.rgProperties := nil;

  FInitPropSet.rgProperties := Marshal.AllocHGlobal(MaxPropCount * SizeOfDBProp);
  FillChar(FInitPropSet.rgProperties, MaxPropCount * SizeOfDBProp, 0);

end;

destructor TOLEDBPropertiesSet.Destroy;
var
  i: integer;
  rgProperty: PDBProp;
begin
  if IntPtr(FInitPropSet) <> nil then begin
    if FInitPropSet.rgProperties <> nil then begin
      for i := 0 to Integer(FInitPropSet.cProperties) - 1 do begin
        rgProperty := GetDBPropPtr(i);
        rgProperty.vValue := Unassigned;
      end;

      Marshal.FreeHGlobal(FInitPropSet.rgProperties);
    end;

    Marshal.FreeHGlobal(FInitPropSet);
  end;

  inherited;
end;

procedure TOLEDBPropertiesSet.Check(const Status: HRESULT);
begin
  try
    FConnection.Check(Status, nil);
  except
    on E: Exception do begin
      AddInfoToErr(E, GetInitPropSetStatus, []);
      raise E;
    end;
  end;
end;

function TOLEDBPropertiesSet.GetInitPropSetStatus: string;
var
  i: integer;
  p: PDBProp;
begin
  Result := GUIDToString(FInitPropSet.guidPropertySet);

  for i := 0 to FInitPropSet.cProperties - 1 do begin
    p := GetDBPropPtr(i);
    if p.dwStatus <> 0 then
      Result := Format('%s'#$D#$A'[%d] := $%X. PropId := %d', [Result, i, p.dwStatus, p.dwPropertyID]);
  end;

end;

function TOLEDBPropertiesSet.GetDBPropPtr(Index: UINT): PDBProp;
begin
  Assert(Index <= FInitPropSet.cProperties);
  Result := IntPtr(Integer(FInitPropSet.rgProperties) + Integer(Index * SizeOfDBProp));
end;

function TOLEDBPropertiesSet.InitProp(const dwPropertyID: DBPROPID; const Required: boolean = False): PDBProp;
begin
  Assert(FInitPropSet.cProperties <= MaxPropCount);

  Result := GetDBPropPtr(FInitPropSet.cProperties);
  Result.dwPropertyID := dwPropertyID;
  if Required then
    Result.dwOptions := DBPROPOPTIONS_REQUIRED
  else
    Result.dwOptions := DBPROPOPTIONS_OPTIONAL;
  Result.colid := DB_NULLID;
end;


procedure TOLEDBPropertiesSet.AddPropSmallInt(const dwPropertyID: DBPROPID; const Value: Smallint);
var
  p: PDBProp;
begin
  p := InitProp(dwPropertyID);

  p.vValue := VarAsType(Value, VT_I2);
  FInitPropSet.cProperties := FInitPropSet.cProperties + 1;
end;

procedure TOLEDBPropertiesSet.AddPropInt(const dwPropertyID: DBPROPID; const Value: Integer);
var
  p: PDBProp;
begin
  p := InitProp(dwPropertyID, True);

  p.vValue := VarAsType(Value, VT_I4);
  FInitPropSet.cProperties := FInitPropSet.cProperties + 1;
end;

procedure TOLEDBPropertiesSet.AddPropBool(const dwPropertyID: DBPROPID; const Value: boolean; const Required: boolean = False);
var
  p: PDBProp;
begin
  p := InitProp(dwPropertyID, Required);

  p.vValue := VarAsType(Value, VT_BOOL);

  FInitPropSet.cProperties := FInitPropSet.cProperties + 1;
end;

procedure TOLEDBPropertiesSet.AddPropStr(const dwPropertyID: DBPROPID; const Value: string; const Required: boolean = False);
var
  p: PDBProp;
begin
  p := InitProp(dwPropertyID, Required);
  p.vValue := Value;

  FInitPropSet.cProperties := FInitPropSet.cProperties + 1;
end;

procedure TOLEDBPropertiesSet.SetProperties(Obj: IDBProperties);
begin
  Assert(Obj <> nil);
  Check(Obj.SetProperties(1, PDBPropSetArray(FInitPropSet)));
end;

procedure TOLEDBPropertiesSet.SetProperties(Obj: ISessionProperties);
begin
  Assert(Obj <> nil);
  Check(Obj.SetProperties(1, PDBPropSetArray(FInitPropSet)));
end;

procedure TOLEDBPropertiesSet.SetProperties(Obj: ICommandProperties);
begin
  Assert(Obj <> nil);
  Check(Obj.SetProperties(1, PDBPropSetArray(FInitPropSet)));
end;

{ TOLEDBPropertiesGet }

constructor TOLEDBPropertiesGet.Create(Connection: TOLEDBConnection; const GuidPropertySet: TGUID);
begin
  inherited Create;

  FConnection := Connection;

  FInitPropSet := Marshal.AllocHGlobal(SizeOf(DBPROPSET));
  FInitPropSet.cProperties := 0;
  FInitPropSet.rgProperties := nil;
  FInitPropSet.guidPropertySet := GuidPropertySet;
end;

destructor TOLEDBPropertiesGet.Destroy;
begin
  Marshal.FreeHGlobal(FInitPropSet);
  inherited;
end;

procedure TOLEDBPropertiesGet.AddPropId(Id: DBPROPID);
begin
  Assert(FPropIdsGC = nil);
  SetLength(FPropIds, Length(FPropIds) + 1);
  FPropIds[Length(FPropIds) - 1] := Id;
end;

procedure TOLEDBPropertiesGet.Check(const Status: HRESULT);
begin
  FConnection.Check(Status, nil);
end;

function TOLEDBPropertiesGet.GetDBPropPtr(rgProperties: PDBPropArray; Index: UINT): PDBProp;
begin
  Assert(Index <= FInitPropSet.cProperties);
  Result := IntPtr(Integer(rgProperties) + Integer(Index * SizeOfDBProp));
end;

procedure TOLEDBPropertiesGet.PrepareToGet;
begin
  FPropIdsGC := AllocGCHandle(FPropIds, True);
  FInitPropSet.rgProperties := GetAddrOfPinnedObject(FPropIdsGC);
  FInitPropSet.cProperties := Length(FPropIds);
end;

procedure TOLEDBPropertiesGet.ProcessResult(rgPropertySets: PDBPropSet; var PropValues: TPropValues);
var
  i: integer;
begin
  SetLength(PropValues, FInitPropSet.cProperties);
  for i := 0 to FInitPropSet.cProperties - 1 do
    PropValues[i] := GetDBPropPtr(rgPropertySets.rgProperties, i).vValue;
end;

procedure TOLEDBPropertiesGet.ClearResult(rgPropertySets: PDBPropSet);
var
  i: integer;
begin
  FreeGCHandle(FPropIdsGC);
  FPropIdsGC := nil;
  FInitPropSet.rgProperties := nil;

  for i := 0 to FInitPropSet.cProperties - 1 do
    GetDBPropPtr(rgPropertySets.rgProperties, i).vValue := Unassigned;

  FreeCoMem(rgPropertySets.rgProperties);
  FreeCoMem(rgPropertySets);
  //FConnection.Malloc.Free(rgPropertySets.rgProperties);
  //FConnection.Malloc.Free(rgPropertySets);
end;

procedure TOLEDBPropertiesGet.GetProperties(Obj: IDBProperties; var PropValues: TPropValues);
var
  cPropertySets: UINT;
  rgPropertySets: PDBPropSet;
begin
  Assert(Obj <> nil);
  PrepareToGet;
  try
    Check(Obj.GetProperties(1, PDBPropIDSetArray(FInitPropSet), cPropertySets, rgPropertySets));
    ProcessResult(rgPropertySets, PropValues);
  finally
    ClearResult(rgPropertySets);
  end;
end;

procedure TOLEDBPropertiesGet.GetProperties(Obj: IRowsetInfo; var PropValues: TPropValues);
var
  cPropertySets: UINT;
  rgPropertySets: PDBPropSet;
begin
  Assert(Obj <> nil);
  PrepareToGet;
  try
    Check(Obj.GetProperties(1, PDBPropIDSetArray(FInitPropSet), cPropertySets, rgPropertySets));
    ProcessResult(rgPropertySets, PropValues);
  finally
    ClearResult(rgPropertySets);
  end;
end;

procedure TOLEDBPropertiesGet.GetProperties(Obj: ICommandProperties; var PropValues: TPropValues);
var
  cPropertySets: UINT;
  rgPropertySets: PDBPropSet;
begin
  Assert(Obj <> nil);
  PrepareToGet;
  try
    Check(Obj.GetProperties(1, PDBPropIDSetArray(FInitPropSet), cPropertySets, rgPropertySets));
    ProcessResult(rgPropertySets, PropValues);
  finally
    ClearResult(rgPropertySets);
  end;
end;

{ TOLEDBErrors }

constructor TOLEDBErrors.Create;
begin
  inherited;
  FList := TList.Create;
end;

destructor TOLEDBErrors.Destroy;
begin
  Clear;
  FList.Free;
  inherited;
end;

procedure TOLEDBErrors.Clear;
var
  i: integer;
begin
  for i := 0 to FList.Count - 1 do
    TObject(FList[i]).Free;
  FList.Clear;
end;

function TOLEDBErrors.GetCount: integer;
begin
  Result := FList.Count;
end;

function TOLEDBErrors.GetError(Index: Integer): EOLEDBError;
begin
  Result := EOLEDBError(FList[Index]);
end;

procedure TOLEDBErrors.Assign(Source: TOLEDBErrors);
var
  i: integer;
  SrcErr, DstErr: EOLEDBError;
begin
  Clear;
  for i := 0 to Source.Count - 1 do begin
    SrcErr := Source.Errors[i];
    DstErr := nil;
    if SrcErr is EMSError then
      DstErr := EMSError.Create(SrcErr.ErrorCode, SrcErr.Message)
    else
      if SrcErr is EOLEDBError then
        DstErr := EOLEDBError.Create(SrcErr.ErrorCode, SrcErr.Message)
      else
        Assert(False);

    DstErr.Assign(SrcErr);
    FList.Add(DstErr);
  end;
end;

{ EOLEDBError }

constructor EOLEDBError.Create(ErrorCode: integer; Msg: WideString);
begin
{$IFDEF LITE}
  inherited Create(Msg);

  FErrorCode := ErrorCode;
  Message := Msg;
{$ELSE}
  inherited Create(ErrorCode, Msg);
{$ENDIF}
  FMessageWide := Msg;

  FErrors := TOLEDBErrors.Create;
end;

destructor EOLEDBError.Destroy;
begin
  FErrors.Free;

  inherited;
end;

function EOLEDBError.GetErrorCount: integer;
begin
  Result := FErrors.Count;
end;

function EOLEDBError.GetError(Index: Integer): EOLEDBError;
begin
  Result := FErrors[Index];
end;

procedure EOLEDBError.Assign(Source: EOLEDBError);
begin
  FOLEDBErrorCode := Source.FOLEDBErrorCode;
  Fiid := Source.Fiid;
{$IFNDEF LITE}
  Component := Source.Component;
{$ENDIF}
  FErrors.Assign(Source.FErrors);
end;

{ EMSError }

constructor EMSError.Create(const pServerErrorInfo: SSERRORINFO; OLEDBErrorCode: integer; Msg: WideString);
begin
  inherited Create(pServerErrorInfo.lNative, Msg);
{$IFDEF LITE}
  FErrorCode := pServerErrorInfo.lNative;
{$ENDIF}

  FMSSQLErrorCode := pServerErrorInfo.lNative;
  FServerName := pServerErrorInfo.pwszServer;
  FProcName := pServerErrorInfo.pwszProcedure;
  FState := pServerErrorInfo.bState;
  FSeverityClass := pServerErrorInfo.bClass;
  FLineNumber := pServerErrorInfo.wLineNumber;
  FLastMessage := pServerErrorInfo.pwszMessage;
end;

procedure EMSError.Assign(Source: EOLEDBError);
var
  Src: EMSError;
begin
  inherited;

  if Source is EMSError then begin
    Src := EMSError(Source);
    FMSSQLErrorCode := Src.FMSSQLErrorCode;

    FServerName := Src.ServerName;
    FProcName := Src.ProcName;
    FState := Src.State;
    FSeverityClass := Src.SeverityClass;
    FLineNumber := Src.LineNumber;
    FLastMessage := Src.LastMessage;
  end;
end;

var
  DataSourceTypes: TBytes;
{$IFDEF CLR}
  DataSourceTypesGC: GCHandle;
{$ENDIF}

procedure InitDataSourceTypes;
var
  pDataSourceTypes: IntPtr;
  byteIndex: integer;

  function AddType(const TypeName: WideString): IntPtr;
  var
    Cnt: integer;
  begin
    Result := IntPtr(Integer(pDataSourceTypes) + byteIndex);
    Cnt := Encoding.Unicode.{$IFNDEF VER5}GetBytes{$ELSE}GetBytesWide{$ENDIF}(TypeName, 0, Length(TypeName), DataSourceTypes, byteIndex);
    DataSourceTypes[byteIndex + Cnt] := 0;
    DataSourceTypes[byteIndex + Cnt + 1] := 0;
    byteIndex := byteIndex + Cnt + 2;
    Assert(byteIndex + Cnt < Length(DataSourceTypes));
  end;

begin
  //SetLength(DataSourceTypes, 300);
  SetLength(DataSourceTypes, 350);
{$IFDEF CLR}
  DataSourceTypesGC := GCHandle.Alloc(DataSourceTypes, GCHandleType.Pinned);
  pDataSourceTypes := Marshal.UnsafeAddrOfPinnedArrayElement(DataSourceTypes, 0);
{$ELSE}
  pDataSourceTypes := @DataSourceTypes[0];
{$ENDIF}
  byteIndex := 0;

  dstSmallint := AddType('smallint');
  dstInt := AddType('int');
  dstReal := AddType('real');
  dstFloat := AddType('float');
  dstMoney := AddType('money');
  dstDateTime := AddType('datetime');
  dstNVarChar := AddType('nvarchar');
  dstNVarCharMax := AddType('nvarchar(max)');
  dstVarChar := AddType('varchar');
  dstVarCharMax := AddType('varchar(max)');
  dstBit := AddType('bit');
  dstTinyInt := AddType('tinyint');
  dstBigint := AddType('bigint');
  dstSql_variant := AddType('sql_variant');
  dstImage := AddType('image');
  dstBinary := AddType('binary');
  dstVarBinary := AddType('varbinary');
  dstGuid := AddType('uniqueidentifier');
end;

procedure FinalizeDataSourceTypes;
begin
{$IFDEF CLR}
  if IntPtr(DataSourceTypesGC) <> nil then
    DataSourceTypesGC.Free;
{$ENDIF}
end;

var
  OSVersionInfo: TOSVersionInfo;

initialization
  __UseRPCCallStyle := True;
  InitDataSourceTypes;
  ParamsInfoOldBehavior := False; // delete 03.06.2006
  // Windows Vista detecting
  OSVersionInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
  Windows.GetVersionEx(OSVersionInfo);
  IsWindowsVista := OSVersionInfo.dwMajorVersion = 6;


finalization
{$IFDEF DEBUG} if StreamCnt <> 0 then MessageBox(0, PChar(IntToStr(StreamCnt) + ' Stream(s) hasn''t been released'), 'DA warning', MB_OK); {$ENDIF}
  FinalizeDataSourceTypes;
{$IFNDEF CLR}
  GlobaIMalloc := nil;
{$ENDIF}
end.
