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